|
31 | 31 | 'credential' => json_decode(WebAuthn::toJson($source), true), |
32 | 32 | ]); |
33 | 33 |
|
34 | | - $assertionResponse = Mockery::mock(AuthenticatorAssertionResponse::class); |
35 | | - |
36 | | - $credential = PublicKeyCredential::create( |
| 34 | + $assertion = PublicKeyCredential::create( |
37 | 35 | type: 'public-key', |
38 | 36 | rawId: $credentialId, |
39 | | - response: $assertionResponse |
| 37 | + response: Mockery::mock(AuthenticatorAssertionResponse::class), |
40 | 38 | ); |
41 | 39 |
|
42 | 40 | $options = createRequestOptions(); |
|
51 | 49 | ->andReturn($updatedSource) |
52 | 50 | ->getMock(); |
53 | 51 |
|
54 | | - $result = $action($credential, $options); |
| 52 | + $result = $action($assertion, $options); |
55 | 53 |
|
56 | 54 | expect($result)->toBeInstanceOf(Passkey::class); |
57 | 55 | expect($result->id)->toBe($passkey->id); |
|
64 | 62 | }); |
65 | 63 |
|
66 | 64 | it('throws exception when response is not an assertion response', function (): void { |
67 | | - $attestationResponse = Mockery::mock(AuthenticatorAttestationResponse::class); |
68 | | - |
69 | | - $credential = PublicKeyCredential::create( |
| 65 | + $assertion = PublicKeyCredential::create( |
70 | 66 | type: 'public-key', |
71 | 67 | rawId: 'test-raw-id', |
72 | | - response: $attestationResponse |
| 68 | + response: Mockery::mock(AuthenticatorAttestationResponse::class), |
73 | 69 | ); |
74 | 70 |
|
75 | | - $options = createRequestOptions(); |
76 | | - |
77 | | - app(VerifyPasskey::class)($credential, $options); |
| 71 | + app(VerifyPasskey::class)($assertion, createRequestOptions()); |
78 | 72 | })->throws(InvalidPasskeyException::class, 'Unable to verify passkey'); |
79 | 73 |
|
80 | 74 | it('throws exception when passkey is not found', function (): void { |
81 | | - $assertionResponse = Mockery::mock(AuthenticatorAssertionResponse::class); |
82 | | - |
83 | | - $credential = PublicKeyCredential::create( |
| 75 | + $assertion = PublicKeyCredential::create( |
84 | 76 | type: 'public-key', |
85 | 77 | rawId: random_bytes(16), |
86 | | - response: $assertionResponse |
| 78 | + response: Mockery::mock(AuthenticatorAssertionResponse::class), |
87 | 79 | ); |
88 | 80 |
|
89 | | - $options = createRequestOptions(); |
90 | | - |
91 | | - app(VerifyPasskey::class)($credential, $options); |
| 81 | + app(VerifyPasskey::class)($assertion, createRequestOptions()); |
92 | 82 | })->throws(InvalidPasskeyException::class, 'Passkey not recognized'); |
93 | 83 |
|
94 | 84 | it('throws exception when passkey does not belong to expected user', function (): void { |
|
112 | 102 | 'credential' => json_decode(WebAuthn::toJson($source), true), |
113 | 103 | ]); |
114 | 104 |
|
115 | | - $assertionResponse = Mockery::mock(AuthenticatorAssertionResponse::class); |
116 | | - |
117 | | - $credential = PublicKeyCredential::create( |
| 105 | + $assertion = PublicKeyCredential::create( |
118 | 106 | type: 'public-key', |
119 | 107 | rawId: $credentialId, |
120 | | - response: $assertionResponse |
| 108 | + response: Mockery::mock(AuthenticatorAssertionResponse::class), |
121 | 109 | ); |
122 | 110 |
|
123 | | - $options = createRequestOptions(); |
124 | | - |
125 | 111 | $action = Mockery::mock(VerifyPasskey::class) |
126 | 112 | ->makePartial() |
127 | 113 | ->shouldAllowMockingProtectedMethods() |
128 | 114 | ->shouldReceive('validate') |
129 | 115 | ->never() |
130 | 116 | ->getMock(); |
131 | 117 |
|
132 | | - $action($credential, $options, $otherUser); |
| 118 | + $action($assertion, createRequestOptions(), $otherUser); |
133 | 119 | })->throws(InvalidPasskeyException::class, 'Passkey not recognized'); |
| 120 | + |
| 121 | +it('verifies an existing passkey after user handle secret rotation', function (): void { |
| 122 | + config()->set('passkeys.allowed_origins', ['https://localhost']); |
| 123 | + config()->set('passkeys.relying_party_id', 'localhost'); |
| 124 | + config()->set('passkeys.user_handle_secret', 'initial-user-handle-secret'); |
| 125 | + |
| 126 | + $user = User::create(['name' => 'John Doe', 'email' => 'john@example.com']); |
| 127 | + $credentialId = random_bytes(16); |
| 128 | + $initialUserHandle = $user->getPasskeyUserHandle(); |
| 129 | + |
| 130 | + $passkey = $user->passkeys()->create([ |
| 131 | + 'name' => 'My MacBook', |
| 132 | + 'credential_id' => Base64UrlSafe::encodeUnpadded($credentialId), |
| 133 | + 'credential' => json_decode(WebAuthn::toJson(createCredentialSource($initialUserHandle, $credentialId)), true), |
| 134 | + ]); |
| 135 | + |
| 136 | + config()->set('passkeys.user_handle_secret', 'rotated-user-handle-secret'); |
| 137 | + |
| 138 | + $options = createRequestOptions(); |
| 139 | + $assertion = PublicKeyCredential::create( |
| 140 | + type: 'public-key', |
| 141 | + rawId: $credentialId, |
| 142 | + response: createSignedAssertionResponse($options->challenge, 'https://localhost', signCount: 6, rpId: 'localhost'), |
| 143 | + ); |
| 144 | + |
| 145 | + $result = app(VerifyPasskey::class)($assertion, $options, $user); |
| 146 | + |
| 147 | + expect($result->id)->toBe($passkey->id); |
| 148 | + expect(Base64UrlSafe::decodeNoPadding($result->refresh()->credential['userHandle']))->toBe($initialUserHandle); |
| 149 | +}); |
0 commit comments