Skip to content

Commit

Permalink
Switch startRegistration to options blob too
Browse files Browse the repository at this point in the history
  • Loading branch information
MasterKale committed Oct 6, 2024
1 parent 5d5ee04 commit 3b61ec1
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 34 deletions.
58 changes: 29 additions & 29 deletions packages/browser/src/methods/startRegistration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ afterEach(() => {
});

test('should convert options before passing to navigator.credentials.create(...)', async () => {
await startRegistration(goodOpts1);
await startRegistration({ optionsJSON: goodOpts1 });

const argsPublicKey = mockNavigatorCreate.mock.calls[0][0].publicKey;
const credId = argsPublicKey.excludeCredentials[0].id;
Expand Down Expand Up @@ -111,7 +111,7 @@ test('should return base64url-encoded response values', async () => {
},
);

const response = await startRegistration(goodOpts1);
const response = await startRegistration({ optionsJSON: goodOpts1 });

expect(response.rawId).toEqual('6mUg8GzxDxs');
expect(response.response.attestationObject).toEqual('bW9ja0F0dGU');
Expand All @@ -121,7 +121,7 @@ test('should return base64url-encoded response values', async () => {
test("should throw error if WebAuthn isn't supported", async () => {
mockSupportsWebauthn.mockReturnValue(false);

await expect(startRegistration(goodOpts1)).rejects.toThrow(
await expect(startRegistration({ optionsJSON: goodOpts1 })).rejects.toThrow(
'WebAuthn is not supported in this browser',
);
});
Expand All @@ -133,7 +133,7 @@ test('should throw error if attestation is cancelled for some reason', async ()
});
});

await expect(startRegistration(goodOpts1)).rejects.toThrow(
await expect(startRegistration({ optionsJSON: goodOpts1 })).rejects.toThrow(
'Registration was not completed',
);
});
Expand All @@ -151,15 +151,15 @@ test('should send extensions to authenticator if present in options', async () =
...goodOpts1,
extensions,
};
await startRegistration(optsWithExts);
await startRegistration({ optionsJSON: optsWithExts });

const argsExtensions = mockNavigatorCreate.mock.calls[0][0].publicKey.extensions;

expect(argsExtensions).toEqual(extensions);
});

test('should not set any extensions if not present in options', async () => {
await startRegistration(goodOpts1);
await startRegistration({ optionsJSON: goodOpts1 });

const argsExtensions = mockNavigatorCreate.mock.calls[0][0].publicKey.extensions;

Expand All @@ -182,13 +182,13 @@ test('should include extension results', async () => {
});

// Extensions aren't present in this object, but it doesn't matter since we're faking the response
const response = await startRegistration(goodOpts1);
const response = await startRegistration({ optionsJSON: goodOpts1 });

expect(response.clientExtensionResults).toEqual(extResults);
});

test('should include extension results when no extensions specified', async () => {
const response = await startRegistration(goodOpts1);
const response = await startRegistration({ optionsJSON: goodOpts1 });

expect(response.clientExtensionResults).toEqual({});
});
Expand All @@ -204,7 +204,7 @@ test('should support "cable" transport in excludeCredentials', async () => {
],
};

await startRegistration(opts);
await startRegistration({ optionsJSON: opts });

expect(
mockNavigatorCreate.mock.calls[0][0].publicKey.excludeCredentials[0]
Expand All @@ -225,7 +225,7 @@ test('should return "cable" transport from response', async () => {
type: 'webauthn.create',
});

const regResponse = await startRegistration(goodOpts1);
const regResponse = await startRegistration({ optionsJSON: goodOpts1 });

expect(regResponse.response.transports).toEqual(['cable']);
});
Expand All @@ -234,8 +234,8 @@ test('should cancel an existing call when executed again', async () => {
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');

// Fire off a request and immediately attempt a second one
startRegistration(goodOpts1);
await startRegistration(goodOpts1);
startRegistration({ optionsJSON: goodOpts1 });
await startRegistration({ optionsJSON: goodOpts1 });
expect(abortSpy).toHaveBeenCalledTimes(1);
});

