@simplewebauthn/browser
Version:
SimpleWebAuthn for Browsers
125 lines (124 loc) • 6.37 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.startRegistration = startRegistration;
const bufferToBase64URLString_js_1 = require("../helpers/bufferToBase64URLString.js");
const base64URLStringToBuffer_js_1 = require("../helpers/base64URLStringToBuffer.js");
const browserSupportsWebAuthn_js_1 = require("../helpers/browserSupportsWebAuthn.js");
const toPublicKeyCredentialDescriptor_js_1 = require("../helpers/toPublicKeyCredentialDescriptor.js");
const identifyRegistrationError_js_1 = require("../helpers/identifyRegistrationError.js");
const webAuthnAbortService_js_1 = require("../helpers/webAuthnAbortService.js");
const toAuthenticatorAttachment_js_1 = require("../helpers/toAuthenticatorAttachment.js");
/**
* Begin authenticator "registration" via WebAuthn attestation
*
* @param optionsJSON Output from **@simplewebauthn/server**'s `generateRegistrationOptions()`
* @param useAutoRegister (Optional) Try to silently create a passkey with the password manager that the user just signed in with. Defaults to `false`.
*/
async function startRegistration(options) {
// @ts-ignore: Intentionally check for old call structure to warn about improper API call
if (!options.optionsJSON && options.challenge) {
console.warn('startRegistration() was not called correctly. It will try to continue with the provided options, but this call should be refactored to use the expected call structure instead. See https://simplewebauthn.dev/docs/packages/browser#typeerror-cannot-read-properties-of-undefined-reading-challenge for more information.');
// @ts-ignore: Reassign the options, passed in as a positional argument, to the expected variable
options = { optionsJSON: options };
}
const { optionsJSON, useAutoRegister = false } = options;
if (!(0, browserSupportsWebAuthn_js_1.browserSupportsWebAuthn)()) {
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 = {
...optionsJSON,
challenge: (0, base64URLStringToBuffer_js_1.base64URLStringToBuffer)(optionsJSON.challenge),
user: {
...optionsJSON.user,
id: (0, base64URLStringToBuffer_js_1.base64URLStringToBuffer)(optionsJSON.user.id),
},
excludeCredentials: optionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor_js_1.toPublicKeyCredentialDescriptor),
};
// Prepare options for `.create()`
const createOptions = {};
/**
* Try to use conditional create to register a passkey for the user with the password manager
* the user just used to authenticate with. The user won't be shown any prominent UI by the
* browser.
*/
if (useAutoRegister) {
// @ts-ignore: `mediation` doesn't yet exist on CredentialCreationOptions but it's possible as of Sept 2024
createOptions.mediation = 'conditional';
}
// Finalize options
createOptions.publicKey = publicKey;
// Set up the ability to cancel this request if the user attempts another
createOptions.signal = webAuthnAbortService_js_1.WebAuthnAbortService.createNewAbortSignal();
// Wait for the user to complete attestation
let credential;
try {
credential = (await navigator.credentials.create(createOptions));
}
catch (err) {
throw (0, identifyRegistrationError_js_1.identifyRegistrationError)({ error: err, options: createOptions });
}
if (!credential) {
throw new Error('Registration was not completed');
}
const { id, rawId, response, type } = credential;
// Continue to play it safe with `getTransports()` for now, even when L3 types say it's required
let transports = undefined;
if (typeof response.getTransports === 'function') {
transports = response.getTransports();
}
// L3 says this is required, but browser and webview support are still not guaranteed.
let responsePublicKeyAlgorithm = undefined;
if (typeof response.getPublicKeyAlgorithm === 'function') {
try {
responsePublicKeyAlgorithm = response.getPublicKeyAlgorithm();
}
catch (error) {
warnOnBrokenImplementation('getPublicKeyAlgorithm()', error);
}
}
let responsePublicKey = undefined;
if (typeof response.getPublicKey === 'function') {
try {
const _publicKey = response.getPublicKey();
if (_publicKey !== null) {
responsePublicKey = (0, bufferToBase64URLString_js_1.bufferToBase64URLString)(_publicKey);
}
}
catch (error) {
warnOnBrokenImplementation('getPublicKey()', error);
}
}
// L3 says this is required, but browser and webview support are still not guaranteed.
let responseAuthenticatorData;
if (typeof response.getAuthenticatorData === 'function') {
try {
responseAuthenticatorData = (0, bufferToBase64URLString_js_1.bufferToBase64URLString)(response.getAuthenticatorData());
}
catch (error) {
warnOnBrokenImplementation('getAuthenticatorData()', error);
}
}
return {
id,
rawId: (0, bufferToBase64URLString_js_1.bufferToBase64URLString)(rawId),
response: {
attestationObject: (0, bufferToBase64URLString_js_1.bufferToBase64URLString)(response.attestationObject),
clientDataJSON: (0, bufferToBase64URLString_js_1.bufferToBase64URLString)(response.clientDataJSON),
transports,
publicKeyAlgorithm: responsePublicKeyAlgorithm,
publicKey: responsePublicKey,
authenticatorData: responseAuthenticatorData,
},
type,
clientExtensionResults: credential.getClientExtensionResults(),
authenticatorAttachment: (0, toAuthenticatorAttachment_js_1.toAuthenticatorAttachment)(credential.authenticatorAttachment),
};
}
/**
* Visibly warn when we detect an issue related to a passkey provider intercepting WebAuthn API
* calls
*/
function warnOnBrokenImplementation(methodName, cause) {
console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${methodName}. You should report this error to them.\n`, cause);
}