WIP: Rename Webauthn to DiscourseWebauthn (#23077)
This commit is contained in:
parent
16c6ab8661
commit
10c6b2a0c2
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -1,5 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
module Webauthn
|
||||
module DiscourseWebauthn
|
||||
class ChallengeGenerator
|
||||
class ChallengeSession
|
||||
attr_reader :challenge, :rp_id, :rp_name
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue