@authsignal/browser
Version:
**[Authsignal](https://www.authsignal.com) provides passwordless step up authentication (Multi-factor Authentication - MFA) that can be placed anywhere within your application. Authsignal also provides a no-code fraud risk rules engine to manage when step
1,132 lines (1,088 loc) • 112 kB
JavaScript
// Unique ID creation requires a high quality random # generator. In the browser we therefore
// require the crypto API and do not support built-in fallback to lower quality random number
// generators (like Math.random()).
let getRandomValues;
const rnds8 = new Uint8Array(16);
function rng() {
// lazy load so that environments that need to polyfill have a chance to do so
if (!getRandomValues) {
// getRandomValues needs to be invoked in a context where "this" is a Crypto implementation.
getRandomValues = typeof crypto !== 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
if (!getRandomValues) {
throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');
}
}
return getRandomValues(rnds8);
}
/**
* Convert array of 16 byte values to UUID string format of the form:
* XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
*/
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 0x100).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
// Note: Be careful editing this code! It's been tuned for performance
// and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
}
const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto);
var native = {
randomUUID
};
function v4(options, buf, offset) {
if (native.randomUUID && !buf && !options) {
return native.randomUUID();
}
options = options || {};
const rnds = options.random || (options.rng || rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
rnds[6] = rnds[6] & 0x0f | 0x40;
rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided
if (buf) {
offset = offset || 0;
for (let i = 0; i < 16; ++i) {
buf[offset + i] = rnds[i];
}
return buf;
}
return unsafeStringify(rnds);
}
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
/* [@simplewebauthn/browser@11.0.0] */
function bufferToBase64URLString(buffer) {
const bytes = new Uint8Array(buffer);
let str = '';
for (const charCode of bytes) {
str += String.fromCharCode(charCode);
}
const base64String = btoa(str);
return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
function base64URLStringToBuffer(base64URLString) {
const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');
const padLength = (4 - (base64.length % 4)) % 4;
const padded = base64.padEnd(base64.length + padLength, '=');
const binary = atob(padded);
const buffer = new ArrayBuffer(binary.length);
const bytes = new Uint8Array(buffer);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return buffer;
}
function browserSupportsWebAuthn() {
return (window?.PublicKeyCredential !== undefined &&
typeof window.PublicKeyCredential === 'function');
}
function toPublicKeyCredentialDescriptor(descriptor) {
const { id } = descriptor;
return {
...descriptor,
id: base64URLStringToBuffer(id),
transports: descriptor.transports,
};
}
function isValidDomain(hostname) {
return (hostname === 'localhost' ||
/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(hostname));
}
class WebAuthnError extends Error {
constructor({ message, code, cause, name, }) {
super(message, { cause });
this.name = name ?? cause.name;
this.code = code;
}
}
function identifyRegistrationError({ error, options, }) {
const { publicKey } = options;
if (!publicKey) {
throw Error('options was missing required publicKey property');
}
if (error.name === 'AbortError') {
if (options.signal instanceof AbortSignal) {
return new WebAuthnError({
message: 'Registration ceremony was sent an abort signal',
code: 'ERROR_CEREMONY_ABORTED',
cause: error,
});
}
}
else if (error.name === 'ConstraintError') {
if (publicKey.authenticatorSelection?.requireResidentKey === true) {
return new WebAuthnError({
message: 'Discoverable credentials were required but no available authenticator supported it',
code: 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT',
cause: error,
});
}
else if (options.mediation === 'conditional' &&
publicKey.authenticatorSelection?.userVerification === 'required') {
return new WebAuthnError({
message: 'User verification was required during automatic registration but it could not be performed',
code: 'ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE',
cause: error,
});
}
else if (publicKey.authenticatorSelection?.userVerification === 'required') {
return new WebAuthnError({
message: 'User verification was required but no available authenticator supported it',
code: 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT',
cause: error,
});
}
}
else if (error.name === 'InvalidStateError') {
return new WebAuthnError({
message: 'The authenticator was previously registered',
code: 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED',
cause: error,
});
}
else if (error.name === 'NotAllowedError') {
return new WebAuthnError({
message: error.message,
code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
cause: error,
});
}
else if (error.name === 'NotSupportedError') {
const validPubKeyCredParams = publicKey.pubKeyCredParams.filter((param) => param.type === 'public-key');
if (validPubKeyCredParams.length === 0) {
return new WebAuthnError({
message: 'No entry in pubKeyCredParams was of type "public-key"',
code: 'ERROR_MALFORMED_PUBKEYCREDPARAMS',
cause: error,
});
}
return new WebAuthnError({
message: 'No available authenticator supported any of the specified pubKeyCredParams algorithms',
code: 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG',
cause: error,
});
}
else if (error.name === 'SecurityError') {
const effectiveDomain = window.location.hostname;
if (!isValidDomain(effectiveDomain)) {
return new WebAuthnError({
message: `${window.location.hostname} is an invalid domain`,
code: 'ERROR_INVALID_DOMAIN',
cause: error,
});
}
else if (publicKey.rp.id !== effectiveDomain) {
return new WebAuthnError({
message: `The RP ID "${publicKey.rp.id}" is invalid for this domain`,
code: 'ERROR_INVALID_RP_ID',
cause: error,
});
}
}
else if (error.name === 'TypeError') {
if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) {
return new WebAuthnError({
message: 'User ID was not between 1 and 64 characters',
code: 'ERROR_INVALID_USER_ID_LENGTH',
cause: error,
});
}
}
else if (error.name === 'UnknownError') {
return new WebAuthnError({
message: 'The authenticator was unable to process the specified options, or could not create a new credential',
code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',
cause: error,
});
}
return error;
}
class BaseWebAuthnAbortService {
createNewAbortSignal() {
if (this.controller) {
const abortError = new Error('Cancelling existing WebAuthn API call for new one');
abortError.name = 'AbortError';
this.controller.abort(abortError);
}
const newController = new AbortController();
this.controller = newController;
return newController.signal;
}
cancelCeremony() {
if (this.controller) {
const abortError = new Error('Manually cancelling existing WebAuthn API call');
abortError.name = 'AbortError';
this.controller.abort(abortError);
this.controller = undefined;
}
}
}
const WebAuthnAbortService = new BaseWebAuthnAbortService();
const attachments = ['cross-platform', 'platform'];
function toAuthenticatorAttachment(attachment) {
if (!attachment) {
return;
}
if (attachments.indexOf(attachment) < 0) {
return;
}
return attachment;
}
async function startRegistration(options) {
const { optionsJSON, useAutoRegister = false } = options;
if (!browserSupportsWebAuthn()) {
throw new Error('WebAuthn is not supported in this browser');
}
const publicKey = {
...optionsJSON,
challenge: base64URLStringToBuffer(optionsJSON.challenge),
user: {
...optionsJSON.user,
id: base64URLStringToBuffer(optionsJSON.user.id),
},
excludeCredentials: optionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor),
};
const createOptions = {};
if (useAutoRegister) {
createOptions.mediation = 'conditional';
}
createOptions.publicKey = publicKey;
createOptions.signal = WebAuthnAbortService.createNewAbortSignal();
let credential;
try {
credential = (await navigator.credentials.create(createOptions));
}
catch (err) {
throw identifyRegistrationError({ error: err, options: createOptions });
}
if (!credential) {
throw new Error('Registration was not completed');
}
const { id, rawId, response, type } = credential;
let transports = undefined;
if (typeof response.getTransports === 'function') {
transports = response.getTransports();
}
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 = bufferToBase64URLString(_publicKey);
}
}
catch (error) {
warnOnBrokenImplementation('getPublicKey()', error);
}
}
let responseAuthenticatorData;
if (typeof response.getAuthenticatorData === 'function') {
try {
responseAuthenticatorData = bufferToBase64URLString(response.getAuthenticatorData());
}
catch (error) {
warnOnBrokenImplementation('getAuthenticatorData()', error);
}
}
return {
id,
rawId: bufferToBase64URLString(rawId),
response: {
attestationObject: bufferToBase64URLString(response.attestationObject),
clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
transports,
publicKeyAlgorithm: responsePublicKeyAlgorithm,
publicKey: responsePublicKey,
authenticatorData: responseAuthenticatorData,
},
type,
clientExtensionResults: credential.getClientExtensionResults(),
authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),
};
}
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);
}
function browserSupportsWebAuthnAutofill() {
if (!browserSupportsWebAuthn()) {
return new Promise((resolve) => resolve(false));
}
const globalPublicKeyCredential = window
.PublicKeyCredential;
if (globalPublicKeyCredential.isConditionalMediationAvailable === undefined) {
return new Promise((resolve) => resolve(false));
}
return globalPublicKeyCredential.isConditionalMediationAvailable();
}
function identifyAuthenticationError({ error, options, }) {
const { publicKey } = options;
if (!publicKey) {
throw Error('options was missing required publicKey property');
}
if (error.name === 'AbortError') {
if (options.signal instanceof AbortSignal) {
return new WebAuthnError({
message: 'Authentication ceremony was sent an abort signal',
code: 'ERROR_CEREMONY_ABORTED',
cause: error,
});
}
}
else if (error.name === 'NotAllowedError') {
return new WebAuthnError({
message: error.message,
code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
cause: error,
});
}
else if (error.name === 'SecurityError') {
const effectiveDomain = window.location.hostname;
if (!isValidDomain(effectiveDomain)) {
return new WebAuthnError({
message: `${window.location.hostname} is an invalid domain`,
code: 'ERROR_INVALID_DOMAIN',
cause: error,
});
}
else if (publicKey.rpId !== effectiveDomain) {
return new WebAuthnError({
message: `The RP ID "${publicKey.rpId}" is invalid for this domain`,
code: 'ERROR_INVALID_RP_ID',
cause: error,
});
}
}
else if (error.name === 'UnknownError') {
return new WebAuthnError({
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,
});
}
return error;
}
async function startAuthentication(options) {
const { optionsJSON, useBrowserAutofill = false, verifyBrowserAutofillInput = true, } = options;
if (!browserSupportsWebAuthn()) {
throw new Error('WebAuthn is not supported in this browser');
}
let allowCredentials;
if (optionsJSON.allowCredentials?.length !== 0) {
allowCredentials = optionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);
}
const publicKey = {
...optionsJSON,
challenge: base64URLStringToBuffer(optionsJSON.challenge),
allowCredentials,
};
const getOptions = {};
if (useBrowserAutofill) {
if (!(await browserSupportsWebAuthnAutofill())) {
throw Error('Browser does not support WebAuthn autofill');
}
const eligibleInputs = document.querySelectorAll("input[autocomplete$='webauthn']");
if (eligibleInputs.length < 1 && verifyBrowserAutofillInput) {
throw Error('No <input> with "webauthn" as the only or last value in its `autocomplete` attribute was detected');
}
getOptions.mediation = 'conditional';
publicKey.allowCredentials = [];
}
getOptions.publicKey = publicKey;
getOptions.signal = WebAuthnAbortService.createNewAbortSignal();
let credential;
try {
credential = (await navigator.credentials.get(getOptions));
}
catch (err) {
throw identifyAuthenticationError({ error: err, options: getOptions });
}
if (!credential) {
throw new Error('Authentication was not completed');
}
const { id, rawId, response, type } = credential;
let userHandle = undefined;
if (response.userHandle) {
userHandle = bufferToBase64URLString(response.userHandle);
}
return {
id,
rawId: bufferToBase64URLString(rawId),
response: {
authenticatorData: bufferToBase64URLString(response.authenticatorData),
clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
signature: bufferToBase64URLString(response.signature),
userHandle,
},
type,
clientExtensionResults: credential.getClientExtensionResults(),
authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),
};
}
function setCookie(_a) {
var name = _a.name, value = _a.value, expire = _a.expire, domain = _a.domain, secure = _a.secure;
var expireString = expire === Infinity ? " expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + expire;
document.cookie =
encodeURIComponent(name) +
"=" +
value +
"; path=/;" +
expireString +
(domain ? "; domain=" + domain : "") +
(secure ? "; secure" : "");
}
function getCookieDomain() {
return document.location.hostname.replace("www.", "");
}
function getCookie(name) {
if (!name) {
return null;
}
return (decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(name).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null);
}
function handleErrorResponse(errorResponse) {
var _a;
var error = (_a = errorResponse.errorDescription) !== null && _a !== void 0 ? _a : errorResponse.error;
console.error(error);
return {
error: error,
};
}
function handleApiResponse(response) {
var _a;
if (response && typeof response === "object" && "error" in response) {
var error = (_a = response.errorDescription) !== null && _a !== void 0 ? _a : response.error;
console.error(error);
return {
error: error,
};
}
else if (response &&
typeof response === "object" &&
"accessToken" in response &&
typeof response.accessToken === "string") {
var accessToken = response.accessToken, data = __rest(response, ["accessToken"]);
return {
data: __assign(__assign({}, data), { token: accessToken }),
};
}
else {
return {
data: response,
};
}
}
function handleWebAuthnError(error) {
var _a, _b;
if (error instanceof WebAuthnError && error.code === "ERROR_INVALID_RP_ID") {
var rpId = ((_b = (_a = error.message) === null || _a === void 0 ? void 0 : _a.match(/"([^"]*)"/)) === null || _b === void 0 ? void 0 : _b[1]) || "";
console.error("[Authsignal] The Relying Party ID \"".concat(rpId, "\" is invalid for this domain.\n To learn more, visit https://docs.authsignal.com/scenarios/passkeys-prebuilt-ui#defining-the-relying-party"));
}
}
var AuthsignalWindowMessage;
(function (AuthsignalWindowMessage) {
AuthsignalWindowMessage["AUTHSIGNAL_CLOSE_POPUP"] = "AUTHSIGNAL_CLOSE_POPUP";
})(AuthsignalWindowMessage || (AuthsignalWindowMessage = {}));
function buildHeaders(_a) {
var token = _a.token, tenantId = _a.tenantId;
var authorizationHeader = token ? "Bearer ".concat(token) : "Basic ".concat(window.btoa(encodeURIComponent(tenantId)));
return {
"Content-Type": "application/json",
Authorization: authorizationHeader,
};
}
function handleTokenExpired(_a) {
var response = _a.response, onTokenExpired = _a.onTokenExpired;
if ("error" in response && response.errorCode === "expired_token" && onTokenExpired) {
onTokenExpired();
}
}
var PasskeyApiClient = /** @class */ (function () {
function PasskeyApiClient(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.tenantId = tenantId;
this.baseUrl = baseUrl;
this.onTokenExpired = onTokenExpired;
}
PasskeyApiClient.prototype.registrationOptions = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var body, response, responseJson;
var token = _b.token, username = _b.username, authenticatorAttachment = _b.authenticatorAttachment;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
body = Boolean(authenticatorAttachment)
? { username: username, authenticatorAttachment: authenticatorAttachment }
: { username: username };
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/passkey/registration-options"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
body: JSON.stringify(body),
})];
case 1:
response = _c.sent();
return [4 /*yield*/, response.json()];
case 2:
responseJson = _c.sent();
handleTokenExpired({ response: responseJson, onTokenExpired: this.onTokenExpired });
return [2 /*return*/, responseJson];
}
});
});
};
PasskeyApiClient.prototype.authenticationOptions = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var body, response, responseJson;
var token = _b.token, challengeId = _b.challengeId;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
body = { challengeId: challengeId };
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/passkey/authentication-options"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
body: JSON.stringify(body),
})];
case 1:
response = _c.sent();
return [4 /*yield*/, response.json()];
case 2:
responseJson = _c.sent();
handleTokenExpired({ response: responseJson, onTokenExpired: this.onTokenExpired });
return [2 /*return*/, responseJson];
}
});
});
};
PasskeyApiClient.prototype.addAuthenticator = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var body, response, responseJson;
var token = _b.token, challengeId = _b.challengeId, registrationCredential = _b.registrationCredential;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
body = {
challengeId: challengeId,
registrationCredential: registrationCredential,
};
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/passkey"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
body: JSON.stringify(body),
})];
case 1:
response = _c.sent();
return [4 /*yield*/, response.json()];
case 2:
responseJson = _c.sent();
handleTokenExpired({ response: responseJson, onTokenExpired: this.onTokenExpired });
return [2 /*return*/, responseJson];
}
});
});
};
PasskeyApiClient.prototype.verify = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var body, response, responseJson;
var token = _b.token, challengeId = _b.challengeId, authenticationCredential = _b.authenticationCredential, deviceId = _b.deviceId;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
body = { challengeId: challengeId, authenticationCredential: authenticationCredential, deviceId: deviceId };
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/verify/passkey"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
body: JSON.stringify(body),
})];
case 1:
response = _c.sent();
return [4 /*yield*/, response.json()];
case 2:
responseJson = _c.sent();
handleTokenExpired({ response: responseJson, onTokenExpired: this.onTokenExpired });
return [2 /*return*/, responseJson];
}
});
});
};
PasskeyApiClient.prototype.getPasskeyAuthenticator = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response;
var credentialIds = _b.credentialIds;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/passkey?credentialIds=").concat(credentialIds), {
method: "GET",
headers: buildHeaders({ tenantId: this.tenantId }),
})];
case 1:
response = _c.sent();
if (!response.ok) {
throw new Error(response.statusText);
}
return [2 /*return*/, response.json()];
}
});
});
};
PasskeyApiClient.prototype.challenge = function (action) {
return __awaiter(this, void 0, void 0, function () {
var response, responseJson;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/challenge"), {
method: "POST",
headers: buildHeaders({ tenantId: this.tenantId }),
body: JSON.stringify({ action: action }),
})];
case 1:
response = _a.sent();
return [4 /*yield*/, response.json()];
case 2:
responseJson = _a.sent();
handleTokenExpired({ response: responseJson, onTokenExpired: this.onTokenExpired });
return [2 /*return*/, responseJson];
}
});
});
};
return PasskeyApiClient;
}());
var TokenCache = /** @class */ (function () {
function TokenCache() {
this.token = null;
}
TokenCache.prototype.handleTokenNotSetError = function () {
var error = "A token has not been set. Call 'setToken' first.";
var errorCode = "TOKEN_NOT_SET";
console.error("Error: ".concat(error));
return {
error: errorCode,
errorDescription: error,
};
};
TokenCache.shared = new TokenCache();
return TokenCache;
}());
var autofillRequestPending = false;
var Passkey = /** @class */ (function () {
function Passkey(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, anonymousId = _a.anonymousId, onTokenExpired = _a.onTokenExpired;
this.passkeyLocalStorageKey = "as_user_passkey_map";
this.cache = TokenCache.shared;
this.api = new PasskeyApiClient({ baseUrl: baseUrl, tenantId: tenantId, onTokenExpired: onTokenExpired });
this.anonymousId = anonymousId;
}
Passkey.prototype.signUp = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var userToken, optionsInput, optionsResponse, registrationResponse, addAuthenticatorResponse, e_1;
var username = _b.username, displayName = _b.displayName, token = _b.token, _c = _b.authenticatorAttachment, authenticatorAttachment = _c === void 0 ? "platform" : _c, _d = _b.useAutoRegister, useAutoRegister = _d === void 0 ? false : _d;
return __generator(this, function (_e) {
switch (_e.label) {
case 0:
userToken = token !== null && token !== void 0 ? token : this.cache.token;
if (!userToken) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
optionsInput = {
username: username,
displayName: displayName,
token: userToken,
authenticatorAttachment: authenticatorAttachment,
};
return [4 /*yield*/, this.api.registrationOptions(optionsInput)];
case 1:
optionsResponse = _e.sent();
if ("error" in optionsResponse) {
return [2 /*return*/, handleErrorResponse(optionsResponse)];
}
_e.label = 2;
case 2:
_e.trys.push([2, 5, , 6]);
return [4 /*yield*/, startRegistration({ optionsJSON: optionsResponse.options, useAutoRegister: useAutoRegister })];
case 3:
registrationResponse = _e.sent();
return [4 /*yield*/, this.api.addAuthenticator({
challengeId: optionsResponse.challengeId,
registrationCredential: registrationResponse,
token: userToken,
})];
case 4:
addAuthenticatorResponse = _e.sent();
if ("error" in addAuthenticatorResponse) {
return [2 /*return*/, handleErrorResponse(addAuthenticatorResponse)];
}
if (addAuthenticatorResponse.isVerified) {
this.storeCredentialAgainstDevice(__assign(__assign({}, registrationResponse), { userId: addAuthenticatorResponse.userId }));
}
if (addAuthenticatorResponse.accessToken) {
this.cache.token = addAuthenticatorResponse.accessToken;
}
return [2 /*return*/, {
data: {
token: addAuthenticatorResponse.accessToken,
userAuthenticator: addAuthenticatorResponse.userAuthenticator,
registrationResponse: registrationResponse,
},
}];
case 5:
e_1 = _e.sent();
autofillRequestPending = false;
handleWebAuthnError(e_1);
throw e_1;
case 6: return [2 /*return*/];
}
});
});
};
Passkey.prototype.signIn = function (params) {
return __awaiter(this, void 0, void 0, function () {
var challengeResponse, _a, optionsResponse, authenticationResponse, verifyResponse, token, userId, userAuthenticatorId, username, userDisplayName, isVerified, e_2;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if ((params === null || params === void 0 ? void 0 : params.token) && params.autofill) {
throw new Error("autofill is not supported when providing a token");
}
if ((params === null || params === void 0 ? void 0 : params.action) && params.token) {
throw new Error("action is not supported when providing a token");
}
if (params === null || params === void 0 ? void 0 : params.autofill) {
if (autofillRequestPending) {
return [2 /*return*/, {}];
}
else {
autofillRequestPending = true;
}
}
if (!(params === null || params === void 0 ? void 0 : params.action)) return [3 /*break*/, 2];
return [4 /*yield*/, this.api.challenge(params.action)];
case 1:
_a = _b.sent();
return [3 /*break*/, 3];
case 2:
_a = null;
_b.label = 3;
case 3:
challengeResponse = _a;
if (challengeResponse && "error" in challengeResponse) {
autofillRequestPending = false;
return [2 /*return*/, handleErrorResponse(challengeResponse)];
}
return [4 /*yield*/, this.api.authenticationOptions({
token: params === null || params === void 0 ? void 0 : params.token,
challengeId: challengeResponse === null || challengeResponse === void 0 ? void 0 : challengeResponse.challengeId,
})];
case 4:
optionsResponse = _b.sent();
if ("error" in optionsResponse) {
autofillRequestPending = false;
return [2 /*return*/, handleErrorResponse(optionsResponse)];
}
_b.label = 5;
case 5:
_b.trys.push([5, 8, , 9]);
return [4 /*yield*/, startAuthentication({
optionsJSON: optionsResponse.options,
useBrowserAutofill: params === null || params === void 0 ? void 0 : params.autofill,
})];
case 6:
authenticationResponse = _b.sent();
if (params === null || params === void 0 ? void 0 : params.onVerificationStarted) {
params.onVerificationStarted();
}
return [4 /*yield*/, this.api.verify({
challengeId: optionsResponse.challengeId,
authenticationCredential: authenticationResponse,
token: params === null || params === void 0 ? void 0 : params.token,
deviceId: this.anonymousId,
})];
case 7:
verifyResponse = _b.sent();
if ("error" in verifyResponse) {
autofillRequestPending = false;
return [2 /*return*/, handleErrorResponse(verifyResponse)];
}
if (verifyResponse.isVerified) {
this.storeCredentialAgainstDevice(__assign(__assign({}, authenticationResponse), { userId: verifyResponse.userId }));
}
if (verifyResponse.accessToken) {
this.cache.token = verifyResponse.accessToken;
}
token = verifyResponse.accessToken, userId = verifyResponse.userId, userAuthenticatorId = verifyResponse.userAuthenticatorId, username = verifyResponse.username, userDisplayName = verifyResponse.userDisplayName, isVerified = verifyResponse.isVerified;
autofillRequestPending = false;
return [2 /*return*/, {
data: {
isVerified: isVerified,
token: token,
userId: userId,
userAuthenticatorId: userAuthenticatorId,
username: username,
displayName: userDisplayName,
authenticationResponse: authenticationResponse,
},
}];
case 8:
e_2 = _b.sent();
autofillRequestPending = false;
handleWebAuthnError(e_2);
throw e_2;
case 9: return [2 /*return*/];
}
});
});
};
Passkey.prototype.isAvailableOnDevice = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var storedCredentials, credentialsMap, credentialIds;
var _d;
var userId = _b.userId;
return __generator(this, function (_e) {
switch (_e.label) {
case 0:
if (!userId) {
throw new Error("userId is required");
}
storedCredentials = localStorage.getItem(this.passkeyLocalStorageKey);
if (!storedCredentials) {
return [2 /*return*/, false];
}
credentialsMap = JSON.parse(storedCredentials);
credentialIds = (_d = credentialsMap[userId]) !== null && _d !== void 0 ? _d : [];
if (credentialIds.length === 0) {
return [2 /*return*/, false];
}
_e.label = 1;
case 1:
_e.trys.push([1, 3, , 4]);
return [4 /*yield*/, this.api.getPasskeyAuthenticator({ credentialIds: credentialIds })];
case 2:
_e.sent();
return [2 /*return*/, true];
case 3:
_e.sent();
return [2 /*return*/, false];
case 4: return [2 /*return*/];
}
});
});
};
Passkey.prototype.storeCredentialAgainstDevice = function (_a) {
var id = _a.id, authenticatorAttachment = _a.authenticatorAttachment, _b = _a.userId, userId = _b === void 0 ? "" : _b;
if (authenticatorAttachment === "cross-platform") {
return;
}
var storedCredentials = localStorage.getItem(this.passkeyLocalStorageKey);
var credentialsMap = storedCredentials ? JSON.parse(storedCredentials) : {};
if (credentialsMap[userId]) {
if (!credentialsMap[userId].includes(id)) {
credentialsMap[userId].push(id);
}
}
else {
credentialsMap[userId] = [id];
}
localStorage.setItem(this.passkeyLocalStorageKey, JSON.stringify(credentialsMap));
};
return Passkey;
}());
var DEFAULT_WIDTH$1 = 400;
var DEFAULT_HEIGHT = 500;
var WindowHandler = /** @class */ (function () {
function WindowHandler() {
this.windowRef = null;
}
WindowHandler.prototype.show = function (_a) {
var url = _a.url, _b = _a.width, width = _b === void 0 ? DEFAULT_WIDTH$1 : _b, _c = _a.height, height = _c === void 0 ? DEFAULT_HEIGHT : _c;
var windowRef = openWindow({ url: url, width: width, height: height, win: window });
if (!windowRef) {
throw new Error("Window is not initialized");
}
this.windowRef = windowRef;
return windowRef;
};
WindowHandler.prototype.close = function () {
if (!this.windowRef) {
throw new Error("Window is not initialized");
}
this.windowRef.close();
};
return WindowHandler;
}());
function openWindow(_a) {
var url = _a.url, width = _a.width, height = _a.height, win = _a.win;
if (!win.top) {
return null;
}
var y = win.top.outerHeight / 2 + win.top.screenY - height / 2;
var x = win.top.outerWidth / 2 + win.top.screenX - width / 2;
return window.open(url, "", "toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=".concat(width, ", height=").concat(height, ", top=").concat(y, ", left=").concat(x));
}
const not = {
inert: ':not([inert]):not([inert] *)',
negTabIndex: ':not([tabindex^="-"])',
disabled: ':not(:disabled)',
};
var focusableSelectors = [
`a[href]${not.inert}${not.negTabIndex}`,
`area[href]${not.inert}${not.negTabIndex}`,
`input:not([type="hidden"]):not([type="radio"])${not.inert}${not.negTabIndex}${not.disabled}`,
`input[type="radio"]${not.inert}${not.negTabIndex}${not.disabled}`,
`select${not.inert}${not.negTabIndex}${not.disabled}`,
`textarea${not.inert}${not.negTabIndex}${not.disabled}`,
`button${not.inert}${not.negTabIndex}${not.disabled}`,
`details${not.inert} > summary:first-of-type${not.negTabIndex}`,
// Discard until Firefox supports `:has()`
// See: https://github.com/KittyGiraudel/focusable-selectors/issues/12
// `details:not(:has(> summary))${not.inert}${not.negTabIndex}`,
`iframe${not.inert}${not.negTabIndex}`,
`audio[controls]${not.inert}${not.negTabIndex}`,
`video[controls]${not.inert}${not.negTabIndex}`,
`[contenteditable]${not.inert}${not.negTabIndex}`,
`[tabindex]${not.inert}${not.negTabIndex}`,
];
/**
* Set the focus to the first element with `autofocus` with the element or the
* element itself.
*/
function moveFocusToDialog(el) {
const focused = (el.querySelector('[autofocus]') || el);
focused.focus();
}
/**
* Get the first and last focusable elements in a given tree.
*/
function getFocusableEdges(el) {
// Check for a focusable element within the subtree of `el`.
const first = findFocusableElement(el, true);
// Only if we find the first element do we need to look for the last one. If
// there’s no last element, we set `last` as a reference to `first` so that
// the returned array is always of length 2.
const last = first ? findFocusableElement(el, false) || first : null;
return [first, last];
}
/**
* Find the first focusable element inside the given node if `forward` is truthy
* or the last focusable element otherwise.
*/
function findFocusableElement(node, forward) {
// If we’re walking forward, check if this node is focusable, and return it
// immediately if it is.
if (forward && isFocusable(node))
return node;
// We should only search the subtree of this node if it can have focusable
// children.
if (canHaveFocusableChildren(node)) {
// Start walking the DOM tree, looking for focusable elements.
// Case 1: If this node has a shadow root, search it recursively.
if (node.shadowRoot) {
// Descend into this subtree.
let next = getNextChildEl(node.shadowRoot, forward);
// Traverse siblings, searching the subtree of each one
// for focusable elements.
while (next) {
const focusableEl = findFocusableElement(next, forward);
if (focusableEl)
return focusableEl;
next = getNextSiblingEl(next, forward);
}
}
// Case 2: If this node is a slot for a Custom Element, search its assigned
// nodes recursively.
else if (node.localName === 'slot') {
const assignedElements = node.assignedElements({
flatten: true,
});
if (!forward)
assignedElements.reverse();
for (const assignedElement of assignedElements) {
const focusableEl = findFocusableElement(assignedElement, forward);
if (focusableEl)
return focusableEl;
}
}
// Case 3: this is a regular Light DOM node. Search its subtree.
else {
// Descend into this subtree.
let next = getNextChildEl(node, forward);
// Traverse siblings, searching the subtree of each one
// for focusable elements.
while (next) {
const focusableEl = findFocusableElement(next, forward);
if (focusableEl)
ret