Expand All @@ -251,7 +251,7 @@ test('should return authenticatorAttachment if present', async () => {
});
});

const response = await startRegistration(goodOpts1);
const response = await startRegistration({ optionsJSON: goodOpts1 });

expect(response.authenticatorAttachment).toEqual('cross-platform');
});
Expand All @@ -276,7 +276,7 @@ test('should return convenience values if getters present', async () => {
});
});

const response = await startRegistration(goodOpts1);
const response = await startRegistration({ optionsJSON: goodOpts1 });

expect(response.response.publicKeyAlgorithm).toEqual(777);
expect(response.response.publicKey).toEqual('AAAAAA');
Expand All @@ -299,7 +299,7 @@ test('should not return convenience values if getters missing', async () => {
});
});

const response = await startRegistration(goodOpts1);
const response = await startRegistration({ optionsJSON: goodOpts1 });

expect(response.response.publicKeyAlgorithm).toBeUndefined();
expect(response.response.publicKey).toBeUndefined();
Expand Down Expand Up @@ -352,9 +352,9 @@ test('should survive browser extensions that intercept WebAuthn and incorrectly
});
});

await expect(startRegistration(goodOpts1)).resolves;
await expect(startRegistration({ optionsJSON: goodOpts1 })).resolves;

const response = await startRegistration(goodOpts1);
const response = await startRegistration({ optionsJSON: goodOpts1 });

expect(response.response.publicKeyAlgorithm).toBeUndefined();
expect(response.response.publicKey).toBeUndefined();
Expand All @@ -374,7 +374,7 @@ describe('WebAuthnError', () => {
test.skip('should identify abort signal', async () => {
mockNavigatorCreate.mockRejectedValueOnce(AbortError);

const rejected = await expect(startRegistration(goodOpts1)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: goodOpts1 })).rejects;
rejected.toThrow(WebAuthnError);
rejected.toThrow(/abort signal/i);
rejected.toThrow(/AbortError/);
Expand All @@ -397,7 +397,7 @@ describe('WebAuthnError', () => {
},
};

const rejected = await expect(startRegistration(opts)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: opts })).rejects;
rejected.toThrow(WebAuthnError);
rejected.toThrow(/discoverable credentials were required/i);
rejected.toThrow(/no available authenticator supported/i);
Expand All @@ -419,7 +419,7 @@ describe('WebAuthnError', () => {
},
};

const rejected = await expect(startRegistration(opts)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: opts })).rejects;
rejected.toThrow(WebAuthnError);
rejected.toThrow(/user verification was required/i);
rejected.toThrow(/no available authenticator supported/i);
Expand All @@ -438,7 +438,7 @@ describe('WebAuthnError', () => {
test('should identify re-registration attempt', async () => {
mockNavigatorCreate.mockRejectedValueOnce(InvalidStateError);

const rejected = await expect(startRegistration(goodOpts1)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: goodOpts1 })).rejects;
rejected.toThrow(WebAuthnError);
rejected.toThrow(/authenticator/i);
rejected.toThrow(/previously registered/i);
Expand All @@ -465,7 +465,7 @@ describe('WebAuthnError', () => {
);
mockNavigatorCreate.mockRejectedValueOnce(NotAllowedError);

const rejected = await expect(startRegistration(goodOpts1)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: goodOpts1 })).rejects;
rejected.toThrow(Error);
rejected.toThrow(/operation failed/i);
rejected.toHaveProperty('name', 'NotAllowedError');
Expand All @@ -486,7 +486,7 @@ describe('WebAuthnError', () => {
);
mockNavigatorCreate.mockRejectedValueOnce(NotAllowedError);

const rejected = await expect(startRegistration(goodOpts1)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: goodOpts1 })).rejects;
rejected.toThrow(Error);
rejected.toThrow(/sites with TLS certificate errors/i);
rejected.toHaveProperty('name', 'NotAllowedError');
Expand All @@ -506,7 +506,7 @@ describe('WebAuthnError', () => {
pubKeyCredParams: [],
};

const rejected = await expect(startRegistration(opts)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: opts })).rejects;
rejected.toThrow(WebAuthnError);
rejected.toThrow(/pubKeyCredParams/i);
rejected.toThrow(/public-key/i);
Expand All @@ -523,7 +523,7 @@ describe('WebAuthnError', () => {
pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
};

const rejected = await expect(startRegistration(opts)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: opts })).rejects;
rejected.toThrow(WebAuthnError);
rejected.toThrow(/No available authenticator/i);
rejected.toThrow(/pubKeyCredParams/i);
Expand Down Expand Up @@ -554,7 +554,7 @@ describe('WebAuthnError', () => {

mockNavigatorCreate.mockRejectedValueOnce(SecurityError);

const rejected = await expect(startRegistration(goodOpts1)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: goodOpts1 })).rejects;
rejected.toThrowError(WebAuthnError);
rejected.toThrow(/1\.2\.3\.4/);
rejected.toThrow(/invalid domain/i);
Expand All @@ -568,7 +568,7 @@ describe('WebAuthnError', () => {

mockNavigatorCreate.mockRejectedValueOnce(SecurityError);

const rejected = await expect(startRegistration(goodOpts1)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: goodOpts1 })).rejects;
rejected.toThrowError(WebAuthnError);
rejected.toThrow(goodOpts1.rp.id);
rejected.toThrow(/invalid for this domain/i);
Expand All @@ -592,7 +592,7 @@ describe('WebAuthnError', () => {
},
};

