diff options
author | Matthew Miller <matthew@millerti.me> | 2022-03-06 22:55:28 -0800 |
---|---|---|
committer | Matthew Miller <matthew@millerti.me> | 2022-03-06 22:55:28 -0800 |
commit | de0443c7d037d32728e03c710522e7591ebfb2d5 (patch) | |
tree | e21e4d8269ccd2a00fbabc9429f767ad6fa2c864 /packages/browser/src/methods/startRegistration.test.ts | |
parent | d710a018e3a64be907ff97f068a8d3cb3b3fee12 (diff) |
Write tests for startRegistration
Diffstat (limited to 'packages/browser/src/methods/startRegistration.test.ts')
-rw-r--r-- | packages/browser/src/methods/startRegistration.test.ts | 240 |
1 files changed, 238 insertions, 2 deletions
diff --git a/packages/browser/src/methods/startRegistration.test.ts b/packages/browser/src/methods/startRegistration.test.ts index 76a01fb..f875a08 100644 --- a/packages/browser/src/methods/startRegistration.test.ts +++ b/packages/browser/src/methods/startRegistration.test.ts @@ -8,6 +8,7 @@ import { import utf8StringToBuffer from '../helpers/utf8StringToBuffer'; import { browserSupportsWebauthn } from '../helpers/browserSupportsWebauthn'; import bufferToBase64URLString from '../helpers/bufferToBase64URLString'; +import { WebAuthnError } from '../helpers/structs'; import startRegistration from './startRegistration'; @@ -29,8 +30,8 @@ const goodOpts1: PublicKeyCredentialCreationOptionsJSON = { }, ], rp: { - id: '1234', - name: 'simplewebauthn', + id: 'simplewebauthn.dev', + name: 'SimpleWebAuthn', }, user: { id: '5678', @@ -173,3 +174,238 @@ test('should include extension results when no extensions specified', async () = expect(response.clientExtensionResults).toEqual({}); }); + +describe('WebAuthnError', () => { + describe('AbortError', () => { + /** + * We can't actually test this because nothing in startRegistration() propagates the abort + * signal. But if you invoked WebAuthn via this and then manually sent an abort signal I guess + * this will catch. + * + * As a matter of fact I couldn't actually get any browser to respect the abort signal... + */ + test.skip('should identify abort signal', async () => { + mockNavigatorCreate.mockRejectedValueOnce(new AbortError()); + + const rejected = await expect(startRegistration(goodOpts1)).rejects; + rejected.toThrow(WebAuthnError); + rejected.toThrow(/abort signal/i); + rejected.toThrow(/AbortError/); + }); + }); + + describe('ConstraintError', () => { + test('should identify unsupported discoverable credentials', async () => { + mockNavigatorCreate.mockRejectedValueOnce(new ConstraintError()); + + const opts: PublicKeyCredentialCreationOptionsJSON = { + ...goodOpts1, + authenticatorSelection: { + residentKey: 'required', + requireResidentKey: true, + } + }; + + const rejected = await expect(startRegistration(opts)).rejects; + rejected.toThrow(WebAuthnError); + rejected.toThrow(/discoverable credentials were required/i); + rejected.toThrow(/no available authenticator supported/i); + rejected.toThrow(/ConstraintError/); + }); + + test('should identify unsupported user verification', async () => { + mockNavigatorCreate.mockRejectedValueOnce(new ConstraintError()); + + const opts: PublicKeyCredentialCreationOptionsJSON = { + ...goodOpts1, + authenticatorSelection: { + userVerification: 'required', + } + }; + + const rejected = await expect(startRegistration(opts)).rejects; + rejected.toThrow(WebAuthnError); + rejected.toThrow(/user verification was required/i); + rejected.toThrow(/no available authenticator supported/i); + rejected.toThrow(/ConstraintError/); + }); + }); + + describe('InvalidStateError', () => { + test('should identify re-registration attempt', async () => { + mockNavigatorCreate.mockRejectedValueOnce(new InvalidStateError()); + + const rejected = await expect(startRegistration(goodOpts1)).rejects; + rejected.toThrow(WebAuthnError); + rejected.toThrow(/authenticator/i); + rejected.toThrow(/previously registered/i); + rejected.toThrow(/InvalidStateError/); + }); + }); + + describe('NotAllowedError', () => { + test('should identify cancellation or timeout', async () => { + mockNavigatorCreate.mockRejectedValueOnce(new NotAllowedError()); + + const rejected = await expect(startRegistration(goodOpts1)).rejects; + rejected.toThrow(WebAuthnError); + rejected.toThrow(/cancel/i); + rejected.toThrow(/timed out/i); + rejected.toThrow(/NotAllowedError/); + }); + }); + + describe('NotSupportedError', () => { + test('should identify missing "public-key" entries in pubKeyCredParams', async () => { + mockNavigatorCreate.mockRejectedValueOnce(new NotSupportedError()); + + const opts = { + ...goodOpts1, + pubKeyCredParams: [], + }; + + const rejected = await expect(startRegistration(opts)).rejects; + rejected.toThrow(WebAuthnError); + rejected.toThrow(/pubKeyCredParams/i); + rejected.toThrow(/public-key/i); + rejected.toThrow(/NotSupportedError/); + }); + + test('should identify no authenticator supports algs in pubKeyCredParams', async () => { + mockNavigatorCreate.mockRejectedValueOnce(new NotSupportedError()); + + const opts: PublicKeyCredentialCreationOptionsJSON = { + ...goodOpts1, + pubKeyCredParams: [{ alg: -7, type: 'public-key' }], + }; + + const rejected = await expect(startRegistration(opts)).rejects; + rejected.toThrow(WebAuthnError); + rejected.toThrow(/No available authenticator/i); + rejected.toThrow(/pubKeyCredParams/i); + rejected.toThrow(/NotSupportedError/); + }); + }); + + describe('SecurityError', () => { + let _originalHostName: string; + + beforeEach(() => { + _originalHostName = window.location.hostname; + }); + + afterEach(() => { + window.location.hostname = _originalHostName; + }); + + test('should identify invalid domain', async () => { + window.location.hostname = '1.2.3.4'; + + mockNavigatorCreate.mockRejectedValueOnce(new SecurityError()); + + const rejected = await expect(startRegistration(goodOpts1)).rejects; + rejected.toThrowError(WebAuthnError); + rejected.toThrow(/1\.2\.3\.4/); + rejected.toThrow(/invalid domain/i); + rejected.toThrow(/SecurityError/); + }); + + test('should identify invalid RP ID', async () => { + window.location.hostname = 'simplewebauthn.com'; + + mockNavigatorCreate.mockRejectedValueOnce(new SecurityError()); + + const rejected = await expect(startRegistration(goodOpts1)).rejects; + rejected.toThrowError(WebAuthnError); + rejected.toThrow(goodOpts1.rp.id); + rejected.toThrow(/invalid for this domain/i); + rejected.toThrow(/SecurityError/); + }); + }); + + describe('TypeError', () => { + test('should identify malformed user ID', async () => { + mockNavigatorCreate.mockRejectedValueOnce(new TypeError('user id is bad')); + + const opts = { + ...goodOpts1, + user: { + ...goodOpts1.user, + id: Array(65).fill('a').join(''), + } + }; + + const rejected = await expect(startRegistration(opts)).rejects; + rejected.toThrowError(WebAuthnError); + rejected.toThrow(/user id/i); + rejected.toThrow(/not between 1 and 64 characters/i); + rejected.toThrow(/TypeError/); + }); + }); + + describe('UnknownError', () => { + test('should identify potential authenticator issues', async () => { + mockNavigatorCreate.mockRejectedValueOnce(new UnknownError()); + + const rejected = await expect(startRegistration(goodOpts1)).rejects; + rejected.toThrow(WebAuthnError); + rejected.toThrow(/authenticator/i); + rejected.toThrow(/unable to process the specified options/i); + rejected.toThrow(/could not create a new credential/i); + rejected.toThrow(/UnknownError/); + }); + }); +}); + +/** + * Custom errors raised by WebAuthn + */ + +class AbortError extends Error { + constructor() { + super(); + this.name = 'AbortError'; + } +} + +class ConstraintError extends Error { + constructor() { + super(); + this.name = 'ConstraintError'; + } +} + +class InvalidStateError extends Error { + constructor() { + super(); + this.name = 'InvalidStateError'; + } +} + +class NotAllowedError extends Error { + constructor() { + super(); + this.name = 'NotAllowedError'; + } +} + +class NotSupportedError extends Error { + constructor() { + super(); + this.name = 'NotSupportedError'; + } +} + +class SecurityError extends Error { + constructor() { + super(); + this.name = 'SecurityError'; + } +} + +class UnknownError extends Error { + constructor() { + super(); + this.name = 'UnknownError'; + } +} |