diff --git a/app/services/webauthn/authenticate_service.rb b/app/services/webauthn/authenticate_service.rb index a575a853995d046bcd54dc9ac5d46e5ee613a96e..52437a77df8bc2dc2a7295724e31d14ab5704718 100644 --- a/app/services/webauthn/authenticate_service.rb +++ b/app/services/webauthn/authenticate_service.rb @@ -30,6 +30,8 @@ def execute false end + private + ## # Validates that webauthn_credential is syntactically valid # diff --git a/spec/models/u2f_registration_spec.rb b/spec/models/u2f_registration_spec.rb index 027d26d965732e0d3a953c17b1c067a70339bd90..1fab3882c2ade338dc6f020bf4d14ece8221444b 100644 --- a/spec/models/u2f_registration_spec.rb +++ b/spec/models/u2f_registration_spec.rb @@ -6,21 +6,67 @@ let_it_be(:user) { create(:user) } let(:u2f_registration_name) { 'u2f_device' } - let(:u2f_registration) do - device = U2F::FakeU2F.new(FFaker::BaconIpsum.characters(5)) - create( - :u2f_registration, name: u2f_registration_name, - user: user, - certificate: Base64.strict_encode64(device.cert_raw), - key_handle: U2F.urlsafe_encode64(device.key_handle_raw), - public_key: Base64.strict_encode64(device.origin_public_key_raw) - ) + let(:app_id) { FFaker::BaconIpsum.characters(5) } + let(:device) { U2F::FakeU2F.new(app_id) } + + describe '.authenticate' do + context 'when registration is found' do + it 'returns true' do + create_u2f_registration + device_challenge = U2F.urlsafe_encode64(SecureRandom.random_bytes(32)) + sign_response_json = device.sign_response(device_challenge) + + response = U2fRegistration.authenticate( + user, + app_id, + sign_response_json, + device_challenge + ) + + expect(response).to eq true + end + end + + context 'when registration not found' do + it 'returns nil' do + device_challenge = U2F.urlsafe_encode64(SecureRandom.random_bytes(32)) + sign_response_json = device.sign_response(device_challenge) + + # data is valid but user does not have any u2f_registrations + response = U2fRegistration.authenticate( + user, + app_id, + sign_response_json, + device_challenge + ) + + expect(response).to eq nil + end + end + + context 'when args passed in are invalid' do + it 'returns false' do + some_app_id = 123 + invalid_json = 'invalid JSON' + challenges = 'whatever' + + response = U2fRegistration.authenticate( + user, + some_app_id, + invalid_json, + challenges + ) + + expect(response).to eq false + end + end end describe 'callbacks' do describe 'after create' do shared_examples_for 'creates webauthn registration' do it 'creates webauthn registration' do + u2f_registration = create_u2f_registration webauthn_registration = WebauthnRegistration.where(u2f_registration_id: u2f_registration.id) expect(webauthn_registration).to exist end @@ -51,13 +97,14 @@ receive(:track_exception).with(kind_of(StandardError), u2f_registration_id: 123)) - u2f_registration + create_u2f_registration end end describe 'after update' do context 'when counter is updated' do it 'updates the webauthn registration counter to be the same value' do + u2f_registration = create_u2f_registration new_counter = u2f_registration.counter + 1 webauthn_registration = WebauthnRegistration.find_by(u2f_registration_id: u2f_registration.id) @@ -70,6 +117,7 @@ context 'when sign count of registration is not updated' do it 'does not update the counter' do + u2f_registration = create_u2f_registration webauthn_registration = WebauthnRegistration.find_by(u2f_registration_id: u2f_registration.id) expect do @@ -79,4 +127,15 @@ end end end + + def create_u2f_registration + create( + :u2f_registration, + name: u2f_registration_name, + user: user, + certificate: Base64.strict_encode64(device.cert_raw), + key_handle: U2F.urlsafe_encode64(device.key_handle_raw), + public_key: Base64.strict_encode64(device.origin_public_key_raw) + ) + end end diff --git a/spec/services/webauthn/authenticate_service_spec.rb b/spec/services/webauthn/authenticate_service_spec.rb index 61f64f24f5e78e165e39855b8bd79665b2e1670c..b40f9465b635e967e8a11c4191483736eae08144 100644 --- a/spec/services/webauthn/authenticate_service_spec.rb +++ b/spec/services/webauthn/authenticate_service_spec.rb @@ -30,19 +30,28 @@ get_result['clientExtensionResults'] = {} service = Webauthn::AuthenticateService.new(user, get_result.to_json, challenge) - expect(service.execute).to be_truthy + expect(service.execute).to eq true end - it 'returns false if the response is valid but no matching stored credential is present' do - other_client = WebAuthn::FakeClient.new(origin) - other_client.create(challenge: challenge) # rubocop:disable Rails/SaveBang + context 'when response is valid but no matching stored credential is present' do + it 'returns false' do + other_client = WebAuthn::FakeClient.new(origin) + other_client.create(challenge: challenge) # rubocop:disable Rails/SaveBang - get_result = other_client.get(challenge: challenge) + get_result = other_client.get(challenge: challenge) - get_result['clientExtensionResults'] = {} - service = Webauthn::AuthenticateService.new(user, get_result.to_json, challenge) + get_result['clientExtensionResults'] = {} + service = Webauthn::AuthenticateService.new(user, get_result.to_json, challenge) + + expect(service.execute).to eq false + end + end - expect(service.execute).to be_falsey + context 'when device response includes invalid json' do + it 'returns false' do + service = Webauthn::AuthenticateService.new(user, 'invalid JSON', '') + expect(service.execute).to eq false + end end end end