From 10c6b2a0c25e8436a918b050e7fdd92f9fa42024 Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Fri, 18 Aug 2023 08:39:10 -0400 Subject: [PATCH] WIP: Rename Webauthn to DiscourseWebauthn (#23077) --- app/controllers/session_controller.rb | 12 ++--- app/controllers/users_controller.rb | 25 +++++----- app/controllers/users_email_controller.rb | 6 +-- app/models/concerns/second_factor_manager.rb | 8 ++-- lib/{webauthn.rb => discourse_webauthn.rb} | 48 ++++++++++++++++--- lib/webauthn/challenge_generator.rb | 2 +- .../security_key_authentication_service.rb | 4 +- .../security_key_base_validation_service.rb | 2 +- .../security_key_registration_service.rb | 8 ++-- .../lib/concern/second_factor_manager_spec.rb | 6 +-- spec/lib/webauthn/challenge_generator_spec.rb | 10 ++-- ...ecurity_key_authentication_service_spec.rb | 24 +++++----- .../security_key_registration_service_spec.rb | 36 +++++++------- spec/rails_helper.rb | 2 +- spec/requests/session_controller_spec.rb | 6 ++- spec/requests/users_controller_spec.rb | 14 +++--- spec/support/webauthn_integration_helpers.rb | 10 ++-- 17 files changed, 135 insertions(+), 88 deletions(-) rename lib/{webauthn.rb => discourse_webauthn.rb} (52%) diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index dc20fbb8696..0f0dbe98982 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -344,9 +344,9 @@ class SessionController < ApplicationController end if matched_user&.security_keys_enabled? - Webauthn.stage_challenge(matched_user, secure_session) + DiscourseWebauthn.stage_challenge(matched_user, secure_session) response.merge!( - Webauthn.allowed_credentials(matched_user, secure_session).merge( + DiscourseWebauthn.allowed_credentials(matched_user, secure_session).merge( security_key_required: true, ), ) @@ -433,8 +433,8 @@ class SessionController < ApplicationController allowed_methods: challenge[:allowed_methods], ) if user.security_keys_enabled? - Webauthn.stage_challenge(user, secure_session) - json.merge!(Webauthn.allowed_credentials(user, secure_session)) + DiscourseWebauthn.stage_challenge(user, secure_session) + json.merge!(DiscourseWebauthn.allowed_credentials(user, secure_session)) json[:security_keys_enabled] = true else json[:security_keys_enabled] = false @@ -660,8 +660,8 @@ class SessionController < ApplicationController if !second_factor_authentication_result.ok failure_payload = second_factor_authentication_result.to_h if user.security_keys_enabled? - Webauthn.stage_challenge(user, secure_session) - failure_payload.merge!(Webauthn.allowed_credentials(user, secure_session)) + DiscourseWebauthn.stage_challenge(user, secure_session) + failure_payload.merge!(DiscourseWebauthn.allowed_credentials(user, secure_session)) end @second_factor_failure_payload = failed_json.merge(failure_payload) return second_factor_authentication_result diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index b907bb0b0e4..33f2db264c8 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -813,11 +813,11 @@ class UsersController < ApplicationController format.html do return render "password_reset", layout: "no_ember" if @error - Webauthn.stage_challenge(@user, secure_session) + DiscourseWebauthn.stage_challenge(@user, secure_session) store_preloaded( "password_reset", MultiJson.dump( - security_params.merge(Webauthn.allowed_credentials(@user, secure_session)), + security_params.merge(DiscourseWebauthn.allowed_credentials(@user, secure_session)), ), ) @@ -827,8 +827,9 @@ class UsersController < ApplicationController format.json do return render json: { message: @error } if @error - Webauthn.stage_challenge(@user, secure_session) - render json: security_params.merge(Webauthn.allowed_credentials(@user, secure_session)) + DiscourseWebauthn.stage_challenge(@user, secure_session) + render json: + security_params.merge(DiscourseWebauthn.allowed_credentials(@user, secure_session)) end end end @@ -895,7 +896,7 @@ class UsersController < ApplicationController format.html do return render "password_reset", layout: "no_ember" if @error - Webauthn.stage_challenge(@user, secure_session) + DiscourseWebauthn.stage_challenge(@user, secure_session) security_params = { is_developer: UsernameCheckerService.is_developer?(@user.email), @@ -904,7 +905,7 @@ class UsersController < ApplicationController security_key_required: @user.security_keys_enabled?, backup_enabled: @user.backup_codes_enabled?, multiple_second_factor_methods: @user.has_multiple_second_factor_methods?, - }.merge(Webauthn.allowed_credentials(@user, secure_session)) + }.merge(DiscourseWebauthn.allowed_credentials(@user, secure_session)) store_preloaded("password_reset", MultiJson.dump(security_params)) @@ -1545,13 +1546,13 @@ class UsersController < ApplicationController end def create_second_factor_security_key - challenge_session = Webauthn.stage_challenge(current_user, secure_session) + challenge_session = DiscourseWebauthn.stage_challenge(current_user, secure_session) render json: success_json.merge( challenge: challenge_session.challenge, rp_id: challenge_session.rp_id, rp_name: challenge_session.rp_name, - supported_algorithms: ::Webauthn::SUPPORTED_ALGORITHMS, + supported_algorithms: ::DiscourseWebauthn::SUPPORTED_ALGORITHMS, user_secure_id: current_user.create_or_fetch_secure_identifier, existing_active_credential_ids: current_user.second_factor_security_key_credential_ids, @@ -1563,15 +1564,15 @@ class UsersController < ApplicationController params.require(:attestation) params.require(:clientData) - ::Webauthn::SecurityKeyRegistrationService.new( + ::DiscourseWebauthn::SecurityKeyRegistrationService.new( current_user, params, - challenge: Webauthn.challenge(current_user, secure_session), - rp_id: Webauthn.rp_id(current_user, secure_session), + challenge: DiscourseWebauthn.challenge(current_user, secure_session), + rp_id: DiscourseWebauthn.rp_id(current_user, secure_session), origin: Discourse.base_url, ).register_second_factor_security_key render json: success_json - rescue ::Webauthn::SecurityKeyError => err + rescue ::DiscourseWebauthn::SecurityKeyError => err render json: failed_json.merge(error: err.message) end diff --git a/app/controllers/users_email_controller.rb b/app/controllers/users_email_controller.rb index 98ac6458ed0..69957486877 100644 --- a/app/controllers/users_email_controller.rb +++ b/app/controllers/users_email_controller.rb @@ -127,11 +127,11 @@ class UsersEmailController < ApplicationController else @show_second_factor = true if @user.totp_enabled? if @user.security_keys_enabled? - Webauthn.stage_challenge(@user, secure_session) + DiscourseWebauthn.stage_challenge(@user, secure_session) @show_security_key = params[:show_totp].to_s == "true" ? false : true - @security_key_challenge = Webauthn.challenge(@user, secure_session) + @security_key_challenge = DiscourseWebauthn.challenge(@user, secure_session) @security_key_allowed_credential_ids = - Webauthn.allowed_credentials(@user, secure_session)[:allowed_credential_ids] + DiscourseWebauthn.allowed_credentials(@user, secure_session)[:allowed_credential_ids] end end diff --git a/app/models/concerns/second_factor_manager.rb b/app/models/concerns/second_factor_manager.rb index 0adad926788..d173a3d7dc3 100644 --- a/app/models/concerns/second_factor_manager.rb +++ b/app/models/concerns/second_factor_manager.rb @@ -146,7 +146,7 @@ module SecondFactorManager # if we have gotten down to this point without being # OK or invalid something has gone very weird. invalid_second_factor_method_result - rescue ::Webauthn::SecurityKeyError => err + rescue ::DiscourseWebauthn::SecurityKeyError => err invalid_security_key_result(err.message) end @@ -163,11 +163,11 @@ module SecondFactorManager end def authenticate_security_key(secure_session, security_key_credential) - ::Webauthn::SecurityKeyAuthenticationService.new( + ::DiscourseWebauthn::SecurityKeyAuthenticationService.new( self, security_key_credential, - challenge: Webauthn.challenge(self, secure_session), - rp_id: Webauthn.rp_id(self, secure_session), + challenge: DiscourseWebauthn.challenge(self, secure_session), + rp_id: DiscourseWebauthn.rp_id(self, secure_session), origin: Discourse.base_url, ).authenticate_security_key end diff --git a/lib/webauthn.rb b/lib/discourse_webauthn.rb similarity index 52% rename from lib/webauthn.rb rename to lib/discourse_webauthn.rb index ef47bd9960d..ff3158b2584 100644 --- a/lib/webauthn.rb +++ b/lib/discourse_webauthn.rb @@ -4,7 +4,7 @@ require "webauthn/security_key_base_validation_service" require "webauthn/security_key_registration_service" require "webauthn/security_key_authentication_service" -module Webauthn +module DiscourseWebauthn ACCEPTABLE_REGISTRATION_TYPE = "webauthn.create" ACCEPTABLE_AUTHENTICATION_TYPE = "webauthn.get" @@ -51,7 +51,7 @@ module Webauthn # they must respond with a valid webauthn response and # credentials. def self.stage_challenge(user, secure_session) - ::Webauthn::ChallengeGenerator.generate.commit_to_session(secure_session, user) + ::DiscourseWebauthn::ChallengeGenerator.generate.commit_to_session(secure_session, user) end def self.allowed_credentials(user, secure_session) @@ -60,19 +60,55 @@ module Webauthn { allowed_credential_ids: credential_ids, challenge: - secure_session[Webauthn::ChallengeGenerator::ChallengeSession.session_challenge_key(user)], + secure_session[ + DiscourseWebauthn::ChallengeGenerator::ChallengeSession.session_challenge_key(user) + ], } end def self.rp_id(user, secure_session) - secure_session[Webauthn::ChallengeGenerator::ChallengeSession.session_rp_id_key(user)] + secure_session[DiscourseWebauthn::ChallengeGenerator::ChallengeSession.session_rp_id_key(user)] end def self.rp_name(user, secure_session) - secure_session[Webauthn::ChallengeGenerator::ChallengeSession.session_rp_name_key(user)] + secure_session[ + DiscourseWebauthn::ChallengeGenerator::ChallengeSession.session_rp_name_key(user) + ] end def self.challenge(user, secure_session) - secure_session[Webauthn::ChallengeGenerator::ChallengeSession.session_challenge_key(user)] + secure_session[ + DiscourseWebauthn::ChallengeGenerator::ChallengeSession.session_challenge_key(user) + ] + end + + def self.validate_first_factor_key(key) + pp key + webauthn_credential = DiscourseWebauthn::Credential.from_get(key) + p "webauthn_credential" + pp webauthn_credential + + # stored_credential = user.credentials.find_by(webauthn_id: webauthn_credential.id) + + # begin + # webauthn_credential.verify( + # session[:authentication_challenge], + # public_key: stored_credential.public_key, + # sign_count: stored_credential.sign_count + # ) + + # # Update the stored credential sign count with the value from `webauthn_credential.sign_count` + # stored_credential.update!(sign_count: webauthn_credential.sign_count) + + # # Continue with successful sign in or 2FA verification... + + # rescue ::WebAuthn::SignCountVerificationError => e + # # Cryptographic verification of the authenticator data succeeded, but the signature counter was less then or equal + # # to the stored value. This can have several reasons and depending on your risk tolerance you can choose to fail or + # # pass authentication. For more information see https://www.w3.org/TR/webauthn/#sign-counter + # pp e + # rescue ::WebAuthn::Error => e + # # Handle error + # end end end diff --git a/lib/webauthn/challenge_generator.rb b/lib/webauthn/challenge_generator.rb index cd08e06593c..f6704bd94dc 100644 --- a/lib/webauthn/challenge_generator.rb +++ b/lib/webauthn/challenge_generator.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -module Webauthn +module DiscourseWebauthn class ChallengeGenerator class ChallengeSession attr_reader :challenge, :rp_id, :rp_name diff --git a/lib/webauthn/security_key_authentication_service.rb b/lib/webauthn/security_key_authentication_service.rb index 5da5b6b5a7c..920b726e1b4 100644 --- a/lib/webauthn/security_key_authentication_service.rb +++ b/lib/webauthn/security_key_authentication_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "cose" -module Webauthn +module DiscourseWebauthn class SecurityKeyAuthenticationService < SecurityKeyBaseValidationService ## # See https://w3c.github.io/webauthn/#sctn-verifying-assertion for @@ -30,7 +30,7 @@ module Webauthn client_data # 8. Verify that the value of C.type is the string webauthn.get. - validate_webauthn_type(::Webauthn::ACCEPTABLE_AUTHENTICATION_TYPE) + validate_webauthn_type(::DiscourseWebauthn::ACCEPTABLE_AUTHENTICATION_TYPE) # 9. Verify that the value of C.challenge equals the base64url encoding of options.challenge. validate_challenge diff --git a/lib/webauthn/security_key_base_validation_service.rb b/lib/webauthn/security_key_base_validation_service.rb index 5af6a52529f..6cfc50f3f9c 100644 --- a/lib/webauthn/security_key_base_validation_service.rb +++ b/lib/webauthn/security_key_base_validation_service.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module Webauthn +module DiscourseWebauthn class SecurityKeyBaseValidationService def initialize(current_user, params, challenge_params) @current_user = current_user diff --git a/lib/webauthn/security_key_registration_service.rb b/lib/webauthn/security_key_registration_service.rb index 1695fddf232..3a8ccd3deb2 100644 --- a/lib/webauthn/security_key_registration_service.rb +++ b/lib/webauthn/security_key_registration_service.rb @@ -2,7 +2,7 @@ require "cbor" require "cose" -module Webauthn +module DiscourseWebauthn class SecurityKeyRegistrationService < SecurityKeyBaseValidationService ## # See https://w3c.github.io/webauthn/#sctn-registering-a-new-credential for @@ -10,7 +10,7 @@ module Webauthn # place in the step flow to make the process clearer. def register_second_factor_security_key # 4. Verify that the value of C.type is webauthn.create. - validate_webauthn_type(::Webauthn::ACCEPTABLE_REGISTRATION_TYPE) + validate_webauthn_type(::DiscourseWebauthn::ACCEPTABLE_REGISTRATION_TYPE) # 5. Verify that the value of C.challenge equals the base64url encoding of options.challenge. validate_challenge @@ -51,7 +51,7 @@ module Webauthn # codes. credential_public_key, credential_public_key_bytes, credential_id = extract_public_key_and_credential_from_attestation(auth_data) - if ::Webauthn::SUPPORTED_ALGORITHMS.exclude?(credential_public_key.alg) + if ::DiscourseWebauthn::SUPPORTED_ALGORITHMS.exclude?(credential_public_key.alg) raise( UnsupportedPublicKeyAlgorithmError, I18n.t("webauthn.validation.unsupported_public_key_algorithm_error"), @@ -72,7 +72,7 @@ module Webauthn # name [WebAuthn-Registries]. # 16. Verify that attStmt is a correct attestation statement, conveying a valid attestation signature, # by using the attestation statement format fmt’s verification procedure given attStmt, authData and hash. - if ::Webauthn::VALID_ATTESTATION_FORMATS.exclude?(attestation["fmt"]) || + if ::DiscourseWebauthn::VALID_ATTESTATION_FORMATS.exclude?(attestation["fmt"]) || attestation["fmt"] != "none" raise( UnsupportedAttestationFormatError, diff --git a/spec/lib/concern/second_factor_manager_spec.rb b/spec/lib/concern/second_factor_manager_spec.rb index 80dfeb67a43..4a7f5a0c00b 100644 --- a/spec/lib/concern/second_factor_manager_spec.rb +++ b/spec/lib/concern/second_factor_manager_spec.rb @@ -182,7 +182,7 @@ RSpec.describe SecondFactorManager do before do disable_totp simulate_localhost_webauthn_challenge - Webauthn.stage_challenge(user, secure_session) + DiscourseWebauthn.stage_challenge(user, secure_session) end context "when security key params are valid" do @@ -264,7 +264,7 @@ RSpec.describe SecondFactorManager do before do simulate_localhost_webauthn_challenge - Webauthn.stage_challenge(user, secure_session) + DiscourseWebauthn.stage_challenge(user, secure_session) end context "when method selected is invalid" do @@ -312,7 +312,7 @@ RSpec.describe SecondFactorManager do before do simulate_localhost_webauthn_challenge - Webauthn.stage_challenge(user, secure_session) + DiscourseWebauthn.stage_challenge(user, secure_session) end context "when security key params are valid" do diff --git a/spec/lib/webauthn/challenge_generator_spec.rb b/spec/lib/webauthn/challenge_generator_spec.rb index 4a6c2617626..faa0bb5f156 100644 --- a/spec/lib/webauthn/challenge_generator_spec.rb +++ b/spec/lib/webauthn/challenge_generator_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -RSpec.describe Webauthn::ChallengeGenerator do - it "generates a Webauthn::ChallengeGenerator::ChallengeSession with correct params" do - session = Webauthn::ChallengeGenerator.generate - expect(session).to be_a(Webauthn::ChallengeGenerator::ChallengeSession) +RSpec.describe DiscourseWebauthn::ChallengeGenerator do + it "generates a DiscourseWebauthn::ChallengeGenerator::ChallengeSession with correct params" do + session = DiscourseWebauthn::ChallengeGenerator.generate + expect(session).to be_a(DiscourseWebauthn::ChallengeGenerator::ChallengeSession) expect(session.challenge).not_to eq(nil) expect(session.rp_id).to eq(Discourse.current_hostname) expect(session.rp_name).to eq(SiteSetting.title) @@ -15,7 +15,7 @@ RSpec.describe Webauthn::ChallengeGenerator do it "stores the challenge, rp id, and rp name in the provided session object" do secure_session = {} - generated_session = Webauthn::ChallengeGenerator.generate + generated_session = DiscourseWebauthn::ChallengeGenerator.generate generated_session.commit_to_session(secure_session, user) expect(secure_session["staged-webauthn-challenge-#{user&.id}"]).to eq( diff --git a/spec/lib/webauthn/security_key_authentication_service_spec.rb b/spec/lib/webauthn/security_key_authentication_service_spec.rb index 3ca7e5bc91d..4cfc11981c7 100644 --- a/spec/lib/webauthn/security_key_authentication_service_spec.rb +++ b/spec/lib/webauthn/security_key_authentication_service_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require "webauthn" +require "discourse_webauthn" require "webauthn/security_key_registration_service" ## @@ -37,7 +37,7 @@ require "webauthn/security_key_registration_service" # # The origin params just need to be whatever your localhost URL for Discourse is. -RSpec.describe Webauthn::SecurityKeyAuthenticationService do +RSpec.describe DiscourseWebauthn::SecurityKeyAuthenticationService do subject(:service) { described_class.new(current_user, params, challenge_params) } let(:security_key_user) { current_user } @@ -118,7 +118,7 @@ RSpec.describe Webauthn::SecurityKeyAuthenticationService do it "raises a NotFoundError" do expect { service.authenticate_security_key }.to raise_error( - Webauthn::NotFoundError, + DiscourseWebauthn::NotFoundError, I18n.t("webauthn.validation.not_found_error"), ) end @@ -129,7 +129,7 @@ RSpec.describe Webauthn::SecurityKeyAuthenticationService do it "raises an OwnershipError" do expect { service.authenticate_security_key }.to raise_error( - Webauthn::OwnershipError, + DiscourseWebauthn::OwnershipError, I18n.t("webauthn.validation.ownership_error"), ) end @@ -140,7 +140,7 @@ RSpec.describe Webauthn::SecurityKeyAuthenticationService do it "raises an InvalidTypeError" do expect { service.authenticate_security_key }.to raise_error( - Webauthn::InvalidTypeError, + DiscourseWebauthn::InvalidTypeError, I18n.t("webauthn.validation.invalid_type_error"), ) end @@ -151,7 +151,7 @@ RSpec.describe Webauthn::SecurityKeyAuthenticationService do it "raises a ChallengeMismatchError" do expect { service.authenticate_security_key }.to raise_error( - Webauthn::ChallengeMismatchError, + DiscourseWebauthn::ChallengeMismatchError, I18n.t("webauthn.validation.challenge_mismatch_error"), ) end @@ -162,7 +162,7 @@ RSpec.describe Webauthn::SecurityKeyAuthenticationService do it "raises a InvalidOriginError" do expect { service.authenticate_security_key }.to raise_error( - Webauthn::InvalidOriginError, + DiscourseWebauthn::InvalidOriginError, I18n.t("webauthn.validation.invalid_origin_error"), ) end @@ -173,7 +173,7 @@ RSpec.describe Webauthn::SecurityKeyAuthenticationService do it "raises a InvalidRelyingPartyIdError" do expect { service.authenticate_security_key }.to raise_error( - Webauthn::InvalidRelyingPartyIdError, + DiscourseWebauthn::InvalidRelyingPartyIdError, I18n.t("webauthn.validation.invalid_relying_party_id_error"), ) end @@ -184,7 +184,7 @@ RSpec.describe Webauthn::SecurityKeyAuthenticationService do it "raises a PublicKeyError" do expect { service.authenticate_security_key }.to raise_error( - Webauthn::PublicKeyError, + DiscourseWebauthn::PublicKeyError, I18n.t("webauthn.validation.public_key_error"), ) end @@ -195,7 +195,7 @@ RSpec.describe Webauthn::SecurityKeyAuthenticationService do it "raises a UnknownCOSEAlgorithmError" do expect { service.authenticate_security_key }.to raise_error( - Webauthn::UnknownCOSEAlgorithmError, + DiscourseWebauthn::UnknownCOSEAlgorithmError, I18n.t("webauthn.validation.unknown_cose_algorithm_error"), ) end @@ -237,6 +237,8 @@ RSpec.describe Webauthn::SecurityKeyAuthenticationService do end it "all supported algorithms are implemented" do - Webauthn::SUPPORTED_ALGORITHMS.each { |alg| expect(COSE::Algorithm.find(alg)).not_to be_nil } + DiscourseWebauthn::SUPPORTED_ALGORITHMS.each do |alg| + expect(COSE::Algorithm.find(alg)).not_to be_nil + end end end diff --git a/spec/lib/webauthn/security_key_registration_service_spec.rb b/spec/lib/webauthn/security_key_registration_service_spec.rb index 45d78c516e1..e45c1766d2c 100644 --- a/spec/lib/webauthn/security_key_registration_service_spec.rb +++ b/spec/lib/webauthn/security_key_registration_service_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require "webauthn" +require "discourse_webauthn" require "webauthn/security_key_registration_service" -RSpec.describe Webauthn::SecurityKeyRegistrationService do +RSpec.describe DiscourseWebauthn::SecurityKeyRegistrationService do subject(:service) { described_class.new(current_user, params, challenge_params) } let(:client_data_challenge) { Base64.encode64(challenge) } @@ -42,7 +42,7 @@ RSpec.describe Webauthn::SecurityKeyRegistrationService do it "raises an InvalidTypeError" do expect { service.register_second_factor_security_key }.to raise_error( - Webauthn::InvalidTypeError, + DiscourseWebauthn::InvalidTypeError, I18n.t("webauthn.validation.invalid_type_error"), ) end @@ -53,7 +53,7 @@ RSpec.describe Webauthn::SecurityKeyRegistrationService do it "raises a ChallengeMismatchError" do expect { service.register_second_factor_security_key }.to raise_error( - Webauthn::ChallengeMismatchError, + DiscourseWebauthn::ChallengeMismatchError, I18n.t("webauthn.validation.challenge_mismatch_error"), ) end @@ -64,7 +64,7 @@ RSpec.describe Webauthn::SecurityKeyRegistrationService do it "raises a InvalidOriginError" do expect { service.register_second_factor_security_key }.to raise_error( - Webauthn::InvalidOriginError, + DiscourseWebauthn::InvalidOriginError, I18n.t("webauthn.validation.invalid_origin_error"), ) end @@ -75,7 +75,7 @@ RSpec.describe Webauthn::SecurityKeyRegistrationService do it "raises a InvalidRelyingPartyIdError" do expect { service.register_second_factor_security_key }.to raise_error( - Webauthn::InvalidRelyingPartyIdError, + DiscourseWebauthn::InvalidRelyingPartyIdError, I18n.t("webauthn.validation.invalid_relying_party_id_error"), ) end @@ -83,35 +83,39 @@ RSpec.describe Webauthn::SecurityKeyRegistrationService do context "when the public key algorithm is not supported by the server" do before do - @original_supported_alg_value = Webauthn::SUPPORTED_ALGORITHMS - silence_warnings { Webauthn::SUPPORTED_ALGORITHMS = [-999] } + @original_supported_alg_value = DiscourseWebauthn::SUPPORTED_ALGORITHMS + silence_warnings { DiscourseWebauthn::SUPPORTED_ALGORITHMS = [-999] } end it "raises a UnsupportedPublicKeyAlgorithmError" do expect { service.register_second_factor_security_key }.to raise_error( - Webauthn::UnsupportedPublicKeyAlgorithmError, + DiscourseWebauthn::UnsupportedPublicKeyAlgorithmError, I18n.t("webauthn.validation.unsupported_public_key_algorithm_error"), ) end - after { silence_warnings { Webauthn::SUPPORTED_ALGORITHMS = @original_supported_alg_value } } + after do + silence_warnings { DiscourseWebauthn::SUPPORTED_ALGORITHMS = @original_supported_alg_value } + end end context "when the attestation format is not supported" do before do - @original_supported_alg_value = Webauthn::VALID_ATTESTATION_FORMATS - silence_warnings { Webauthn::VALID_ATTESTATION_FORMATS = ["err"] } + @original_supported_alg_value = DiscourseWebauthn::VALID_ATTESTATION_FORMATS + silence_warnings { DiscourseWebauthn::VALID_ATTESTATION_FORMATS = ["err"] } end it "raises a UnsupportedAttestationFormatError" do expect { service.register_second_factor_security_key }.to raise_error( - Webauthn::UnsupportedAttestationFormatError, + DiscourseWebauthn::UnsupportedAttestationFormatError, I18n.t("webauthn.validation.unsupported_attestation_format_error"), ) end after do - silence_warnings { Webauthn::VALID_ATTESTATION_FORMATS = @original_supported_alg_value } + silence_warnings do + DiscourseWebauthn::VALID_ATTESTATION_FORMATS = @original_supported_alg_value + end end end @@ -126,7 +130,7 @@ RSpec.describe Webauthn::SecurityKeyRegistrationService do # error! expect { service.register_second_factor_security_key }.to raise_error( - Webauthn::CredentialIdInUseError, + DiscourseWebauthn::CredentialIdInUseError, I18n.t("webauthn.validation.credential_id_in_use_error"), ) end @@ -139,7 +143,7 @@ RSpec.describe Webauthn::SecurityKeyRegistrationService do it "raises a MalformedAttestationError" do expect { service.register_second_factor_security_key }.to raise_error( - Webauthn::MalformedAttestationError, + DiscourseWebauthn::MalformedAttestationError, I18n.t("webauthn.validation.malformed_attestation_error"), ) end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 62fb77e15f5..c0b187c1985 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -182,7 +182,7 @@ RSpec.configure do |config| config.include RSpecHtmlMatchers config.include IntegrationHelpers, type: :request config.include SystemHelpers, type: :system - config.include WebauthnIntegrationHelpers + config.include DiscourseWebauthnIntegrationHelpers config.include SiteSettingsHelpers config.include SidekiqHelpers config.include UploadsHelpers diff --git a/spec/requests/session_controller_spec.rb b/spec/requests/session_controller_spec.rb index f399c34a647..36f8c2382f9 100644 --- a/spec/requests/session_controller_spec.rb +++ b/spec/requests/session_controller_spec.rb @@ -117,8 +117,10 @@ RSpec.describe SessionController do [user_security_key.credential_id], ) secure_session = SecureSession.new(session["secure_session_id"]) - expect(response_body_parsed["challenge"]).to eq(Webauthn.challenge(user, secure_session)) - expect(Webauthn.rp_id(user, secure_session)).to eq(Discourse.current_hostname) + expect(response_body_parsed["challenge"]).to eq( + DiscourseWebauthn.challenge(user, secure_session), + ) + expect(DiscourseWebauthn.rp_id(user, secure_session)).to eq(Discourse.current_hostname) end end end diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 5c6b914b40b..d84d4a446c3 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -459,8 +459,8 @@ RSpec.describe UsersController do it "stages a webauthn challenge and rp-id for the user" do secure_session = SecureSession.new(session["secure_session_id"]) - expect(Webauthn.challenge(user1, secure_session)).not_to eq(nil) - expect(Webauthn.rp_id(user1, secure_session)).to eq(Discourse.current_hostname) + expect(DiscourseWebauthn.challenge(user1, secure_session)).not_to eq(nil) + expect(DiscourseWebauthn.rp_id(user1, secure_session)).to eq(Discourse.current_hostname) end it "changes password with valid security key challenge and authentication" do @@ -5658,13 +5658,15 @@ RSpec.describe UsersController do create_second_factor_security_key secure_session = read_secure_session response_parsed = response.parsed_body - expect(response_parsed["challenge"]).to eq(Webauthn.challenge(user1, secure_session)) - expect(response_parsed["rp_id"]).to eq(Webauthn.rp_id(user1, secure_session)) - expect(response_parsed["rp_name"]).to eq(Webauthn.rp_name(user1, secure_session)) + expect(response_parsed["challenge"]).to eq(DiscourseWebauthn.challenge(user1, secure_session)) + expect(response_parsed["rp_id"]).to eq(DiscourseWebauthn.rp_id(user1, secure_session)) + expect(response_parsed["rp_name"]).to eq(DiscourseWebauthn.rp_name(user1, secure_session)) expect(response_parsed["user_secure_id"]).to eq( user1.reload.create_or_fetch_secure_identifier, ) - expect(response_parsed["supported_algorithms"]).to eq(::Webauthn::SUPPORTED_ALGORITHMS) + expect(response_parsed["supported_algorithms"]).to eq( + ::DiscourseWebauthn::SUPPORTED_ALGORITHMS, + ) end context "if the user has security key credentials already" do diff --git a/spec/support/webauthn_integration_helpers.rb b/spec/support/webauthn_integration_helpers.rb index a62a459500a..e5648f4649f 100644 --- a/spec/support/webauthn_integration_helpers.rb +++ b/spec/support/webauthn_integration_helpers.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module WebauthnIntegrationHelpers +module DiscourseWebauthnIntegrationHelpers ## # Usage notes: # @@ -10,8 +10,8 @@ module WebauthnIntegrationHelpers # # To make this all work together you need to # create a UserSecurityKey for a user using valid_security_key_data, - # and you override Webauthn::ChallengeGenerator.generate to return - # a Webauthn::ChallengeGenerator::ChallengeSession object using + # and you override DiscourseWebauthn::ChallengeGenerator.generate to return + # a DiscourseWebauthn::ChallengeGenerator::ChallengeSession object using # valid_security_key_challenge_data. # # This is because the challenge is embedded @@ -64,8 +64,8 @@ module WebauthnIntegrationHelpers def simulate_localhost_webauthn_challenge stub_as_dev_localhost - Webauthn::ChallengeGenerator.stubs(:generate).returns( - Webauthn::ChallengeGenerator::ChallengeSession.new( + DiscourseWebauthn::ChallengeGenerator.stubs(:generate).returns( + DiscourseWebauthn::ChallengeGenerator::ChallengeSession.new( challenge: valid_security_key_challenge_data[:challenge], rp_id: Discourse.current_hostname, ),