From 905e60295c86cb15e33011b0b7edf4891ae25173 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Wed, 20 May 2020 15:41:31 -0700 Subject: Create browser package --- packages/browser/src/index.ts | 0 packages/browser/src/setupTests.ts | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 packages/browser/src/index.ts create mode 100644 packages/browser/src/setupTests.ts (limited to 'packages/browser/src') diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/browser/src/setupTests.ts b/packages/browser/src/setupTests.ts new file mode 100644 index 0000000..4cf23af --- /dev/null +++ b/packages/browser/src/setupTests.ts @@ -0,0 +1,3 @@ +// Silence some console output +jest.spyOn(console, 'log').mockImplementation(); +jest.spyOn(console, 'debug').mockImplementation(); -- cgit v1.2.3 From acfc8822607a95e58fff085d3905ecb29346b270 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Wed, 20 May 2020 23:37:52 -0700 Subject: Add some browser helpers --- packages/browser/src/helpers/supportsWebauthn.ts | 9 +++++++++ packages/browser/src/helpers/toBase64String.ts | 9 +++++++++ packages/browser/src/helpers/toUint8Array.ts | 7 +++++++ 3 files changed, 25 insertions(+) create mode 100644 packages/browser/src/helpers/supportsWebauthn.ts create mode 100644 packages/browser/src/helpers/toBase64String.ts create mode 100644 packages/browser/src/helpers/toUint8Array.ts (limited to 'packages/browser/src') diff --git a/packages/browser/src/helpers/supportsWebauthn.ts b/packages/browser/src/helpers/supportsWebauthn.ts new file mode 100644 index 0000000..f566942 --- /dev/null +++ b/packages/browser/src/helpers/supportsWebauthn.ts @@ -0,0 +1,9 @@ +/** + * Determine if the browser is capable of Webauthn + */ +export default function supportsWebauthn(): boolean { + return ( + window.PublicKeyCredential !== undefined + && typeof window.PublicKeyCredential === 'function' + ); +} diff --git a/packages/browser/src/helpers/toBase64String.ts b/packages/browser/src/helpers/toBase64String.ts new file mode 100644 index 0000000..8234002 --- /dev/null +++ b/packages/browser/src/helpers/toBase64String.ts @@ -0,0 +1,9 @@ +import base64js from 'base64-js'; + +export default function toBase64String(buffer: ArrayBuffer): string { + // TODO: Make sure converting buffer to Uint8Array() is correct + return base64js.fromByteArray(new Uint8Array(buffer)) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=/g, ""); +} diff --git a/packages/browser/src/helpers/toUint8Array.ts b/packages/browser/src/helpers/toUint8Array.ts new file mode 100644 index 0000000..a807a88 --- /dev/null +++ b/packages/browser/src/helpers/toUint8Array.ts @@ -0,0 +1,7 @@ +/** + * A helper method to convert a string sent from the server to a Uint8Array the authenticator will + * expect. + */ +export default function toUint8Array(value: string): Uint8Array { + return Uint8Array.from(value, c => c.charCodeAt(0)); +} -- cgit v1.2.3 From 4888c8bea2762d4b45ab8e9c286453368ec57c66 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Wed, 20 May 2020 23:38:07 -0700 Subject: Add startAttestation method to browser --- packages/browser/src/methods/startAttestation.ts | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 packages/browser/src/methods/startAttestation.ts (limited to 'packages/browser/src') diff --git a/packages/browser/src/methods/startAttestation.ts b/packages/browser/src/methods/startAttestation.ts new file mode 100644 index 0000000..3b955cc --- /dev/null +++ b/packages/browser/src/methods/startAttestation.ts @@ -0,0 +1,45 @@ +import { + PublicKeyCredentialCreationOptionsJSON, + EncodedAuthenticatorAttestationResponse, + AttestationCredential, +} from '@webauthntine/typescript-types'; + +import toUint8Array from '../helpers/toUint8Array'; +import toBase64String from '../helpers/toBase64String'; +import supportsWebauthn from '../helpers/supportsWebauthn'; + +/** + * + */ +export default async function startAttestation( + creationOptionsJSON: PublicKeyCredentialCreationOptionsJSON +): Promise { + if (!supportsWebauthn()) { + throw new Error('Webauthn is not supported in this browser'); + } + + // We need to convert some values to Uint8Arrays before passing the credentials to the navigator + const publicKey: PublicKeyCredentialCreationOptions = { + ...creationOptionsJSON.publicKey, + challenge: toUint8Array(creationOptionsJSON.publicKey.challenge), + user: { + ...creationOptionsJSON.publicKey.user, + id: toUint8Array(creationOptionsJSON.publicKey.user.id), + }, + }; + + // Wait for the user to complete attestation + const credential = await navigator.credentials.create({ publicKey }); + + if (!credential) { + throw new Error('Attestation was not completed'); + } + + const { response } = (credential as AttestationCredential); + + // Convert values to base64 to make it easier to send back to the server + return { + base64AttestationObject: toBase64String(response.attestationObject), + base64ClientDataJSON: toBase64String(response.clientDataJSON), + }; +} -- cgit v1.2.3 From aaa60c122a0595f301f7cf89edd1f757aa9f1b75 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Wed, 20 May 2020 23:38:23 -0700 Subject: Export supportsWebauthn from browser --- packages/browser/src/index.ts | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'packages/browser/src') diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index e69de29..bde3335 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -0,0 +1,5 @@ +import supportsWebauthn from './helpers/supportsWebauthn'; + +export { + supportsWebauthn, +}; -- cgit v1.2.3 From dddf3c30d104de7c5fa9da055dd52ed00d8ddaf9 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Wed, 20 May 2020 23:53:15 -0700 Subject: Refine type naming --- packages/browser/src/methods/startAttestation.ts | 4 ++-- packages/server/src/assertion/verifyAssertionResponse.ts | 4 ++-- packages/server/src/attestation/verifyAttestationResponse.ts | 4 ++-- packages/typescript-types/src/index.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'packages/browser/src') diff --git a/packages/browser/src/methods/startAttestation.ts b/packages/browser/src/methods/startAttestation.ts index 3b955cc..af28e13 100644 --- a/packages/browser/src/methods/startAttestation.ts +++ b/packages/browser/src/methods/startAttestation.ts @@ -1,6 +1,6 @@ import { PublicKeyCredentialCreationOptionsJSON, - EncodedAuthenticatorAttestationResponse, + AuthenticatorAttestationResponseJSON, AttestationCredential, } from '@webauthntine/typescript-types'; @@ -13,7 +13,7 @@ import supportsWebauthn from '../helpers/supportsWebauthn'; */ export default async function startAttestation( creationOptionsJSON: PublicKeyCredentialCreationOptionsJSON -): Promise { +): Promise { if (!supportsWebauthn()) { throw new Error('Webauthn is not supported in this browser'); } diff --git a/packages/server/src/assertion/verifyAssertionResponse.ts b/packages/server/src/assertion/verifyAssertionResponse.ts index d906e76..2a5dd8f 100644 --- a/packages/server/src/assertion/verifyAssertionResponse.ts +++ b/packages/server/src/assertion/verifyAssertionResponse.ts @@ -1,6 +1,6 @@ import base64url from 'base64url'; import { - EncodedAuthenticatorAssertionResponse, + AuthenticatorAssertionResponseJSON, U2F_USER_PRESENTED, AuthenticatorDevice, VerifiedAssertion, @@ -20,7 +20,7 @@ import verifySignature from '@helpers/verifySignature'; * @param expectedOrigin Expected URL of website attestation should have occurred on */ export default function verifyAssertionResponse( - response: EncodedAuthenticatorAssertionResponse, + response: AuthenticatorAssertionResponseJSON, expectedOrigin: string, authenticator: AuthenticatorDevice, ): VerifiedAssertion { diff --git a/packages/server/src/attestation/verifyAttestationResponse.ts b/packages/server/src/attestation/verifyAttestationResponse.ts index ad226eb..9b90391 100644 --- a/packages/server/src/attestation/verifyAttestationResponse.ts +++ b/packages/server/src/attestation/verifyAttestationResponse.ts @@ -1,6 +1,6 @@ import decodeAttestationObject from '@helpers/decodeAttestationObject'; import decodeClientDataJSON from '@helpers/decodeClientDataJSON'; -import { ATTESTATION_FORMATS, EncodedAuthenticatorAttestationResponse, VerifiedAttestation } from '@webauthntine/typescript-types'; +import { ATTESTATION_FORMATS, AuthenticatorAttestationResponseJSON, VerifiedAttestation } from '@webauthntine/typescript-types'; import verifyFIDOU2F from './verifications/verifyFIDOU2F'; import verifyPacked from './verifications/verifyPacked'; @@ -14,7 +14,7 @@ import verifyAndroidSafetynet from './verifications/verifyAndroidSafetyNet'; * @param expectedOrigin Expected URL of website attestation should have occurred on */ export default function verifyAttestationResponse( - response: EncodedAuthenticatorAttestationResponse, + response: AuthenticatorAttestationResponseJSON, expectedOrigin: string, ): VerifiedAttestation { const { base64AttestationObject, base64ClientDataJSON } = response; diff --git a/packages/typescript-types/src/index.ts b/packages/typescript-types/src/index.ts index 8ab5763..8cb15de 100644 --- a/packages/typescript-types/src/index.ts +++ b/packages/typescript-types/src/index.ts @@ -73,7 +73,7 @@ export interface AssertionCredential extends PublicKeyCredential { * A slightly-modified AuthenticatorAttestationResponse to simplify working with ArrayBuffers that * are base64-encoded in the browser so that they can be sent as JSON to the server. */ -export interface EncodedAuthenticatorAttestationResponse extends Omit< +export interface AuthenticatorAttestationResponseJSON extends Omit< AuthenticatorAttestationResponse, 'clientDataJSON' | 'attestationObject' > { base64ClientDataJSON: string, @@ -84,7 +84,7 @@ AuthenticatorAttestationResponse, 'clientDataJSON' | 'attestationObject' * A slightly-modified AuthenticatorAttestationResponse to simplify working with ArrayBuffers that * are base64-encoded in the browser so that they can be sent as JSON to the server. */ -export interface EncodedAuthenticatorAssertionResponse extends Omit< +export interface AuthenticatorAssertionResponseJSON extends Omit< AuthenticatorAssertionResponse, 'clientDataJSON' | 'authenticatorData' | 'signature' > { base64AuthenticatorData: string; -- cgit v1.2.3 From e1a5fc6bd77be5cc6a67c51520209cf8639bca40 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Wed, 20 May 2020 23:55:24 -0700 Subject: Add more docs to startAttestation in browser --- packages/browser/src/methods/startAttestation.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'packages/browser/src') diff --git a/packages/browser/src/methods/startAttestation.ts b/packages/browser/src/methods/startAttestation.ts index af28e13..4d5995a 100644 --- a/packages/browser/src/methods/startAttestation.ts +++ b/packages/browser/src/methods/startAttestation.ts @@ -9,7 +9,9 @@ import toBase64String from '../helpers/toBase64String'; import supportsWebauthn from '../helpers/supportsWebauthn'; /** + * Begin authenticator registration via WebAuthn "attestation" * + * @param creationOptionsJSON Output from @webauthntine/server's generateAttestationOptions(...) */ export default async function startAttestation( creationOptionsJSON: PublicKeyCredentialCreationOptionsJSON -- cgit v1.2.3 From 39385d48866e976ab37ab802c2500b7a7471ace2 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Wed, 20 May 2020 23:59:07 -0700 Subject: Try to settle on a metaphor --- packages/browser/src/methods/startAttestation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/browser/src') diff --git a/packages/browser/src/methods/startAttestation.ts b/packages/browser/src/methods/startAttestation.ts index 4d5995a..800b8e6 100644 --- a/packages/browser/src/methods/startAttestation.ts +++ b/packages/browser/src/methods/startAttestation.ts @@ -9,7 +9,7 @@ import toBase64String from '../helpers/toBase64String'; import supportsWebauthn from '../helpers/supportsWebauthn'; /** - * Begin authenticator registration via WebAuthn "attestation" + * Begin authenticator "registration" via WebAuthn attestation * * @param creationOptionsJSON Output from @webauthntine/server's generateAttestationOptions(...) */ -- cgit v1.2.3 From 5a4f92d69c3b4cbfc745dce601b10b7d720d0961 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Wed, 20 May 2020 23:59:38 -0700 Subject: Add startAttestation to browser exports --- packages/browser/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'packages/browser/src') diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index bde3335..bcf2e2f 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -1,5 +1,7 @@ +import startAttestation from './methods/startAttestation'; import supportsWebauthn from './helpers/supportsWebauthn'; export { + startAttestation, supportsWebauthn, }; -- cgit v1.2.3 From 2b9998d38a81e70b94ee7fb0cfada0a6478d2808 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 21 May 2020 08:40:59 -0700 Subject: Add startAssertion to browser --- packages/browser/src/index.ts | 2 + packages/browser/src/methods/startAssertion.ts | 54 ++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 packages/browser/src/methods/startAssertion.ts (limited to 'packages/browser/src') diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index bcf2e2f..38ce91b 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -1,7 +1,9 @@ import startAttestation from './methods/startAttestation'; +import startAssertion from './methods/startAssertion'; import supportsWebauthn from './helpers/supportsWebauthn'; export { startAttestation, + startAssertion, supportsWebauthn, }; diff --git a/packages/browser/src/methods/startAssertion.ts b/packages/browser/src/methods/startAssertion.ts new file mode 100644 index 0000000..c42e614 --- /dev/null +++ b/packages/browser/src/methods/startAssertion.ts @@ -0,0 +1,54 @@ +import { + PublicKeyCredentialRequestOptionsJSON, + AuthenticatorAssertionResponseJSON, + AssertionCredential, +} from '@webauthntine/typescript-types'; + +import toUint8Array from '../helpers/toUint8Array'; +import toBase64String from '../helpers/toBase64String'; +import supportsWebauthn from '../helpers/supportsWebauthn'; + +/** + * Begin authenticator "login" via WebAuthn assertion + * + * @param requestOptionsJSON Output from @webauthntine/server's generateAssertionOptions(...) + */ +export default async function startAssertion( + requestOptionsJSON: PublicKeyCredentialRequestOptionsJSON +): Promise { + if (!supportsWebauthn()) { + throw new Error('Webauthn is not supported in this browser'); + } + + // We need to convert some values to Uint8Arrays before passing the credentials to the navigator + const publicKey: PublicKeyCredentialRequestOptions = { + ...requestOptionsJSON.publicKey, + challenge: toUint8Array(requestOptionsJSON.publicKey.challenge), + allowCredentials: requestOptionsJSON.publicKey.allowCredentials.map((cred) => ({ + ...cred, + id: toUint8Array(cred.id), + })) + }; + + // Wait for the user to complete assertion + const credential = await navigator.credentials.get({ publicKey }); + + if (!credential) { + throw new Error('Assertion was not completed'); + } + + const { response } = (credential as AssertionCredential); + + let base64UserHandle = undefined; + if (response.userHandle) { + base64UserHandle = toBase64String(response.userHandle); + } + + // Convert values to base64 to make it easier to send back to the server + return { + base64AuthenticatorData: toBase64String(response.authenticatorData), + base64ClientDataJSON: toBase64String(response.clientDataJSON), + base64Signature: toBase64String(response.signature), + base64UserHandle, + }; +} -- cgit v1.2.3 From 690e6112b233f640ff25ff10f254ba083dec6236 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 21 May 2020 11:17:31 -0700 Subject: Capitalize “WebAuthn” properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/browser/src/methods/startAssertion.ts | 2 +- packages/browser/src/methods/startAttestation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/browser/src') diff --git a/packages/browser/src/methods/startAssertion.ts b/packages/browser/src/methods/startAssertion.ts index c42e614..603c6fb 100644 --- a/packages/browser/src/methods/startAssertion.ts +++ b/packages/browser/src/methods/startAssertion.ts @@ -17,7 +17,7 @@ export default async function startAssertion( requestOptionsJSON: PublicKeyCredentialRequestOptionsJSON ): Promise { if (!supportsWebauthn()) { - throw new Error('Webauthn is not supported in this browser'); + throw new Error('WebAuthn is not supported in this browser'); } // We need to convert some values to Uint8Arrays before passing the credentials to the navigator diff --git a/packages/browser/src/methods/startAttestation.ts b/packages/browser/src/methods/startAttestation.ts index 800b8e6..1a4b13d 100644 --- a/packages/browser/src/methods/startAttestation.ts +++ b/packages/browser/src/methods/startAttestation.ts @@ -17,7 +17,7 @@ export default async function startAttestation( creationOptionsJSON: PublicKeyCredentialCreationOptionsJSON ): Promise { if (!supportsWebauthn()) { - throw new Error('Webauthn is not supported in this browser'); + throw new Error('WebAuthn is not supported in this browser'); } // We need to convert some values to Uint8Arrays before passing the credentials to the navigator -- cgit v1.2.3 From 0ef186ddb89a4c03a95db864260f51c5ccfadb39 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 21 May 2020 11:53:46 -0700 Subject: Add tests for startAttestation in browser --- .../src/helpers/__mocks__/supportsWebauthn.ts | 2 + .../browser/src/methods/startAttestation.test.ts | 112 +++++++++++++++++++++ packages/browser/src/setupTests.ts | 12 +++ 3 files changed, 126 insertions(+) create mode 100644 packages/browser/src/helpers/__mocks__/supportsWebauthn.ts create mode 100644 packages/browser/src/methods/startAttestation.test.ts (limited to 'packages/browser/src') diff --git a/packages/browser/src/helpers/__mocks__/supportsWebauthn.ts b/packages/browser/src/helpers/__mocks__/supportsWebauthn.ts new file mode 100644 index 0000000..b6b47d4 --- /dev/null +++ b/packages/browser/src/helpers/__mocks__/supportsWebauthn.ts @@ -0,0 +1,2 @@ +// We just need a simple mock so we can control whether this returns `true` or `false` +export default jest.fn(); diff --git a/packages/browser/src/methods/startAttestation.test.ts b/packages/browser/src/methods/startAttestation.test.ts new file mode 100644 index 0000000..0efec48 --- /dev/null +++ b/packages/browser/src/methods/startAttestation.test.ts @@ -0,0 +1,112 @@ +import base64js from 'base64-js'; + +import { AttestationCredential, PublicKeyCredentialCreationOptionsJSON } from '@webauthntine/typescript-types'; + +import toUint8Array from '../helpers/toUint8Array'; +import supportsWebauthn from '../helpers/supportsWebauthn'; + +import startAttestation from './startAttestation'; + +jest.mock('../helpers/supportsWebauthn'); + +const mockNavigatorCreate = (window.navigator.credentials.create as jest.Mock); +const mockSupportsWebauthn = (supportsWebauthn as jest.Mock); + +const mockAttestationObject = 'mockAtte'; +const mockClientDataJSON = 'mockClie'; + +const goodOpts1: PublicKeyCredentialCreationOptionsJSON = { + publicKey: { + challenge: 'fizz', + attestation: 'direct', + pubKeyCredParams: [{ + alg: -7, + type: "public-key", + }], + rp: { + id: '1234', + name: 'webauthntine', + }, + user: { + id: '5678', + displayName: 'username', + name: 'username', + }, + timeout: 1, + }, +}; + +beforeEach(() => { + mockNavigatorCreate.mockReset(); + mockSupportsWebauthn.mockReset(); +}); + +test('should convert options before passing to navigator.credentials.create(...)', async (done) => { + mockSupportsWebauthn.mockReturnValue(true); + + // Stub out a response so the method won't throw + mockNavigatorCreate.mockImplementation((): Promise => { + return new Promise((resolve) => { + resolve({ response: {} }); + }); + }); + + await startAttestation(goodOpts1); + + const argsPublicKey = mockNavigatorCreate.mock.calls[0][0].publicKey; + + expect(argsPublicKey.challenge).toEqual(toUint8Array(goodOpts1.publicKey.challenge)); + expect(argsPublicKey.user.id).toEqual(toUint8Array(goodOpts1.publicKey.user.id)); + + done(); +}); + +test('should return base64-encoded response values', async (done) => { + mockSupportsWebauthn.mockReturnValue(true); + + mockNavigatorCreate.mockImplementation((): Promise => { + return new Promise((resolve) => { + resolve({ + id: 'foobar', + rawId: toUint8Array('foobar'), + response: { + attestationObject: base64js.toByteArray(mockAttestationObject), + clientDataJSON: base64js.toByteArray(mockClientDataJSON), + }, + getClientExtensionResults: () => ({}), + type: 'webauthn.create', + }); + }); + }); + + const response = await startAttestation(goodOpts1); + + expect(response).toEqual({ + base64AttestationObject: mockAttestationObject, + base64ClientDataJSON: mockClientDataJSON, + }); + + done(); +}) + +test('should throw error if WebAuthn isn\'t supported', async (done) => { + mockSupportsWebauthn.mockReturnValue(false); + + await expect(startAttestation(goodOpts1)).rejects.toThrow('WebAuthn is not supported in this browser'); + + done(); +}); + +test('should throw error if attestation is cancelled for some reason', async (done) => { + mockSupportsWebauthn.mockReturnValue(true); + + mockNavigatorCreate.mockImplementation((): Promise => { + return new Promise((resolve) => { + resolve(null); + }); + }); + + await expect(startAttestation(goodOpts1)).rejects.toThrow('Attestation was not completed'); + + done(); +}); diff --git a/packages/browser/src/setupTests.ts b/packages/browser/src/setupTests.ts index 4cf23af..dcb981e 100644 --- a/packages/browser/src/setupTests.ts +++ b/packages/browser/src/setupTests.ts @@ -1,3 +1,15 @@ // Silence some console output jest.spyOn(console, 'log').mockImplementation(); jest.spyOn(console, 'debug').mockImplementation(); + +/** + * JSDom doesn't seem to support `credentials`, so let's define them here so we can mock their + * implementations in specific tests. + */ +// @ts-ignore 2540 +window.navigator.credentials = { + // attestation + create: jest.fn(), + // assertion + get: jest.fn(), +}; -- cgit v1.2.3 From 019190e91cff53d78f48c6c70e2ea8da23846a94 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 21 May 2020 11:55:06 -0700 Subject: Add remaining unit tests for browser --- .../browser/src/helpers/supportsWebauthn.test.ts | 15 +++ packages/browser/src/index.test.ts | 13 +++ .../browser/src/methods/startAssertion.test.ts | 111 +++++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 packages/browser/src/helpers/supportsWebauthn.test.ts create mode 100644 packages/browser/src/index.test.ts create mode 100644 packages/browser/src/methods/startAssertion.test.ts (limited to 'packages/browser/src') diff --git a/packages/browser/src/helpers/supportsWebauthn.test.ts b/packages/browser/src/helpers/supportsWebauthn.test.ts new file mode 100644 index 0000000..078c27d --- /dev/null +++ b/packages/browser/src/helpers/supportsWebauthn.test.ts @@ -0,0 +1,15 @@ +import supportsWebauthn from './supportsWebauthn'; + +beforeEach(() => { + // @ts-ignore 2741 + window.PublicKeyCredential = jest.fn().mockReturnValue(() => {}); +}); + +test('should return true when browser supports WebAuthn', () => { + expect(supportsWebauthn()).toBe(true); +}); + +test('should return false when browser does not support WebAuthn', () => { + delete window.PublicKeyCredential; + expect(supportsWebauthn()).toBe(false); +}); diff --git a/packages/browser/src/index.test.ts b/packages/browser/src/index.test.ts new file mode 100644 index 0000000..0d132ba --- /dev/null +++ b/packages/browser/src/index.test.ts @@ -0,0 +1,13 @@ +import * as index from './index'; + +test('should export method `startAttestation`', () => { + expect(index.startAttestation).toBeDefined(); +}); + +test('should export method `startAssertion`', () => { + expect(index.startAssertion).toBeDefined(); +}); + +test('should export method `supportsWebauthn`', () => { + expect(index.supportsWebauthn).toBeDefined(); +}); diff --git a/packages/browser/src/methods/startAssertion.test.ts b/packages/browser/src/methods/startAssertion.test.ts new file mode 100644 index 0000000..b069f60 --- /dev/null +++ b/packages/browser/src/methods/startAssertion.test.ts @@ -0,0 +1,111 @@ +import base64js from 'base64-js'; + +import { AssertionCredential, PublicKeyCredentialRequestOptionsJSON } from '@webauthntine/typescript-types'; + +import toUint8Array from '../helpers/toUint8Array'; +import supportsWebauthn from '../helpers/supportsWebauthn'; + +import startAssertion from './startAssertion'; + +jest.mock('../helpers/supportsWebauthn'); + +const mockNavigatorGet = (window.navigator.credentials.get as jest.Mock); +const mockSupportsWebauthn = (supportsWebauthn as jest.Mock); + +const mockAttestationObject = 'mockAsse'; +const mockClientDataJSON = 'mockClie'; +const mockSignature = 'mockSign'; +const mockUserHandle = 'mockUser'; + +const goodOpts1: PublicKeyCredentialRequestOptionsJSON = { + publicKey: { + challenge: 'fizz', + allowCredentials: [{ + id: 'credId', + type: 'public-key', + transports: ['nfc'], + }], + timeout: 1, + }, +}; + +beforeEach(() => { + mockNavigatorGet.mockReset(); + mockSupportsWebauthn.mockReset(); +}); + +test('should convert options before passing to navigator.credentials.get(...)', async (done) => { + mockSupportsWebauthn.mockReturnValue(true); + + // Stub out a response so the method won't throw + mockNavigatorGet.mockImplementation((): Promise => { + return new Promise((resolve) => { + resolve({ response: {} }); + }); + }); + + await startAssertion(goodOpts1); + + const argsPublicKey = mockNavigatorGet.mock.calls[0][0].publicKey; + + expect(argsPublicKey.challenge).toEqual(toUint8Array(goodOpts1.publicKey.challenge)); + expect(argsPublicKey.allowCredentials[0].id).toEqual( + toUint8Array(goodOpts1.publicKey.allowCredentials[0].id), + ); + + done(); +}); + +test('should return base64-encoded response values', async (done) => { + mockSupportsWebauthn.mockReturnValue(true); + + mockNavigatorGet.mockImplementation((): Promise => { + return new Promise((resolve) => { + resolve({ + id: 'foobar', + rawId: toUint8Array('foobar'), + response: { + clientDataJSON: base64js.toByteArray(mockClientDataJSON), + authenticatorData: base64js.toByteArray(mockClientDataJSON), + signature: base64js.toByteArray(mockSignature), + userHandle: base64js.toByteArray(mockUserHandle), + }, + getClientExtensionResults: () => ({}), + type: 'webauthn.get', + }); + }); + }); + + const response = await startAssertion(goodOpts1); + + expect(response).toEqual({ + base64AuthenticatorData: mockClientDataJSON, + base64ClientDataJSON: mockClientDataJSON, + base64Signature: mockSignature, + base64UserHandle: mockUserHandle, + }); + + done(); +}) + +test('should throw error if WebAuthn isn\'t supported', async (done) => { + mockSupportsWebauthn.mockReturnValue(false); + + await expect(startAssertion(goodOpts1)).rejects.toThrow('WebAuthn is not supported in this browser'); + + done(); +}); + +test('should throw error if assertion is cancelled for some reason', async (done) => { + mockSupportsWebauthn.mockReturnValue(true); + + mockNavigatorGet.mockImplementation((): Promise => { + return new Promise((resolve) => { + resolve(null); + }); + }); + + await expect(startAssertion(goodOpts1)).rejects.toThrow('Assertion was not completed'); + + done(); +}); -- cgit v1.2.3 From 50247d5c97d3d7c04a8052618d6477e2b072d59c Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Thu, 21 May 2020 17:18:48 -0700 Subject: Comment out console spies in browser --- packages/browser/src/setupTests.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'packages/browser/src') diff --git a/packages/browser/src/setupTests.ts b/packages/browser/src/setupTests.ts index dcb981e..019ba42 100644 --- a/packages/browser/src/setupTests.ts +++ b/packages/browser/src/setupTests.ts @@ -1,6 +1,7 @@ // Silence some console output -jest.spyOn(console, 'log').mockImplementation(); -jest.spyOn(console, 'debug').mockImplementation(); +// jest.spyOn(console, 'log').mockImplementation(); +// jest.spyOn(console, 'debug').mockImplementation(); +// jest.spyOn(console, 'error').mockImplementation(); /** * JSDom doesn't seem to support `credentials`, so let's define them here so we can mock their -- cgit v1.2.3