diff options
author | Matthew Miller <matthew@millerti.me> | 2023-08-22 10:13:03 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-22 10:13:03 -0700 |
commit | fefc95e4535e6ecf903f647124a492fba3fd11d6 (patch) | |
tree | 4c924d43d32fb12a780533302eaf5dee08875d75 /packages/browser/src/methods | |
parent | 443c341bc2163f07b93a3ef84a43294d10b826f8 (diff) | |
parent | 2935857c76d458c26701842e500f8d97d17499c5 (diff) |
Merge pull request #425 from MasterKale/feat/server-esm-take-2-dnt
feat/server-esm-take-2-dnt
Diffstat (limited to 'packages/browser/src/methods')
4 files changed, 144 insertions, 77 deletions
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, + ), }; } |