diff options
author | Jordan Tucker <jordanbtucker@gmail.com> | 2023-08-28 14:28:52 -0500 |
---|---|---|
committer | Jordan Tucker <jordanbtucker@gmail.com> | 2023-08-28 14:28:52 -0500 |
commit | ab1a3e42dfd14301b278d86a677f73b0cb7cf37a (patch) | |
tree | 3fab7c8134e779e9677c4534e5958dc1dfaea570 /packages/server/src | |
parent | 0d9eda359379d8704eeda1995607bbd27de4ebe2 (diff) |
Allow expectedChallenge to return a Promise
Diffstat (limited to 'packages/server/src')
4 files changed, 145 insertions, 4 deletions
diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts index bf2a79a..822bdd9 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts @@ -394,6 +394,82 @@ Deno.test('should fail verification if custom challenge verifier returns false', ); }); +Deno.test('should pass verification if custom challenge verifier returns a Promise that resolves with true', async () => { + const verification = await verifyAuthenticationResponse({ + response: { + id: + 'AaIBxnYfL2pDWJmIii6CYgHBruhVvFGHheWamphVioG_TnEXxKA9MW4FWnJh21zsbmRpRJso9i2JmAtWOtXfVd4oXTgYVusXwhWWsA', + rawId: + 'AaIBxnYfL2pDWJmIii6CYgHBruhVvFGHheWamphVioG_TnEXxKA9MW4FWnJh21zsbmRpRJso9i2JmAtWOtXfVd4oXTgYVusXwhWWsA', + response: { + authenticatorData: 'SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFYftypQ', + clientDataJSON: + 'eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZXlKaFkzUjFZV3hEYUdGc2JHVnVaMlVpT2lKTE0xRjRUMnB1VmtwTWFVZHNibFpGY0RWMllUVlJTbVZOVmxkT1psODNVRmxuZFhSbllrRjBRVlZCSWl3aVlYSmlhWFJ5WVhKNVJHRjBZU0k2SW5OcFoyNU5aVkJzWldGelpTSjkiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9', + signature: + 'MEUCIByFAVGfkoKPEzynp-37BX_HOXSaC6-58-ELjB7BG9opAiEAyD_1mN9YAPrphcwpzK3ym2Xx8EjAapgQ326mKgQ1pW0', + userHandle: 'internalUserId', + }, + type: 'public-key', + clientExtensionResults: {}, + }, + expectedChallenge: (challenge: string) => { + const parsedChallenge: { + actualChallenge: string; + arbitraryData: string; + } = JSON.parse( + isoBase64URL.toString(challenge), + ); + return Promise.resolve( + parsedChallenge.actualChallenge === + 'K3QxOjnVJLiGlnVEp5va5QJeMVWNf_7PYgutgbAtAUA', + ); + }, + expectedOrigin: 'http://localhost:8000', + expectedRPID: 'localhost', + authenticator: { + credentialID: isoBase64URL.toBuffer( + 'AaIBxnYfL2pDWJmIii6CYgHBruhVvFGHheWamphVioG_TnEXxKA9MW4FWnJh21zsbmRpRJso9i2JmAtWOtXfVd4oXTgYVusXwhWWsA', + ), + credentialPublicKey: isoBase64URL.toBuffer( + 'pQECAyYgASFYILTrxTUQv3X4DRM6L_pk65FSMebenhCx3RMsTKoBm-AxIlggEf3qk5552QLNSh1T1oQs7_2C2qysDwN4r4fCp52Hsqs', + ), + counter: 0, + }, + }); + + assert(verification.verified); +}); + +Deno.test('should fail verification if custom challenge verifier returns a Promise that resolves with false', async () => { + await assertRejects( + () => + verifyAuthenticationResponse({ + response: assertionResponse, + expectedChallenge: (challenge) => Promise.resolve(challenge === 'willNeverMatch'), + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + }), + Error, + 'Custom challenge verifier returned false', + ); +}); + +Deno.test('should fail verification if custom challenge verifier returns a Promise that rejects', async () => { + await assertRejects( + () => + verifyAuthenticationResponse({ + response: assertionResponse, + expectedChallenge: () => Promise.reject(new Error('rejected')), + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + }), + Error, + 'rejected', + ); +}); + Deno.test('should return authenticator extension output', async () => { const verification = await verifyAuthenticationResponse({ response: { diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts index d3c2484..41370a0 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.ts @@ -15,7 +15,7 @@ import { isoBase64URL, isoUint8Array } from '../helpers/iso/index.ts'; export type VerifyAuthenticationResponseOpts = { response: AuthenticationResponseJSON; - expectedChallenge: string | ((challenge: string) => boolean); + expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>); expectedOrigin: string | string[]; expectedRPID: string | string[]; authenticator: AuthenticatorDevice; @@ -94,7 +94,7 @@ export async function verifyAuthenticationResponse( // Ensure the device provided the challenge we gave it if (typeof expectedChallenge === 'function') { - if (!expectedChallenge(challenge)) { + if (!(await expectedChallenge(challenge))) { throw new Error( `Custom challenge verifier returned false for registration response challenge "${challenge}"`, ); diff --git a/packages/server/src/registration/verifyRegistrationResponse.test.ts b/packages/server/src/registration/verifyRegistrationResponse.test.ts index 26a1d77..59dbd13 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.test.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.test.ts @@ -750,6 +750,71 @@ Deno.test('should fail verification if custom challenge verifier returns false', ); }); +Deno.test('should pass verification if custom challenge verifier returns a Promise that resolves with true', async () => { + const verification = await verifyRegistrationResponse({ + response: { + id: + 'AUywDsPYEOoucI3-o-jB1J6Kt6QAxLMa1WwFKj1bNi4pAakWAsZX-pJ4gAeDmocL7SXnl8vzUfLkfrOGIVmds1RhjU1DYIWlxcGhAA', + rawId: + 'AUywDsPYEOoucI3-o-jB1J6Kt6QAxLMa1WwFKj1bNi4pAakWAsZX-pJ4gAeDmocL7SXnl8vzUfLkfrOGIVmds1RhjU1DYIWlxcGhAA', + response: { + attestationObject: + 'o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIhAPgoy3sxIeUvN9Mo8twyIQb9hXDHxQ2urIaEq14u6vNHAiB8ltlCippsMIIsh6AqMoZlUH_BH0bXT1xsN2zKoCEy72hhdXRoRGF0YVjQSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFYfWYqK3OAAI1vMYKZIsLJfHwVQMATAFMsA7D2BDqLnCN_qPowdSeirekAMSzGtVsBSo9WzYuKQGpFgLGV_qSeIAHg5qHC-0l55fL81Hy5H6zhiFZnbNUYY1NQ2CFpcXBoQClAQIDJiABIVggPzMMB0nPKu9zvu6tvvyaP7MlGKJi4zazYQw5kyCjGykiWCCyHxcnMCwcj4llYwRY-MedgOCQzcz_TgKeabY4yFQyrA', + clientDataJSON: + 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiZXlKaFkzUjFZV3hEYUdGc2JHVnVaMlVpT2lKNFVuTlpaRU5SZGpWWFdrOXhiWGhTWldsYWJEWkRPWEUxVTJaeVdtNWxOR3hPVTNJNVVWWjBVR2xuSWl3aVlYSmlhWFJ5WVhKNVJHRjBZU0k2SW1GeVltbDBjbUZ5ZVVSaGRHRkdiM0pUYVdkdWFXNW5JbjAiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9', + transports: ['internal'], + }, + type: 'public-key', + clientExtensionResults: {}, + }, + expectedChallenge: (challenge: string) => { + const parsedChallenge: { + actualChallenge: string; + arbitraryData: string; + } = JSON.parse( + isoBase64URL.toString(challenge), + ); + return Promise.resolve( + parsedChallenge.actualChallenge === + 'xRsYdCQv5WZOqmxReiZl6C9q5SfrZne4lNSr9QVtPig', + ); + }, + expectedOrigin: 'http://localhost:8000', + expectedRPID: 'localhost', + }); + + assert(verification.verified); +}); + +Deno.test('should fail verification if custom challenge verifier returns a Promise that resolves with false', async () => { + await assertRejects( + () => + verifyRegistrationResponse({ + response: attestationNone, + expectedChallenge: (challenge: string) => + Promise.resolve(challenge === 'thisWillneverMatch'), + expectedOrigin: 'https://dev.dontneeda.pw', + expectedRPID: 'dev.dontneeda.pw', + }), + Error, + 'Custom challenge verifier returned false', + ); +}); + +Deno.test('should fail verification if custom challenge verifier returns a Promise that rejects', async () => { + await assertRejects( + () => + verifyRegistrationResponse({ + response: attestationNone, + expectedChallenge: (challenge: string) => Promise.reject(new Error('rejected')), + expectedOrigin: 'https://dev.dontneeda.pw', + expectedRPID: 'dev.dontneeda.pw', + }), + Error, + 'rejected', + ); +}); + Deno.test('should return credential backup info', async () => { const verification = await verifyRegistrationResponse({ response: attestationNone, diff --git a/packages/server/src/registration/verifyRegistrationResponse.ts b/packages/server/src/registration/verifyRegistrationResponse.ts index 081d31d..154d31a 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.ts @@ -30,7 +30,7 @@ import { verifyAttestationApple } from './verifications/verifyAttestationApple.t export type VerifyRegistrationResponseOpts = { response: RegistrationResponseJSON; - expectedChallenge: string | ((challenge: string) => boolean); + expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>); expectedOrigin: string | string[]; expectedRPID?: string | string[]; requireUserVerification?: boolean; @@ -95,7 +95,7 @@ export async function verifyRegistrationResponse( // Ensure the device provided the challenge we gave it if (typeof expectedChallenge === 'function') { - if (!expectedChallenge(challenge)) { + if (!(await expectedChallenge(challenge))) { throw new Error( `Custom challenge verifier returned false for registration response challenge "${challenge}"`, ); |