|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require "spec_helper" |
| 4 | +require "webauthn/fake_client" |
| 5 | + |
| 6 | +RSpec.describe Devise::Webauthn::ChallengeStores::Signed do |
| 7 | + include ActiveSupport::Testing::TimeHelpers |
| 8 | + |
| 9 | + let(:client) { WebAuthn::FakeClient.new(WebAuthn.configuration.allowed_origins.first) } |
| 10 | + let(:issued_challenge) { store_for(nil).write(:passkey_authentication, WebAuthn::Credential.options_for_get.challenge) } |
| 11 | + |
| 12 | + def store_for(credential) |
| 13 | + described_class.new(instance_double(ActionDispatch::Request, params: { public_key_credential: credential })) |
| 14 | + end |
| 15 | + |
| 16 | + def store_signing(challenge) |
| 17 | + client.create(challenge: WebAuthn::Credential.options_for_get.challenge) |
| 18 | + store_for(client.get(challenge: challenge)) |
| 19 | + end |
| 20 | + |
| 21 | + it "accepts the challenge it issued when the credential signs it" do |
| 22 | + store = store_signing(issued_challenge) |
| 23 | + |
| 24 | + expect(store.pending?(:passkey_authentication)).to be(true) |
| 25 | + expect(store.consume(:passkey_authentication)).to eq(issued_challenge) |
| 26 | + end |
| 27 | + |
| 28 | + it "rejects a challenge it did not issue" do |
| 29 | + store = store_signing(WebAuthn::Credential.options_for_get.challenge) |
| 30 | + |
| 31 | + expect(store.consume(:passkey_authentication)).to be_nil |
| 32 | + end |
| 33 | + |
| 34 | + it "rejects a challenge issued for another purpose" do |
| 35 | + store = store_signing(issued_challenge) |
| 36 | + |
| 37 | + expect(store.consume(:two_factor_authentication)).to be_nil |
| 38 | + end |
| 39 | + |
| 40 | + it "expires challenges" do |
| 41 | + store = store_signing(issued_challenge) |
| 42 | + |
| 43 | + travel_to(described_class.expires_in.from_now + 1.second) do |
| 44 | + expect(store.consume(:passkey_authentication)).to be_nil |
| 45 | + end |
| 46 | + end |
| 47 | + |
| 48 | + it "accepts a challenge more than once until it expires, because it keeps no state" do |
| 49 | + store = store_signing(issued_challenge) |
| 50 | + |
| 51 | + expect(store.consume(:passkey_authentication)).to eq(issued_challenge) |
| 52 | + expect(store.consume(:passkey_authentication)).to eq(issued_challenge) |
| 53 | + end |
| 54 | + |
| 55 | + it "ignores malformed credentials" do |
| 56 | + [nil, "{}", {}, { "response" => "x" }, { "response" => { "clientDataJSON" => "%%%" } }].each do |malformed| |
| 57 | + expect(store_for(malformed).pending?(:passkey_authentication)).to be(false) |
| 58 | + end |
| 59 | + end |
| 60 | +end |
0 commit comments