const rejected = await expect(startRegistration(opts)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: opts })).rejects;
rejected.toThrowError(WebAuthnError);
rejected.toThrow(/user id/i);
rejected.toThrow(/not between 1 and 64 characters/i);
Expand All @@ -608,7 +608,7 @@ describe('WebAuthnError', () => {
test('should identify potential authenticator issues', async () => {
mockNavigatorCreate.mockRejectedValueOnce(UnknownError);

const rejected = await expect(startRegistration(goodOpts1)).rejects;
const rejected = await expect(startRegistration({ optionsJSON: goodOpts1 })).rejects;
rejected.toThrow(WebAuthnError);
rejected.toThrow(/authenticator/i);
rejected.toThrow(/unable to process the specified options/i);
Expand Down
16 changes: 11 additions & 5 deletions packages/browser/src/methods/startRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ import { identifyRegistrationError } from '../helpers/identifyRegistrationError'
import { WebAuthnAbortService } from '../helpers/webAuthnAbortService';
import { toAuthenticatorAttachment } from '../helpers/toAuthenticatorAttachment';

export type StartRegistrationOpts = {
optionsJSON: PublicKeyCredentialCreationOptionsJSON;
};

/**
* Begin authenticator "registration" via WebAuthn attestation
*
* @param optionsJSON Output from **@simplewebauthn/server**'s `generateRegistrationOptions()`
*/
export async function startRegistration(
optionsJSON: PublicKeyCredentialCreationOptionsJSON,
options: StartRegistrationOpts,
): Promise<RegistrationResponseJSON> {
const { optionsJSON } = options;

if (!browserSupportsWebAuthn()) {
throw new Error('WebAuthn is not supported in this browser');
}
Expand All @@ -39,16 +45,16 @@ export async function startRegistration(
};

// Finalize options
const options: CredentialCreationOptions = { publicKey };
const createOptions: CredentialCreationOptions = { publicKey };
// Set up the ability to cancel this request if the user attempts another
options.signal = WebAuthnAbortService.createNewAbortSignal();
createOptions.signal = WebAuthnAbortService.createNewAbortSignal();

// Wait for the user to complete attestation
let credential;
try {
credential = (await navigator.credentials.create(options)) as RegistrationCredential;
credential = (await navigator.credentials.create(createOptions)) as RegistrationCredential;
} catch (err) {
throw identifyRegistrationError({ error: err as Error, options });
throw identifyRegistrationError({ error: err as Error, options: createOptions });
}

if (!credential) {
Expand Down

0 comments on commit 3b61ec1

Please sign in to comment.