mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 22:43:33 +08:00
DEV: Refactor webauthn to support passkeys (1/3) (#23586)
This is part 1 of 3, split up of PR #23529. This PR refactors the webauthn code to support passkey authentication/registration. Passkeys aren't used yet, that is coming in PRs 2 and 3. Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
This commit is contained in:
89
lib/discourse_webauthn/base_validation_service.rb
Normal file
89
lib/discourse_webauthn/base_validation_service.rb
Normal file
@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseWebauthn
|
||||
class BaseValidationService
|
||||
def initialize(current_user, params, session:, factor_type:)
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
@factor_type = factor_type
|
||||
@session = session
|
||||
end
|
||||
|
||||
def validate_webauthn_type(type_to_check)
|
||||
return if client_data["type"] == type_to_check
|
||||
raise(InvalidTypeError, I18n.t("webauthn.validation.invalid_type_error"))
|
||||
end
|
||||
|
||||
def validate_challenge
|
||||
return if challenge_match?
|
||||
raise(ChallengeMismatchError, I18n.t("webauthn.validation.challenge_mismatch_error"))
|
||||
end
|
||||
|
||||
def validate_origin
|
||||
return if origin_match?
|
||||
raise(InvalidOriginError, I18n.t("webauthn.validation.invalid_origin_error"))
|
||||
end
|
||||
|
||||
def validate_rp_id_hash
|
||||
return if rp_id_hash_match?
|
||||
raise(
|
||||
InvalidRelyingPartyIdError,
|
||||
I18n.t("webauthn.validation.invalid_relying_party_id_error"),
|
||||
)
|
||||
end
|
||||
|
||||
## flags per specification
|
||||
# https://www.w3.org/TR/webauthn-2/#sctn-authenticator-data
|
||||
# bit 0 - user presence
|
||||
# bit 1 - reserved for future use
|
||||
# bit 2 - user verification
|
||||
# bit 3-5 - reserved for future use
|
||||
# bit 6 - attested credential data
|
||||
# bit 7 - extension data
|
||||
|
||||
def validate_user_presence
|
||||
flags = auth_data[32].unpack("b*")[0].split("")
|
||||
# bit 0 - user presence
|
||||
return if flags[0] == "1"
|
||||
raise(UserPresenceError, I18n.t("webauthn.validation.user_presence_error"))
|
||||
end
|
||||
|
||||
def validate_user_verification
|
||||
flags = auth_data[32].unpack("b*")[0].split("")
|
||||
# bit 2 - user verification
|
||||
return if flags[2] == "1"
|
||||
raise(UserVerificationError, I18n.t("webauthn.validation.user_verification_error"))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# https://w3c.github.io/webauthn/#sctn-registering-a-new-credential
|
||||
# Let JSONtext be the result of running UTF-8 decode on the value of response.clientDataJSON.
|
||||
def client_data_json
|
||||
@client_data_json ||= Base64.decode64(@params[:clientData])
|
||||
end
|
||||
|
||||
# Let C, the client data claimed as collected during the credential creation, be the result of running
|
||||
# an implementation-specific JSON parser on JSONtext.
|
||||
def client_data
|
||||
@client_data ||= JSON.parse(client_data_json)
|
||||
end
|
||||
|
||||
def challenge_match?
|
||||
Base64.decode64(client_data["challenge"]) ==
|
||||
DiscourseWebauthn.challenge(@current_user, @session)
|
||||
end
|
||||
|
||||
def origin_match?
|
||||
client_data["origin"] == DiscourseWebauthn.origin
|
||||
end
|
||||
|
||||
def rp_id_hash_match?
|
||||
auth_data[0..31] == OpenSSL::Digest::SHA256.digest(DiscourseWebauthn.rp_id)
|
||||
end
|
||||
|
||||
def client_data_hash
|
||||
@client_data_hash ||= OpenSSL::Digest::SHA256.digest(client_data_json)
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user