diff options
Diffstat (limited to 'packages/browser/src')
18 files changed, 203 insertions, 121 deletions
diff --git a/packages/browser/src/helpers/__jest__/generateCustomError.ts b/packages/browser/src/helpers/__jest__/generateCustomError.ts index 55f6acf..25609fa 100644 --- a/packages/browser/src/helpers/__jest__/generateCustomError.ts +++ b/packages/browser/src/helpers/__jest__/generateCustomError.ts @@ -10,7 +10,10 @@ type WebAuthnErrorName = | 'SecurityError' | 'UnknownError'; -export function generateCustomError(name: WebAuthnErrorName, message = ''): Error { +export function generateCustomError( + name: WebAuthnErrorName, + message = '', +): Error { const customError = new Error(); customError.name = name; customError.message = message; diff --git a/packages/browser/src/helpers/browserSupportsWebAuthn.test.ts b/packages/browser/src/helpers/browserSupportsWebAuthn.test.ts index 20d96c2..dcd5c7c 100644 --- a/packages/browser/src/helpers/browserSupportsWebAuthn.test.ts +++ b/packages/browser/src/helpers/browserSupportsWebAuthn.test.ts @@ -10,13 +10,19 @@ test('should return true when browser supports WebAuthn', () => { }); test('should return false when browser does not support WebAuthn', () => { - delete (window as any).PublicKeyCredential; + // This looks weird but it appeases the linter so it's _fiiiine_ + delete (window as { PublicKeyCredential: unknown }).PublicKeyCredential; expect(browserSupportsWebAuthn()).toBe(false); }); test('should return false when window is undefined', () => { // Make window undefined as it is in node environments. - const windowSpy = jest.spyOn<any, 'window'>(global, 'window', 'get'); + const windowSpy = jest.spyOn<typeof globalThis, 'window'>( + global, + 'window', + 'get', + ); + // @ts-ignore: Intentionally making window unavailable windowSpy.mockImplementation(() => undefined); expect(window).toBe(undefined); diff --git a/packages/browser/src/helpers/browserSupportsWebAuthn.ts b/packages/browser/src/helpers/browserSupportsWebAuthn.ts index 79fe673..706862d 100644 --- a/packages/browser/src/helpers/browserSupportsWebAuthn.ts +++ b/packages/browser/src/helpers/browserSupportsWebAuthn.ts @@ -3,6 +3,7 @@ */ export function browserSupportsWebAuthn(): boolean { return ( - window?.PublicKeyCredential !== undefined && typeof window.PublicKeyCredential === 'function' + window?.PublicKeyCredential !== undefined && + typeof window.PublicKeyCredential === 'function' ); } diff --git a/packages/browser/src/helpers/browserSupportsWebAuthnAutofill.ts b/packages/browser/src/helpers/browserSupportsWebAuthnAutofill.ts index afc1176..cfdfb52 100644 --- a/packages/browser/src/helpers/browserSupportsWebAuthnAutofill.ts +++ b/packages/browser/src/helpers/browserSupportsWebAuthnAutofill.ts @@ -4,18 +4,19 @@ import { PublicKeyCredentialFuture } from '@simplewebauthn/typescript-types'; * Determine if the browser supports conditional UI, so that WebAuthn credentials can * be shown to the user in the browser's typical password autofill popup. */ -export async function browserSupportsWebAuthnAutofill(): Promise<boolean> { +export function browserSupportsWebAuthnAutofill(): Promise<boolean> { /** * I don't like the `as unknown` here but there's a `declare var PublicKeyCredential` in * TS' DOM lib that's making it difficult for me to just go `as PublicKeyCredentialFuture` as I * want. I think I'm fine with this for now since it's _supposed_ to be temporary, until TS types * have a chance to catch up. */ - const globalPublicKeyCredential = - window.PublicKeyCredential as unknown as PublicKeyCredentialFuture; + const globalPublicKeyCredential = window + .PublicKeyCredential as unknown as PublicKeyCredentialFuture; - return ( - globalPublicKeyCredential.isConditionalMediationAvailable !== undefined && - globalPublicKeyCredential.isConditionalMediationAvailable() - ); + if (globalPublicKeyCredential.isConditionalMediationAvailable === undefined) { + return new Promise((resolve) => resolve(false)); + } + + return globalPublicKeyCredential.isConditionalMediationAvailable(); } diff --git a/packages/browser/src/helpers/identifyAuthenticationError.ts b/packages/browser/src/helpers/identifyAuthenticationError.ts index d8d6960..78732b2 100644 --- a/packages/browser/src/helpers/identifyAuthenticationError.ts +++ b/packages/browser/src/helpers/identifyAuthenticationError.ts @@ -57,7 +57,8 @@ export function identifyAuthenticationError({ // https://www.w3.org/TR/webauthn-2/#sctn-op-get-assertion (Step 1) // https://www.w3.org/TR/webauthn-2/#sctn-op-get-assertion (Step 12) return new WebAuthnError({ - message: 'The authenticator was unable to process the specified options, or could not create a new assertion signature', + message: + 'The authenticator was unable to process the specified options, or could not create a new assertion signature', code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR', cause: error, }); diff --git a/packages/browser/src/helpers/identifyRegistrationError.ts b/packages/browser/src/helpers/identifyRegistrationError.ts index 02c9dac..59533da 100644 --- a/packages/browser/src/helpers/identifyRegistrationError.ts +++ b/packages/browser/src/helpers/identifyRegistrationError.ts @@ -30,11 +30,14 @@ export function identifyRegistrationError({ if (publicKey.authenticatorSelection?.requireResidentKey === true) { // https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 4) return new WebAuthnError({ - message: 'Discoverable credentials were required but no available authenticator supported it', + message: + 'Discoverable credentials were required but no available authenticator supported it', code: 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT', cause: error, }); - } else if (publicKey.authenticatorSelection?.userVerification === 'required') { + } else if ( + publicKey.authenticatorSelection?.userVerification === 'required' + ) { // https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 5) return new WebAuthnError({ message: 'User verification was required but no available authenticator supported it', @@ -48,7 +51,7 @@ export function identifyRegistrationError({ return new WebAuthnError({ message: 'The authenticator was previously registered', code: 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED', - cause: error + cause: error, }); } else if (error.name === 'NotAllowedError') { /** @@ -62,7 +65,7 @@ export function identifyRegistrationError({ }); } else if (error.name === 'NotSupportedError') { const validPubKeyCredParams = publicKey.pubKeyCredParams.filter( - param => param.type === 'public-key', + (param) => param.type === 'public-key', ); if (validPubKeyCredParams.length === 0) { @@ -76,7 +79,8 @@ export function identifyRegistrationError({ // https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 2) return new WebAuthnError({ - message: 'No available authenticator supported any of the specified pubKeyCredParams algorithms', + message: + 'No available authenticator supported any of the specified pubKeyCredParams algorithms', code: 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG', cause: error, }); @@ -87,7 +91,7 @@ export function identifyRegistrationError({ return new WebAuthnError({ message: `${window.location.hostname} is an invalid domain`, code: 'ERROR_INVALID_DOMAIN', - cause: error + cause: error, }); } else if (publicKey.rp.id !== effectiveDomain) { // https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 8) @@ -110,7 +114,8 @@ export function identifyRegistrationError({ // https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 1) // https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 8) return new WebAuthnError({ - message: 'The authenticator was unable to process the specified options, or could not create a new credential', + message: + 'The authenticator was unable to process the specified options, or could not create a new credential', code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR', cause: error, }); diff --git a/packages/browser/src/helpers/isValidDomain.ts b/packages/browser/src/helpers/isValidDomain.ts index 4d2eedd..22f045f 100644 --- a/packages/browser/src/helpers/isValidDomain.ts +++ b/packages/browser/src/helpers/isValidDomain.ts @@ -9,6 +9,7 @@ export function isValidDomain(hostname: string): boolean { return ( // Consider localhost valid as well since it's okay wrt Secure Contexts - hostname === 'localhost' || /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(hostname) + hostname === 'localhost' || + /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(hostname) ); } diff --git a/packages/browser/src/helpers/platformAuthenticatorIsAvailable.test.ts b/packages/browser/src/helpers/platformAuthenticatorIsAvailable.test.ts index 3e0b65b..6f2b91d 100644 --- a/packages/browser/src/helpers/platformAuthenticatorIsAvailable.test.ts +++ b/packages/browser/src/helpers/platformAuthenticatorIsAvailable.test.ts @@ -7,8 +7,8 @@ beforeEach(() => { // @ts-ignore 2741 window.PublicKeyCredential = jest.fn().mockReturnValue(() => {}); - window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = - mockIsUVPAA.mockResolvedValue(true); + window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = mockIsUVPAA + .mockResolvedValue(true); }); test('should return true when platform authenticator is available', async () => { @@ -26,7 +26,8 @@ test('should return false when platform authenticator is unavailable', async () }); test('should return false when browser does not support WebAuthn', async () => { - delete (window as any).PublicKeyCredential; + // This looks weird but it appeases the linter so it's _fiiiine_ + delete (window as { PublicKeyCredential: unknown }).PublicKeyCredential; const isAvailable = await platformAuthenticatorIsAvailable(); expect(isAvailable).toEqual(false); diff --git a/packages/browser/src/helpers/platformAuthenticatorIsAvailable.ts b/packages/browser/src/helpers/platformAuthenticatorIsAvailable.ts index 7dc1505..269789c 100644 --- a/packages/browser/src/helpers/platformAuthenticatorIsAvailable.ts +++ b/packages/browser/src/helpers/platformAuthenticatorIsAvailable.ts @@ -6,9 +6,9 @@ import { browserSupportsWebAuthn } from './browserSupportsWebAuthn'; * * This method will _not_ be able to tell you the name of the platform authenticator. */ -export async function platformAuthenticatorIsAvailable(): Promise<boolean> { +export function platformAuthenticatorIsAvailable(): Promise<boolean> { if (!browserSupportsWebAuthn()) { - return false; + return new Promise((resolve) => resolve(false)); } return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); diff --git a/packages/browser/src/helpers/webAuthnAbortService.test.ts b/packages/browser/src/helpers/webAuthnAbortService.test.ts index 98c1ccd..506bb2a 100644 --- a/packages/browser/src/helpers/webAuthnAbortService.test.ts +++ b/packages/browser/src/helpers/webAuthnAbortService.test.ts @@ -13,7 +13,7 @@ test('should call abort() with AbortError on existing controller when creating a // Spy on the existing instance of AbortController const abortSpy = jest.fn(); - // @ts-ignore + // @ts-ignore: Ignore the fact that `controller` is private webauthnAbortService.controller.abort = abortSpy; // Generate a new signal, which should call `abort()` on the existing controller diff --git a/packages/browser/src/helpers/webAuthnAbortService.ts b/packages/browser/src/helpers/webAuthnAbortService.ts index f90b263..50e00ba 100644 --- a/packages/browser/src/helpers/webAuthnAbortService.ts +++ b/packages/browser/src/helpers/webAuthnAbortService.ts @@ -12,7 +12,9 @@ class WebAuthnAbortService { createNewAbortSignal() { // Abort any existing calls to navigator.credentials.create() or navigator.credentials.get() if (this.controller) { - const abortError = new Error('Cancelling existing WebAuthn API call for new one'); + const abortError = new Error( + 'Cancelling existing WebAuthn API call for new one', + ); abortError.name = 'AbortError'; this.controller.abort(abortError); } diff --git a/packages/browser/src/helpers/webAuthnError.ts b/packages/browser/src/helpers/webAuthnError.ts index 1debec0..fb1def5 100644 --- a/packages/browser/src/helpers/webAuthnError.ts +++ b/packages/browser/src/helpers/webAuthnError.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ /** * A custom Error used to return a more nuanced error detailing _why_ one of the eight documented * errors in the spec was raised after calling `navigator.credentials.create()` or @@ -25,24 +24,20 @@ export class WebAuthnError extends Error { cause, name, }: { - message: string, - code: WebAuthnErrorCode, - cause: Error, - name?: string, + message: string; + code: WebAuthnErrorCode; + cause: Error; + name?: string; }) { - /** - * `cause` is supported in evergreen browsers, but not IE10, so this ts-ignore is to - * help Rollup complete the ES5 build. - */ - // @ts-ignore - super(message, { cause }) + // @ts-ignore: help Rollup understand that `cause` is okay to set + super(message, { cause }); this.name = name ?? cause.name; this.code = code; } } export type WebAuthnErrorCode = - 'ERROR_CEREMONY_ABORTED' + | 'ERROR_CEREMONY_ABORTED' | 'ERROR_INVALID_DOMAIN' | 'ERROR_INVALID_RP_ID' | 'ERROR_INVALID_USER_ID_LENGTH' @@ -52,5 +47,4 @@ export type WebAuthnErrorCode = | 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT' | 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED' | 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG' - | 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY' - ; + | 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY'; diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 67c7c74..77cd491 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -9,11 +9,11 @@ import { platformAuthenticatorIsAvailable } from './helpers/platformAuthenticato import { browserSupportsWebAuthnAutofill } from './helpers/browserSupportsWebAuthnAutofill'; export { - startRegistration, - startAuthentication, browserSupportsWebAuthn, browserSupportsWebAuthnAutofill, platformAuthenticatorIsAvailable, + startAuthentication, + startRegistration, }; export type { WebAuthnErrorCode } from './helpers/webAuthnError'; diff --git a/packages/browser/src/methods/startAuthentication.test.ts b/packages/browser/src/methods/startAuthentication.test.ts index f8830ae..11f078e 100644 --- a/packages/browser/src/methods/startAuthentication.test.ts +++ b/packages/browser/src/methods/startAuthentication.test.ts @@ -1,8 +1,8 @@ import { AuthenticationCredential, - PublicKeyCredentialRequestOptionsJSON, AuthenticationExtensionsClientInputs, AuthenticationExtensionsClientOutputs, + PublicKeyCredentialRequestOptionsJSON, } from '@simplewebauthn/typescript-types'; import { browserSupportsWebAuthn } from '../helpers/browserSupportsWebAuthn'; @@ -49,8 +49,8 @@ const goodOpts2UTF8: PublicKeyCredentialRequestOptionsJSON = { beforeEach(() => { // Stub out a response so the method won't throw - mockNavigatorGet.mockImplementation((): Promise<any> => { - return new Promise(resolve => { + mockNavigatorGet.mockImplementation((): Promise<unknown> => { + return new Promise((resolve) => { resolve({ response: {}, getClientExtensionResults: () => ({}), @@ -62,7 +62,7 @@ beforeEach(() => { mockSupportsAutofill.mockResolvedValue(true); // Reset the abort service so we get an accurate call count - // @ts-ignore + // @ts-ignore: Ignore the fact that `controller` is private webauthnAbortService.controller = undefined; }); @@ -78,7 +78,9 @@ test('should convert options before passing to navigator.credentials.get(...)', const argsPublicKey = mockNavigatorGet.mock.calls[0][0].publicKey; const credId = argsPublicKey.allowCredentials[0].id; - expect(new Uint8Array(argsPublicKey.challenge)).toEqual(new Uint8Array([102, 105, 122, 122])); + expect(new Uint8Array(argsPublicKey.challenge)).toEqual( + new Uint8Array([102, 105, 122, 122]), + ); // Make sure the credential ID is an ArrayBuffer with a length of 64 expect(credId instanceof ArrayBuffer).toEqual(true); expect(credId.byteLength).toEqual(64); @@ -104,7 +106,7 @@ test('should convert allow allowCredential to undefined when empty', async () => test('should return base64url-encoded response values', async () => { mockNavigatorGet.mockImplementation((): Promise<AuthenticationCredential> => { - return new Promise(resolve => { + return new Promise((resolve) => { resolve({ id: 'foobar', rawId: Buffer.from('foobar', 'ascii'), @@ -124,13 +126,15 @@ test('should return base64url-encoded response values', async () => { const response = await startAuthentication(goodOpts1); expect(response.rawId).toEqual('Zm9vYmFy'); - expect(response.response.authenticatorData).toEqual('bW9ja0F1dGhlbnRpY2F0b3JEYXRh'); + expect(response.response.authenticatorData).toEqual( + 'bW9ja0F1dGhlbnRpY2F0b3JEYXRh', + ); expect(response.response.clientDataJSON).toEqual('bW9ja0NsaWVudERhdGFKU09O'); expect(response.response.signature).toEqual('bW9ja1NpZ25hdHVyZQ'); expect(response.response.userHandle).toEqual('mockUserHandle'); }); -test("should throw error if WebAuthn isn't supported", async () => { +test('should throw error if WebAuthn isn\'t supported', async () => { mockSupportsWebAuthn.mockReturnValue(false); await expect(startAuthentication(goodOpts1)).rejects.toThrow( @@ -140,12 +144,14 @@ test("should throw error if WebAuthn isn't supported", async () => { test('should throw error if assertion is cancelled for some reason', async () => { mockNavigatorGet.mockImplementation((): Promise<null> => { - return new Promise(resolve => { + return new Promise((resolve) => { resolve(null); }); }); - await expect(startAuthentication(goodOpts1)).rejects.toThrow('Authentication was not completed'); + await expect(startAuthentication(goodOpts1)).rejects.toThrow( + 'Authentication was not completed', + ); }); test('should handle UTF-8 challenges', async () => { @@ -155,7 +161,24 @@ test('should handle UTF-8 challenges', async () => { expect(new Uint8Array(argsPublicKey.challenge)).toEqual( new Uint8Array([ - 227, 130, 132, 227, 130, 140, 227, 130, 132, 227, 130, 140, 227, 129, 160, 227, 129, 156, + 227, + 130, + 132, + 227, + 130, + 140, + 227, + 130, + 132, + 227, + 130, + 140, + 227, + 129, + 160, + 227, + 129, + 156, ]), ); }); @@ -164,9 +187,9 @@ test('should send extensions to authenticator if present in options', async () = const extensions: AuthenticationExtensionsClientInputs = { credProps: true, appid: 'appidHere', - // @ts-ignore + // @ts-ignore: Send arbitrary extensions uvm: true, - // @ts-ignore + // @ts-ignore: Send arbitrary extensions appidExclude: 'appidExcludeHere', }; const optsWithExts: PublicKeyCredentialRequestOptionsJSON = { @@ -197,8 +220,8 @@ test('should include extension results', async () => { }; // Mock extension return values from authenticator - mockNavigatorGet.mockImplementation((): Promise<any> => { - return new Promise(resolve => { + mockNavigatorGet.mockImplementation((): Promise<unknown> => { + return new Promise((resolve) => { resolve({ response: {}, getClientExtensionResults: () => extResults }); }); }); @@ -228,7 +251,10 @@ test('should support "cable" transport', async () => { await startAuthentication(opts); - expect(mockNavigatorGet.mock.calls[0][0].publicKey.allowCredentials[0].transports[0]).toEqual( + expect( + mockNavigatorGet.mock.calls[0][0].publicKey.allowCredentials[0] + .transports[0], + ).toEqual( 'cable', ); }); @@ -266,8 +292,10 @@ test('should set up autofill a.k.a. Conditional UI', async () => { expect(mockNavigatorGet.mock.calls[0][0].mediation).toEqual('conditional'); // The latest version of https://github.com/w3c/webauthn/pull/1576 says allowCredentials should // be an "empty list", as opposed to being undefined - expect(mockNavigatorGet.mock.calls[0][0].publicKey.allowCredentials).toBeDefined(); - expect(mockNavigatorGet.mock.calls[0][0].publicKey.allowCredentials.length).toEqual(0); + expect(mockNavigatorGet.mock.calls[0][0].publicKey.allowCredentials) + .toBeDefined(); + expect(mockNavigatorGet.mock.calls[0][0].publicKey.allowCredentials.length) + .toEqual(0); }); test('should throw error if autofill not supported', async () => { @@ -295,11 +323,11 @@ test('should throw error if no acceptable <input> is found', async () => { test('should return authenticatorAttachment if present', async () => { // Mock extension return values from authenticator - mockNavigatorGet.mockImplementation((): Promise<any> => { - return new Promise(resolve => { + mockNavigatorGet.mockImplementation((): Promise<unknown> => { + return new Promise((resolve) => { resolve({ response: {}, - getClientExtensionResults: () => { }, + getClientExtensionResults: () => {}, authenticatorAttachment: 'cross-platform', }); }); @@ -341,7 +369,10 @@ describe('WebAuthnError', () => { * * See https://github.com/MasterKale/SimpleWebAuthn/discussions/350#discussioncomment-4896572 */ - const NotAllowedError = generateCustomError('NotAllowedError', 'Operation failed.'); + const NotAllowedError = generateCustomError( + 'NotAllowedError', + 'Operation failed.', + ); mockNavigatorGet.mockRejectedValueOnce(NotAllowedError); const rejected = await expect(startAuthentication(goodOpts1)).rejects; @@ -361,7 +392,7 @@ describe('WebAuthnError', () => { */ const NotAllowedError = generateCustomError( 'NotAllowedError', - 'WebAuthn is not supported on sites with TLS certificate errors.' + 'WebAuthn is not supported on sites with TLS certificate errors.', ); mockNavigatorGet.mockRejectedValueOnce(NotAllowedError); diff --git a/packages/browser/src/methods/startAuthentication.ts b/packages/browser/src/methods/startAuthentication.ts index cce28e7..f6782ab 100644 --- a/packages/browser/src/methods/startAuthentication.ts +++ b/packages/browser/src/methods/startAuthentication.ts @@ -1,7 +1,7 @@ import { - PublicKeyCredentialRequestOptionsJSON, AuthenticationCredential, AuthenticationResponseJSON, + PublicKeyCredentialRequestOptionsJSON, } from '@simplewebauthn/typescript-types'; import { bufferToBase64URLString } from '../helpers/bufferToBase64URLString'; @@ -33,7 +33,9 @@ export async function startAuthentication( // of public key let allowCredentials; if (requestOptionsJSON.allowCredentials?.length !== 0) { - allowCredentials = requestOptionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor); + allowCredentials = requestOptionsJSON.allowCredentials?.map( + toPublicKeyCredentialDescriptor, + ); } // We need to convert some values to Uint8Arrays before passing the credentials to the navigator @@ -56,11 +58,15 @@ export async function startAuthentication( } // Check for an <input> with "webauthn" in its `autocomplete` attribute - const eligibleInputs = document.querySelectorAll("input[autocomplete*='webauthn']"); + const eligibleInputs = document.querySelectorAll( + 'input[autocomplete*=\'webauthn\']', + ); // WebAuthn autofill requires at least one valid input if (eligibleInputs.length < 1) { - throw Error('No <input> with `"webauthn"` in its `autocomplete` attribute was detected'); + throw Error( + 'No <input> with `"webauthn"` in its `autocomplete` attribute was detected', + ); } // `CredentialMediationRequirement` doesn't know about "conditional" yet as of @@ -106,6 +112,8 @@ export async function startAuthentication( }, type, clientExtensionResults: credential.getClientExtensionResults(), - authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment), + authenticatorAttachment: toAuthenticatorAttachment( + credential.authenticatorAttachment, + ), }; } diff --git a/packages/browser/src/methods/startRegistration.test.ts b/packages/browser/src/methods/startRegistration.test.ts index e27099d..b8ca081 100644 --- a/packages/browser/src/methods/startRegistration.test.ts +++ b/packages/browser/src/methods/startRegistration.test.ts @@ -52,8 +52,8 @@ const goodOpts1: PublicKeyCredentialCreationOptionsJSON = { beforeEach(() => { // Stub out a response so the method won't throw - mockNavigatorCreate.mockImplementation((): Promise<any> => { - return new Promise(resolve => { + mockNavigatorCreate.mockImplementation((): Promise<unknown> => { + return new Promise((resolve) => { resolve({ response: {}, getClientExtensionResults: () => ({}) }); }); }); @@ -61,7 +61,7 @@ beforeEach(() => { mockSupportsWebauthn.mockReturnValue(true); // Reset the abort service so we get an accurate call count - // @ts-ignore + // @ts-ignore: Ignore the fact that `controller` is private webauthnAbortService.controller = undefined; }); @@ -77,8 +77,12 @@ test('should convert options before passing to navigator.credentials.create(...) const credId = argsPublicKey.excludeCredentials[0].id; // Make sure challenge and user.id are converted to Buffers - expect(new Uint8Array(argsPublicKey.challenge)).toEqual(new Uint8Array([102, 105, 122, 122])); - expect(new Uint8Array(argsPublicKey.user.id)).toEqual(new Uint8Array([53, 54, 55, 56])); + expect(new Uint8Array(argsPublicKey.challenge)).toEqual( + new Uint8Array([102, 105, 122, 122]), + ); + expect(new Uint8Array(argsPublicKey.user.id)).toEqual( + new Uint8Array([53, 54, 55, 56]), + ); // Confirm construction of excludeCredentials array expect(credId instanceof ArrayBuffer).toEqual(true); @@ -88,25 +92,27 @@ test('should convert options before passing to navigator.credentials.create(...) }); test('should return base64url-encoded response values', async () => { - mockNavigatorCreate.mockImplementation((): Promise<RegistrationCredential> => { - return new Promise(resolve => { - resolve({ - id: 'foobar', - rawId: utf8StringToBuffer('foobar'), - response: { - attestationObject: Buffer.from(mockAttestationObject, 'ascii'), - clientDataJSON: Buffer.from(mockClientDataJSON, 'ascii'), - getTransports: () => [], - getAuthenticatorData: () => new Uint8Array(), - getPublicKey: () => null, - getPublicKeyAlgorithm: () => -999, - }, - getClientExtensionResults: () => ({}), - type: 'public-key', - authenticatorAttachment: '', + mockNavigatorCreate.mockImplementation( + (): Promise<RegistrationCredential> => { + return new Promise((resolve) => { + resolve({ + id: 'foobar', + rawId: utf8StringToBuffer('foobar'), + response: { + attestationObject: Buffer.from(mockAttestationObject, 'ascii'), + clientDataJSON: Buffer.from(mockClientDataJSON, 'ascii'), + getTransports: () => [], + getAuthenticatorData: () => new Uint8Array(), + getPublicKey: () => null, + getPublicKeyAlgorithm: () => -999, + }, + getClientExtensionResults: () => ({}), + type: 'public-key', + authenticatorAttachment: '', + }); }); - }); - }); + }, + ); const response = await startRegistration(goodOpts1); @@ -115,7 +121,7 @@ test('should return base64url-encoded response values', async () => { expect(response.response.clientDataJSON).toEqual('bW9ja0NsaWU'); }); -test("should throw error if WebAuthn isn't supported", async () => { +test('should throw error if WebAuthn isn\'t supported', async () => { mockSupportsWebauthn.mockReturnValue(false); await expect(startRegistration(goodOpts1)).rejects.toThrow( @@ -125,21 +131,23 @@ test("should throw error if WebAuthn isn't supported", async () => { test('should throw error if attestation is cancelled for some reason', async () => { mockNavigatorCreate.mockImplementation((): Promise<null> => { - return new Promise(resolve => { + return new Promise((resolve) => { resolve(null); }); }); - await expect(startRegistration(goodOpts1)).rejects.toThrow('Registration was not completed'); + await expect(startRegistration(goodOpts1)).rejects.toThrow( + 'Registration was not completed', + ); }); test('should send extensions to authenticator if present in options', async () => { const extensions: AuthenticationExtensionsClientInputs = { credProps: true, appid: 'appidHere', - // @ts-ignore + // @ts-ignore: Send arbitrary extensions uvm: true, - // @ts-ignore + // @ts-ignore: Send arbitrary extensions appidExclude: 'appidExcludeHere', }; const optsWithExts: PublicKeyCredentialCreationOptionsJSON = { @@ -170,8 +178,8 @@ test('should include extension results', async () => { }; // Mock extension return values from authenticator - mockNavigatorCreate.mockImplementation((): Promise<any> => { - return new Promise(resolve => { + mockNavigatorCreate.mockImplementation((): Promise<unknown> => { + return new Promise((resolve) => { resolve({ response: {}, getClientExtensionResults: () => extResults }); }); }); @@ -202,7 +210,8 @@ test('should support "cable" transport in excludeCredentials', async () => { await startRegistration(opts); expect( - mockNavigatorCreate.mock.calls[0][0].publicKey.excludeCredentials[0].transports[0], + mockNavigatorCreate.mock.calls[0][0].publicKey.excludeCredentials[0] + .transports[0], ).toEqual('cable'); }); @@ -235,11 +244,11 @@ test('should cancel an existing call when executed again', async () => { test('should return authenticatorAttachment if present', async () => { // Mock extension return values from authenticator - mockNavigatorCreate.mockImplementation((): Promise<any> => { - return new Promise(resolve => { + mockNavigatorCreate.mockImplementation((): Promise<unknown> => { + return new Promise((resolve) => { resolve({ response: {}, - getClientExtensionResults: () => { }, + getClientExtensionResults: () => {}, authenticatorAttachment: 'cross-platform', }); }); @@ -257,15 +266,15 @@ test('should return convenience values if getters present', async () => { * that's already buried in the response. */ // Mock extension return values from authenticator - mockNavigatorCreate.mockImplementation((): Promise<any> => { - return new Promise(resolve => { + mockNavigatorCreate.mockImplementation((): Promise<unknown> => { + return new Promise((resolve) => { resolve({ response: { getPublicKeyAlgorithm: () => 777, getPublicKey: () => new Uint8Array([0, 0, 0, 0]).buffer, getAuthenticatorData: () => new Uint8Array([0, 0, 0, 0]).buffer, }, - getClientExtensionResults: () => { }, + getClientExtensionResults: () => {}, }); }); }); @@ -284,11 +293,11 @@ test('should not return convenience values if getters missing', async () => { * that's already buried in the response. */ // Mock extension return values from authenticator - mockNavigatorCreate.mockImplementation((): Promise<any> => { - return new Promise(resolve => { + mockNavigatorCreate.mockImplementation((): Promise<unknown> => { + return new Promise((resolve) => { resolve({ response: {}, - getClientExtensionResults: () => { }, + getClientExtensionResults: () => {}, }); }); }); @@ -341,7 +350,10 @@ describe('WebAuthnError', () => { rejected.toThrow(/discoverable credentials were required/i); rejected.toThrow(/no available authenticator supported/i); rejected.toHaveProperty('name', 'ConstraintError'); - rejected.toHaveProperty('code', 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT'); + rejected.toHaveProperty( + 'code', + 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT', + ); rejected.toHaveProperty('cause', ConstraintError); }); @@ -360,7 +372,10 @@ describe('WebAuthnError', () => { rejected.toThrow(/user verification was required/i); rejected.toThrow(/no available authenticator supported/i); rejected.toHaveProperty('name', 'ConstraintError'); - rejected.toHaveProperty('code', 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT'); + rejected.toHaveProperty( + 'code', + 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT', + ); rejected.toHaveProperty('cause', ConstraintError); }); }); @@ -376,7 +391,10 @@ describe('WebAuthnError', () => { rejected.toThrow(/authenticator/i); rejected.toThrow(/previously registered/i); rejected.toHaveProperty('name', 'InvalidStateError'); - rejected.toHaveProperty('code', 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED'); + rejected.toHaveProperty( + 'code', + 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED', + ); rejected.toHaveProperty('cause', InvalidStateError); }); }); @@ -389,7 +407,10 @@ describe('WebAuthnError', () => { * * See https://github.com/MasterKale/SimpleWebAuthn/discussions/350#discussioncomment-4896572 */ - const NotAllowedError = generateCustomError('NotAllowedError', 'Operation failed.'); + const NotAllowedError = generateCustomError( + 'NotAllowedError', + 'Operation failed.', + ); mockNavigatorCreate.mockRejectedValueOnce(NotAllowedError); const rejected = await expect(startRegistration(goodOpts1)).rejects; @@ -409,7 +430,7 @@ describe('WebAuthnError', () => { */ const NotAllowedError = generateCustomError( 'NotAllowedError', - 'WebAuthn is not supported on sites with TLS certificate errors.' + 'WebAuthn is not supported on sites with TLS certificate errors.', ); mockNavigatorCreate.mockRejectedValueOnce(NotAllowedError); @@ -455,7 +476,10 @@ describe('WebAuthnError', () => { rejected.toThrow(/No available authenticator/i); rejected.toThrow(/pubKeyCredParams/i); rejected.toHaveProperty('name', 'NotSupportedError'); - rejected.toHaveProperty('code', 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG'); + rejected.toHaveProperty( + 'code', + 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG', + ); rejected.toHaveProperty('cause', NotSupportedError); }); }); diff --git a/packages/browser/src/methods/startRegistration.ts b/packages/browser/src/methods/startRegistration.ts index 5b97a5e..74da7fd 100644 --- a/packages/browser/src/methods/startRegistration.ts +++ b/packages/browser/src/methods/startRegistration.ts @@ -1,8 +1,8 @@ import { + AuthenticatorTransportFuture, PublicKeyCredentialCreationOptionsJSON, RegistrationCredential, RegistrationResponseJSON, - AuthenticatorTransportFuture, } from '@simplewebauthn/typescript-types'; import { utf8StringToBuffer } from '../helpers/utf8StringToBuffer'; @@ -81,7 +81,9 @@ export async function startRegistration( // L3 says this is required, but browser and webview support are still not guaranteed. let responseAuthenticatorData: string | undefined; if (typeof response.getAuthenticatorData === 'function') { - responseAuthenticatorData = bufferToBase64URLString(response.getAuthenticatorData()); + responseAuthenticatorData = bufferToBase64URLString( + response.getAuthenticatorData(), + ); } return { @@ -97,6 +99,8 @@ export async function startRegistration( }, type, clientExtensionResults: credential.getClientExtensionResults(), - authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment), + authenticatorAttachment: toAuthenticatorAttachment( + credential.authenticatorAttachment, + ), }; } diff --git a/packages/browser/src/setupTests.ts b/packages/browser/src/setupTests.ts index 5b6efcf..2ac528a 100644 --- a/packages/browser/src/setupTests.ts +++ b/packages/browser/src/setupTests.ts @@ -7,7 +7,7 @@ * JSDom doesn't seem to support `credentials`, so let's define them here so we can mock their * implementations in specific tests. */ -Object.defineProperty(window.navigator, 'credentials', { +Object.defineProperty(globalThis.window.navigator, 'credentials', { writable: true, value: { create: jest.fn(), |