@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
2,481 lines • 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)
return focusableEl;
next = getNextSiblingEl(next, forward);
}
}
}
// If we’re walking backward, we want to check the node’s entire subtree
// before checking the node itself. If this node is focusable, return it.
if (!forward && isFocusable(node))
return node;
return null;
}
function getNextChildEl(node, forward) {
return forward ? node.firstElementChild : node.lastElementChild;
}
function getNextSiblingEl(el, forward) {
return forward ? el.nextElementSibling : el.previousElementSibling;
}
/**
* Determine if an element is hidden from the user.
*/
const isHidden = (el) => {
// Browsers hide all non-<summary> descendants of closed <details> elements
// from user interaction, but those non-<summary> elements may still match our
// focusable-selectors and may still have dimensions, so we need a special
// case to ignore them.
if (el.matches('details:not([open]) *') &&
!el.matches('details>summary:first-of-type'))
return true;
// If this element has no painted dimensions, it's hidden.
return !(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
};
/**
* Determine if an element is focusable and has user-visible painted dimensions.
*/
const isFocusable = (el) => {
// A shadow host that delegates focus will never directly receive focus,
// even with `tabindex=0`. Consider our <fancy-button> custom element, which
// delegates focus to its shadow button:
//
// <fancy-button tabindex="0">
// #shadow-root
// <button><slot></slot></button>
// </fancy-button>
//
// The browser acts as as if there is only one focusable element – the shadow
// button. Our library should behave the same way.
if (el.shadowRoot?.delegatesFocus)
return false;
return el.matches(focusableSelectors.join(',')) && !isHidden(el);
};
/**
* Determine if an element can have focusable children. Useful for bailing out
* early when walking the DOM tree.
* @example
* This div is inert, so none of its children can be focused, even though they
* meet our criteria for what is focusable. Once we check the div, we can skip
* the rest of the subtree.
* ```html
* <div inert>
* <button>Button</button>
* <a href="#">Link</a>
* </div>
* ```
*/
function canHaveFocusableChildren(el) {
// The browser will never send focus into a Shadow DOM if the host element
// has a negative tabindex. This applies to both slotted Light DOM Shadow DOM
// children
if (el.shadowRoot && el.getAttribute('tabindex') === '-1')
return false;
// Elemments matching this selector are either hidden entirely from the user,
// or are visible but unavailable for interaction. Their descentants can never
// receive focus.
return !el.matches(':disabled,[hidden],[inert]');
}
/**
* Get the active element, accounting for Shadow DOM subtrees.
* @author Cory LaViska
* @see: https://www.abeautifulsite.net/posts/finding-the-active-element-in-a-shadow-root/
*/
function getActiveElement(root = document) {
const activeEl = root.activeElement;
if (!activeEl)
return null;
// If there’s a shadow root, recursively find the active element within it.
// If the recursive call returns null, return the active element
// of the top-level Document.
if (activeEl.shadowRoot)
return getActiveElement(activeEl.shadowRoot) || document.activeElement;
// If not, we can just return the active element
return activeEl;
}
/**
* Trap the focus inside the given element
*/
function trapTabKey(el, event) {
const [firstFocusableChild, lastFocusableChild] = getFocusableEdges(el);
// If there are no focusable children in the dialog, prevent the user from
// tabbing out of it
if (!firstFocusableChild)
return event.preventDefault();
const activeElement = getActiveElement();
// If the SHIFT key is pressed while tabbing (moving backwards) and the
// currently focused item is the first one, move the focus to the last
// focusable item from the dialog element
if (event.shiftKey && activeElement === firstFocusableChild) {
// @ts-ignore: we know that `lastFocusableChild` is not null here
lastFocusableChild.focus();
event.preventDefault();
}
// If the SHIFT key is not pressed (moving forwards) and the currently focused
// item is the last one, move the focus to the first focusable item from the
// dialog element
else if (!event.shiftKey && activeElement === lastFocusableChild) {
firstFocusableChild.focus();
event.preventDefault();
}
}
class A11yDialog {
$el;
id;
previouslyFocused;
shown;
constructor(element) {
this.$el = element;
this.id = this.$el.getAttribute('data-a11y-dialog') || this.$el.id;
this.previouslyFocused = null;
this.shown = false;
this.maintainFocus = this.maintainFocus.bind(this);
this.bindKeypress = this.bindKeypress.bind(this);
this.handleTriggerClicks = this.handleTriggerClicks.bind(this);
this.show = this.show.bind(this);
this.hide = this.hide.bind(this);
this.$el.setAttribute('aria-hidden', 'true');
this.$el.setAttribute('aria-modal', 'true');
this.$el.setAttribute('tabindex', '-1');
if (!this.$el.hasAttribute('role')) {
this.$el.setAttribute('role', 'dialog');
}
document.addEventListener('click', this.handleTriggerClicks, true);
}
/**
* Destroy the current instance (after making sure the dialog has been hidden)
* and remove all associated listeners from dialog openers and closers
*/
destroy() {
// Hide the dialog to avoid destroying an open instance
this.hide();
// Remove the click event delegates for our openers and closers
document.removeEventListener('click', this.handleTriggerClicks, true);
// Clone and replace the dialog element to prevent memory leaks caused by
// event listeners that the author might not have cleaned up.
this.$el.replaceWith(this.$el.cloneNode(true));
// Dispatch a `destroy` event
this.fire('destroy');
return this;
}
/**
* Show the dialog element, trap the current focus within it, listen for some
* specific key presses and fire all registered callbacks for `show` event
*/
show(event) {
// If the dialog is already open, abort
if (this.shown)
return this;
// Keep a reference to the currently focused element to be able to restore
// it later
this.shown = true;
this.$el.removeAttribute('aria-hidden');
this.previouslyFocused = getActiveElement();
// Due to a long lasting bug in Safari, clicking an interactive element
// (like a <button>) does *not* move the focus to that element, which means
// `document.activeElement` is whatever element is currently focused (like
// an <input>), or the <body> element otherwise. We can work around that
// problem by checking whether the focused element is the <body>, and if it,
// store the click event target.
// See: https://bugs.webkit.org/show_bug.cgi?id=22261
if (this.previouslyFocused?.tagName === 'BODY' && event?.target) {
this.previouslyFocused = event.target;
}
// Set the focus to the dialog element
// See: https://github.com/KittyGiraudel/a11y-dialog/pull/583
if (event?.type === 'focus') {
this.maintainFocus(event);
}
else {
moveFocusToDialog(this.$el);
}
// Bind a focus event listener to the body element to make sure the focus
// stays trapped inside the dialog while open, and start listening for some
// specific key presses (TAB and ESC)
document.body.addEventListener('focus', this.maintainFocus, true);
this.$el.addEventListener('keydown', this.bindKeypress, true);
// Dispatch a `show` event
this.fire('show', event);
return this;
}
/**
* Hide the dialog element, restore the focus to the previously active
* element, stop listening for some specific key presses and fire all
* registered callbacks for `hide` event
*/
hide(event) {
// If the dialog is already closed, abort
if (!this.shown)
return this;
this.shown = false;
this.$el.setAttribute('aria-hidden', 'true');
this.previouslyFocused?.focus?.();
// Remove the focus event listener to the body element and stop listening
// for specific key presses
document.body.removeEventListener('focus', this.maintainFocus, true);
this.$el.removeEventListener('keydown', this.bindKeypress, true);
// Dispatch a `hide` event
this.fire('hide', event);
return this;
}
/**
* Register a new callback for the given event type
*/
on(type, handler, options) {
this.$el.addEventListener(type, handler, options);
return this;
}
/**
* Unregister an existing callback for the given event type
*/
off(type, handler, options) {
this.$el.removeEventListener(type, handler, options);
return this;
}
/**
* Dispatch a custom event from the DOM element associated with this dialog.
* This allows authors to listen for and respond to the events in their own
* code
*/
fire(type, event) {
this.$el.dispatchEvent(new CustomEvent(type, {
detail: event,
cancelable: true,
}));
}
/**
* Add a delegated event listener for when elememts that open or close the
* dialog are clicked, and call `show` or `hide`, respectively
*/
handleTriggerClicks(event) {
const target = event.target;
// We use `.closest(..)` and not `.matches(..)` here so that clicking
// an element nested within a dialog opener does cause the dialog to open
if (target.closest(`[data-a11y-dialog-show="${this.id}"]`)) {
this.show(event);
}
if (target.closest(`[data-a11y-dialog-hide="${this.id}"]`) ||
(target.closest('[data-a11y-dialog-hide]') &&
target.closest('[aria-modal="true"]') === this.$el)) {
this.hide(event);
}
}
/**
* Private event handler used when listening to some specific key presses
* (namely ESC and TAB)
*/
bindKeypress(event) {
// This is an escape hatch in case there are nested open dialogs, so that
// only the top most dialog gets interacted with
if (document.activeElement?.closest('[aria-modal="true"]') !== this.$el) {
return;
}
let hasOpenPopover = false;
try {
hasOpenPopover = !!this.$el.querySelector('[popover]:not([popover="manual"]):popover-open');
}
catch {
// Run that DOM query in a try/catch because not all browsers support the
// `:popover-open` selector, which would cause the whole expression to
// fail
// See: https://caniuse.com/mdn-css_selectors_popover-open
// See: https://github.com/KittyGiraudel/a11y-dialog/pull/578#discussion_r1343215149
}
// If the dialog is shown and the ESC key is pressed, prevent any further
// effects from the ESC key and hide the dialog, unless:
// - its role is `alertdialog`, which means it should be modal
// - or it contains an open popover, in which case ESC should close it
if (event.key === 'Escape' &&
this.$el.getAttribute('role') !== 'alertdialog' &&
!hasOpenPopover) {
event.preventDefault();
this.hide(event);
}
// If the dialog is shown and the TAB key is pressed, make sure the focus
// stays trapped within the dialog element
if (event.key === 'Tab') {
trapTabKey(this.$el, event);
}
}
/**
* If the dialog is shown and the focus is not within a dialog element (either
* this one or another one in case of nested dialogs) or attribute, move it
* back to the dialog container
* See: https://github.com/KittyGiraudel/a11y-dialog/issues/177
*/
maintainFocus(event) {
const target = event.target;
if (!target.closest('[aria-modal="true"], [data-a11y-dialog-ignore-focus-trap]')) {
moveFocusToDialog(this.$el);
}
}
}
function instantiateDialogs() {
for (const el of document.querySelectorAll('[data-a11y-dialog]')) {
new A11yDialog(el);
}
}
if (typeof document !== 'undefined') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', instantiateDialogs);
}
else {
instantiateDialogs();
}
}
var CONTAINER_ID = "__authsignal-popup-container";
var CONTENT_ID = "__authsignal-popup-content";
var OVERLAY_ID = "__authsignal-popup-overlay";
var STYLE_ID = "__authsignal-popup-style";
var IFRAME_ID = "__authsignal-popup-iframe";
var DEFAULT_WIDTH = "385px";
var INITIAL_HEIGHT = "384px";
var PopupHandler = /** @class */ (function () {
function PopupHandler(_a) {
var width = _a.width, isClosable = _a.isClosable;
this.popup = null;
if (document.querySelector("#".concat(CONTAINER_ID))) {
throw new Error("Multiple instances of Authsignal popup is not supported.");
}
this.create({ width: width, isClosable: isClosable });
}
PopupHandler.prototype.create = function (_a) {
var _this = this;
var _b = _a.width, width = _b === void 0 ? DEFAULT_WIDTH : _b, _c = _a.isClosable, isClosable = _c === void 0 ? true : _c;
var isWidthValidCSSValue = CSS.supports("width", width);
var popupWidth = width;
if (!isWidthValidCSSValue) {
console.warn("Invalid CSS value for `popupOptions.width`. Using default value instead.");
popupWidth = DEFAULT_WIDTH;
}
// Create dialog container
var container = document.createElement("div");
container.setAttribute("id", CONTAINER_ID);
container.setAttribute("aria-hidden", "true");
if (!isClosable) {
container.setAttribute("role", "alertdialog");
}
// Create dialog overlay
var overlay = document.createElement("div");
overlay.setAttribute("id", OVERLAY_ID);
if (isClosable) {
overlay.setAttribute("data-a11y-dialog-hide", "true");
}
// Create dialog content
var content = document.createElement("div");
content.setAttribute("id", CONTENT_ID);
document.body.appendChild(container);
// Create CSS for dialog
var style = document.createElement("style");
style.setAttribute("id", STYLE_ID);
style.textContent = "\n #".concat(CONTAINER_ID, ",\n #").concat(OVERLAY_ID, " {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n\n #").concat(CONTAINER_ID, " {\n z-index: 2147483647;\n display: flex;\n }\n\n #").concat(CONTAINER_ID, "[aria-hidden='true'] {\n display: none;\n }\n\n #").concat(OVERLAY_ID, " {\n background-color: rgba(0, 0, 0, 0.18);\n }\n\n #").concat(CONTENT_ID, " {\n margin: auto;\n z-index: 2147483647;\n position: relative;\n background-color: transparent;\n border-radius: 8px;\n width: ").concat(popupWidth, ";\n }\n\n #").concat(CONTENT_ID, " iframe {\n width: 1px;\n min-width: 100%;\n border-radius: inherit;\n max-height: 95vh;\n height: ").concat(INITIAL_HEIGHT, ";\n }\n ");
// Attach the created elements
document.head.insertAdjacentElement("beforeend", style);
container.appendChild(overlay);
container.appendChild(content);
this.popup = new A11yDialog(container);
// Safari and Firefox will fail the WebAuthn request if the document making
// the request does not have focus. This will reduce the chances of that
// happening by focusing on the dialog container.
container.focus();
// Make sure to remove any trace of the dialog on hide
this.popup.on("hide", function () {
_this.destroy();
});
};
PopupHandler.prototype.destroy = function () {
var dialogEl = document.querySelector("#".concat(CONTAINER_ID));
var styleEl = document.querySelector("#".concat(STYLE_ID));
if (dialogEl && styleEl) {
document.body.removeChild(dialogEl);
document.head.removeChild(styleEl);
}
window.removeEventListener("message", resizeIframe);
};
PopupHandler.prototype.show = function (_a) {
var _b;
var url = _a.url;
if (!this.popup) {
throw new Error("Popup is not initialized");
}
var iframe = document.createElement("iframe");
iframe.setAttribute("id", IFRAME_ID);
iframe.setAttribute("name", "authsignal");
iframe.setAttribute("title", "Authsignal multi-factor authentication");
iframe.setAttribute("src", url);
iframe.setAttribute("frameborder", "0");
iframe.setAttribute("allow", "publickey-credentials-get *; publickey-credentials-create *; clipboard-write");
var dialogContent = document.querySelector("#".concat(CONTENT_ID));
if (dialogContent) {
dialogContent.appendChild(iframe);
}
window.addEventListener("message", resizeIframe);
(_b = this.popup) === null || _b === void 0 ? void 0 : _b.show();
};
PopupHandler.prototype.close = function () {
if (!this.popup) {
throw new Error("Popup is not initialized");
}
this.popup.hide();
};
PopupHandler.prototype.on = function (event, listener) {
if (!this.popup) {
throw new Error("Popup is not initialized");
}
this.popup.on(event, listener);
};
return PopupHandler;
}());
function resizeIframe(event) {
var iframeEl = document.querySelector("#".concat(IFRAME_ID));
if (iframeEl && event.data.height) {
iframeEl.style.height = event.data.height + "px";
}
}
var TotpApiClient = /** @class */ (function () {
function TotpApiClient(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.tenantId = tenantId;
this.baseUrl = baseUrl;
this.onTokenExpired = onTokenExpired;
}
TotpApiClient.prototype.enroll = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response, responseJson;
var token = _b.token;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/totp"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
})];
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];
}
});
});
};
TotpApiClient.prototype.verify = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var body, response, responseJson;
var token = _b.token, code = _b.code;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
body = { verificationCode: code };
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/verify/totp"), {
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];
}
});
});
};
return TotpApiClient;
}());
var Totp = /** @class */ (function () {
function Totp(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.cache = TokenCache.shared;
this.api = new TotpApiClient({ baseUrl: baseUrl, tenantId: tenantId, onTokenExpired: onTokenExpired });
}
Totp.prototype.enroll = function () {
return __awaiter(this, void 0, void 0, function () {
var response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.enroll({ token: this.cache.token })];
case 1:
response = _a.sent();
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
Totp.prototype.verify = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response;
var code = _b.code;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.verify({ token: this.cache.token, code: code })];
case 1:
response = _c.sent();
if ("accessToken" in response && response.accessToken) {
this.cache.token = response.accessToken;
}
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
return Totp;
}());
var EmailApiClient = /** @class */ (function () {
function EmailApiClient(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.tenantId = tenantId;
this.baseUrl = baseUrl;
this.onTokenExpired = onTokenExpired;
}
EmailApiClient.prototype.enroll = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var body, response, responseJson;
var token = _b.token, email = _b.email;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
body = { email: email };
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/email-otp"), {
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];
}
});
});
};
EmailApiClient.prototype.challenge = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response, responseJson;
var token = _b.token;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/challenge/email-otp"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
})];
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];
}
});
});
};
EmailApiClient.prototype.verify = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var body, response, responseJson;
var token = _b.token, code = _b.code;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
body = { verificationCode: code };
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/verify/email-otp"), {
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];
}
});
});
};
return EmailApiClient;
}());
var Email = /** @class */ (function () {
function Email(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.cache = TokenCache.shared;
this.api = new EmailApiClient({ baseUrl: baseUrl, tenantId: tenantId, onTokenExpired: onTokenExpired });
}
Email.prototype.enroll = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response;
var email = _b.email;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.enroll({ token: this.cache.token, email: email })];
case 1:
response = _c.sent();
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
Email.prototype.challenge = function () {
return __awaiter(this, void 0, void 0, function () {
var response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.challenge({ token: this.cache.token })];
case 1:
response = _a.sent();
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
Email.prototype.verify = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response;
var code = _b.code;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.verify({ token: this.cache.token, code: code })];
case 1:
response = _c.sent();
if ("accessToken" in response && response.accessToken) {
this.cache.token = response.accessToken;
}
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
return Email;
}());
var SmsApiClient = /** @class */ (function () {
function SmsApiClient(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.tenantId = tenantId;
this.baseUrl = baseUrl;
this.onTokenExpired = onTokenExpired;
}
SmsApiClient.prototype.enroll = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var body, response, responseJson;
var token = _b.token, phoneNumber = _b.phoneNumber;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
body = { phoneNumber: phoneNumber };
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/sms"), {
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];
}
});
});
};
SmsApiClient.prototype.challenge = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response, responseJson;
var token = _b.token;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/challenge/sms"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
})];
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];
}
});
});
};
SmsApiClient.prototype.verify = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var body, response, responseJson;
var token = _b.token, code = _b.code;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
body = { verificationCode: code };
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/verify/sms"), {
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];
}
});
});
};
return SmsApiClient;
}());
var Sms = /** @class */ (function () {
function Sms(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.cache = TokenCache.shared;
this.api = new SmsApiClient({ baseUrl: baseUrl, tenantId: tenantId, onTokenExpired: onTokenExpired });
}
Sms.prototype.enroll = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response;
var phoneNumber = _b.phoneNumber;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.enroll({ token: this.cache.token, phoneNumber: phoneNumber })];
case 1:
response = _c.sent();
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
Sms.prototype.challenge = function () {
return __awaiter(this, void 0, void 0, function () {
var response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.challenge({ token: this.cache.token })];
case 1:
response = _a.sent();
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
Sms.prototype.verify = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response;
var code = _b.code;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.verify({ token: this.cache.token, code: code })];
case 1:
response = _c.sent();
if ("accessToken" in response && response.accessToken) {
this.cache.token = response.accessToken;
}
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
return Sms;
}());
var EmailMagicLinkApiClient = /** @class */ (function () {
function EmailMagicLinkApiClient(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.tenantId = tenantId;
this.baseUrl = baseUrl;
this.onTokenExpired = onTokenExpired;
}
EmailMagicLinkApiClient.prototype.enroll = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var body, response, responseJson;
var token = _b.token, email = _b.email;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
body = { email: email };
return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/email-magic-link"), {
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];
}
});
});
};
EmailMagicLinkApiClient.prototype.challenge = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response, responseJson;
var token = _b.token;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/challenge/email-magic-link"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
})];
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];
}
});
});
};
EmailMagicLinkApiClient.prototype.checkVerificationStatus = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var pollVerificationStatus;
var _this = this;
var token = _b.token;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
pollVerificationStatus = function () { return __awaiter(_this, void 0, void 0, function () {
var response, responseJson;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/verify/email-magic-link/finalize"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
body: JSON.stringify({}),
})];
case 1:
response = _a.sent();
return [4 /*yield*/, response.json()];
case 2:
responseJson = _a.sent();
handleTokenExpired({ response: responseJson, onTokenExpired: this.onTokenExpired });
if (responseJson.isVerified) {
return [2 /*return*/, responseJson];
}
else {
return [2 /*return*/, new Promise(function (resolve) {
setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = resolve;
return [4 /*yield*/, pollVerificationStatus()];
case 1:
_a.apply(void 0, [_b.sent()]);
return [2 /*return*/];
}
});
}); }, 1000);
})];
}
}
});
}); };
return [4 /*yield*/, pollVerificationStatus()];
case 1: return [2 /*return*/, _c.sent()];
}
});
});
};
return EmailMagicLinkApiClient;
}());
var EmailMagicLink = /** @class */ (function () {
function EmailMagicLink(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.cache = TokenCache.shared;
this.api = new EmailMagicLinkApiClient({ baseUrl: baseUrl, tenantId: tenantId, onTokenExpired: onTokenExpired });
}
EmailMagicLink.prototype.enroll = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response;
var email = _b.email;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.enroll({ token: this.cache.token, email: email })];
case 1:
response = _c.sent();
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
EmailMagicLink.prototype.challenge = function () {
return __awaiter(this, void 0, void 0, function () {
var response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.challenge({ token: this.cache.token })];
case 1:
response = _a.sent();
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
EmailMagicLink.prototype.checkVerificationStatus = function () {
return __awaiter(this, void 0, void 0, function () {
var response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.checkVerificationStatus({ token: this.cache.token })];
case 1:
response = _a.sent();
if ("accessToken" in response && response.accessToken) {
this.cache.token = response.accessToken;
}
return [2 /*return*/, handleApiResponse(response)];
}
});
});
};
return EmailMagicLink;
}());
var SecurityKeyApiClient = /** @class */ (function () {
function SecurityKeyApiClient(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.tenantId = tenantId;
this.baseUrl = baseUrl;
this.onTokenExpired = onTokenExpired;
}
SecurityKeyApiClient.prototype.registrationOptions = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response, responseJson;
var token = _b.token;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/security-key/registration-options"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
body: JSON.stringify({}),
})];
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];
}
});
});
};
SecurityKeyApiClient.prototype.authenticationOptions = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response, responseJson;
var token = _b.token;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/security-key/authentication-options"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
body: JSON.stringify({}),
})];
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];
}
});
});
};
SecurityKeyApiClient.prototype.addAuthenticator = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response, responseJson;
var token = _b.token, registrationCredential = _b.registrationCredential;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/security-key"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
body: JSON.stringify(registrationCredential),
})];
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];
}
});
});
};
SecurityKeyApiClient.prototype.verify = function (_a) {
return __awaiter(this, arguments, void 0, function (_b) {
var response, responseJson;
var token = _b.token, authenticationCredential = _b.authenticationCredential;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/verify/security-key"), {
method: "POST",
headers: buildHeaders({ token: token, tenantId: this.tenantId }),
body: JSON.stringify(authenticationCredential),
})];
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];
}
});
});
};
return SecurityKeyApiClient;
}());
var SecurityKey = /** @class */ (function () {
function SecurityKey(_a) {
var baseUrl = _a.baseUrl, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.cache = TokenCache.shared;
this.api = new SecurityKeyApiClient({ baseUrl: baseUrl, tenantId: tenantId, onTokenExpired: onTokenExpired });
}
SecurityKey.prototype.enroll = function () {
return __awaiter(this, void 0, void 0, function () {
var optionsInput, optionsResponse, registrationResponse, addAuthenticatorResponse, e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
optionsInput = {
token: this.cache.token,
};
return [4 /*yield*/, this.api.registrationOptions(optionsInput)];
case 1:
optionsResponse = _a.sent();
if ("error" in optionsResponse) {
return [2 /*return*/, handleErrorResponse(optionsResponse)];
}
_a.label = 2;
case 2:
_a.trys.push([2, 5, , 6]);
return [4 /*yield*/, startRegistration({ optionsJSON: optionsResponse })];
case 3:
registrationResponse = _a.sent();
return [4 /*yield*/, this.api.addAuthenticator({
registrationCredential: registrationResponse,
token: this.cache.token,
})];
case 4:
addAuthenticatorResponse = _a.sent();
if ("error" in addAuthenticatorResponse) {
return [2 /*return*/, handleErrorResponse(addAuthenticatorResponse)];
}
if (addAuthenticatorResponse.accessToken) {
this.cache.token = addAuthenticatorResponse.accessToken;
}
return [2 /*return*/, {
data: {
token: addAuthenticatorResponse.accessToken,
registrationResponse: registrationResponse,
},
}];
case 5:
e_1 = _a.sent();
handleWebAuthnError(e_1);
throw e_1;
case 6: return [2 /*return*/];
}
});
});
};
SecurityKey.prototype.verify = function () {
return __awaiter(this, void 0, void 0, function () {
var optionsResponse, authenticationResponse, verifyResponse, token, isVerified, e_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.cache.token) {
return [2 /*return*/, this.cache.handleTokenNotSetError()];
}
return [4 /*yield*/, this.api.authenticationOptions({
token: this.cache.token,
})];
case 1:
optionsResponse = _a.sent();
if ("error" in optionsResponse) {
return [2 /*return*/, handleErrorResponse(optionsResponse)];
}
_a.label = 2;
case 2:
_a.trys.push([2, 5, , 6]);
return [4 /*yield*/, startAuthentication({
optionsJSON: optionsResponse,
})];
case 3:
authenticationResponse = _a.sent();
return [4 /*yield*/, this.api.verify({
authenticationCredential: authenticationResponse,
token: this.cache.token,
})];
case 4:
verifyResponse = _a.sent();
if ("error" in verifyResponse) {
return [2 /*return*/, handleErrorResponse(verifyResponse)];
}
if (verifyResponse.accessToken) {
this.cache.token = verifyResponse.accessToken;
}
token = verifyResponse.accessToken, isVerified = verifyResponse.isVerified;
return [2 /*return*/, {
data: {
isVerified: isVerified,
token: token,
authenticationResponse: authenticationResponse,
},
}];
case 5:
e_2 = _a.sent();
handleWebAuthnError(e_2);
throw e_2;
case 6: return [2 /*return*/];
}
});
});
};
return SecurityKey;
}());
var DEFAULT_COOKIE_NAME = "__as_aid";
var DEFAULT_PROFILING_COOKIE_NAME = "__as_pid";
var DEFAULT_BASE_URL = "https://api.authsignal.com/v1";
var TMX_ORG_ID = "4a08uqve";
var Authsignal = /** @class */ (function () {
function Authsignal(_a) {
var cookieDomain = _a.cookieDomain, _b = _a.cookieName, cookieName = _b === void 0 ? DEFAULT_COOKIE_NAME : _b, _c = _a.baseUrl, baseUrl = _c === void 0 ? DEFAULT_BASE_URL : _c, tenantId = _a.tenantId, onTokenExpired = _a.onTokenExpired;
this.anonymousId = "";
this.profilingId = "";
this.cookieDomain = "";
this.anonymousIdCookieName = "";
this.cookieDomain = cookieDomain || getCookieDomain();
this.anonymousIdCookieName = cookieName;
if (!tenantId) {
throw new Error("tenantId is required");
}
var idCookie = getCookie(this.anonymousIdCookieName);
if (idCookie) {
this.anonymousId = idCookie;
}
else {
this.anonymousId = v4();
setCookie({
name: this.anonymousIdCookieName,
value: this.anonymousId,
expire: Infinity,
domain: this.cookieDomain,
secure: document.location.protocol !== "http:",
});
}
this.passkey = new Passkey({ tenantId: tenantId, baseUrl: baseUrl, anonymousId: this.anonymousId, onTokenExpired: onTokenExpired });
this.totp = new Totp({ tenantId: tenantId, baseUrl: baseUrl, onTokenExpired: onTokenExpired });
this.email = new Email({ tenantId: tenantId, baseUrl: baseUrl, onTokenExpired: onTokenExpired });
this.emailML = new EmailMagicLink({ tenantId: tenantId, baseUrl: baseUrl, onTokenExpired: onTokenExpired });
this.sms = new Sms({ tenantId: tenantId, baseUrl: baseUrl, onTokenExpired: onTokenExpired });
this.securityKey = new SecurityKey({ tenantId: tenantId, baseUrl: baseUrl, onTokenExpired: onTokenExpired });
}
Authsignal.prototype.setToken = function (token) {
TokenCache.shared.token = token;
};
Authsignal.prototype.launch = function (url, options) {
switch (options === null || options === void 0 ? void 0 : options.mode) {
case "window":
return this.launchWithWindow(url, options);
case "popup":
return this.launchWithPopup(url, options);
case "redirect":
default:
this.launchWithRedirect(url);
}
};
Authsignal.prototype.initAdvancedProfiling = function (baseUrl) {
var profilingId = v4();
this.profilingId = profilingId;
setCookie({
name: DEFAULT_PROFILING_COOKIE_NAME,
value: profilingId,
expire: Infinity,
domain: this.cookieDomain,
secure: document.location.protocol !== "http:",
});
var tmxProfilingScruiptUrl = baseUrl
? "".concat(baseUrl, "/fp/tags.js?org_id=").concat(TMX_ORG_ID, "&session_id=").concat(profilingId)
: "https://h.online-metrix.net/fp/tags.js?org_id=".concat(TMX_ORG_ID, "&session_id=").concat(profilingId);
var script = document.createElement("script");
script.src = tmxProfilingScruiptUrl;
script.async = false;
script.id = "as_adv_profile";
document.head.appendChild(script);
var pixelContainer = document.createElement("noscript");
pixelContainer.setAttribute("id", "as_adv_profile_pixel");
pixelContainer.setAttribute("aria-hidden", "true");
// Instantiate Pixel
var iframe = document.createElement("iframe");
var profilingPixelUrl = baseUrl
? "".concat(baseUrl, "/fp/tags?org_id=").concat(TMX_ORG_ID, "&session_id=").concat(profilingId)
: "https://h.online-metrix.net/fp/tags?org_id=".concat(TMX_ORG_ID, "&session_id=").concat(profilingId);
iframe.setAttribute("id", "as_adv_profile_pixel");
iframe.setAttribute("src", profilingPixelUrl);
iframe.setAttribute("style", "width: 100px; height: 100px; border: 0; position: absolute; top: -5000px;");
if (pixelContainer) {
pixelContainer.appendChild(iframe);
document.body.prepend(pixelContainer);
}
};
Authsignal.prototype.launchWithRedirect = function (url) {
window.location.href = url;
};
Authsignal.prototype.launchWithPopup = function (url, options) {
var popupOptions = options.popupOptions;
var popupHandler = new PopupHandler({ width: popupOptions === null || popupOptions === void 0 ? void 0 : popupOptions.width, isClosable: popupOptions === null || popupOptions === void 0 ? void 0 : popupOptions.isClosable });
var popupUrl = "".concat(url, "&mode=popup");
popupHandler.show({ url: popupUrl });
return new Promise(function (resolve) {
var token = undefined;
var onMessage = function (event) {
var data = null;
try {
data = JSON.parse(event.data);
}
catch (_a) {
// Ignore if the event data is not valid JSON
}
if ((data === null || data === void 0 ? void 0 : data.event) === AuthsignalWindowMessage.AUTHSIGNAL_CLOSE_POPUP) {
token = data.token;
popupHandler.close();
}
};
popupHandler.on("hide", function () {
resolve({ token: token });
});
window.addEventListener("message", onMessage, false);
});
};
Authsignal.prototype.launchWithWindow = function (url, options) {
var windowOptions = options.windowOptions;
var windowHandler = new WindowHandler();
var windowUrl = "".concat(url, "&mode=popup");
windowHandler.show({ url: windowUrl, width: windowOptions === null || windowOptions === void 0 ? void 0 : windowOptions.width, height: windowOptions === null || windowOptions === void 0 ? void 0 : windowOptions.height });
return new Promise(function (resolve) {
var onMessage = function (event) {
var data = null;
try {
data = JSON.parse(event.data);
}
catch (_a) {
// Ignore if the event data is not valid JSON
}
if ((data === null || data === void 0 ? void 0 : data.event) === AuthsignalWindowMessage.AUTHSIGNAL_CLOSE_POPUP) {
windowHandler.close();
resolve({ token: data.token });
}
};
window.addEventListener("message", onMessage, false);
});
};
return Authsignal;
}());
export { Authsignal, AuthsignalWindowMessage, WebAuthnError };