@entrustcorp/idaas-auth-js
Version:
IDaaS Authentication SDK for SPA applications
1,197 lines • 69.1 kB
JavaScript
import * as __WEBPACK_EXTERNAL_MODULE_jose__ from "jose";
function _define_property(obj, key, value) {
if (key in obj) Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
else obj[key] = value;
return obj;
}
const PATH_PARAM_RE = /\{[^{}]+\}/g;
const serializePrimitiveParam = ({ allowReserved, name, value })=>{
if (null == value) return '';
if ('object' == typeof value) throw new Error('Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.');
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
};
const separatorArrayExplode = (style)=>{
switch(style){
case 'label':
return '.';
case 'matrix':
return ';';
case 'simple':
return ',';
default:
return '&';
}
};
const separatorArrayNoExplode = (style)=>{
switch(style){
case 'form':
return ',';
case 'pipeDelimited':
return '|';
case 'spaceDelimited':
return '%20';
default:
return ',';
}
};
const separatorObjectExplode = (style)=>{
switch(style){
case 'label':
return '.';
case 'matrix':
return ';';
case 'simple':
return ',';
default:
return '&';
}
};
const serializeArrayParam = ({ allowReserved, explode, name, style, value })=>{
if (!explode) {
const joinedValues = (allowReserved ? value : value.map((v)=>encodeURIComponent(v))).join(separatorArrayNoExplode(style));
switch(style){
case 'label':
return `.${joinedValues}`;
case 'matrix':
return `;${name}=${joinedValues}`;
case 'simple':
return joinedValues;
default:
return `${name}=${joinedValues}`;
}
}
const separator = separatorArrayExplode(style);
const joinedValues = value.map((v)=>{
if ('label' === style || 'simple' === style) return allowReserved ? v : encodeURIComponent(v);
return serializePrimitiveParam({
allowReserved,
name,
value: v
});
}).join(separator);
return 'label' === style || 'matrix' === style ? separator + joinedValues : joinedValues;
};
const serializeObjectParam = ({ allowReserved, explode, name, style, value })=>{
if (value instanceof Date) return `${name}=${value.toISOString()}`;
if ('deepObject' !== style && !explode) {
let values = [];
Object.entries(value).forEach(([key, v])=>{
values = [
...values,
key,
allowReserved ? v : encodeURIComponent(v)
];
});
const joinedValues = values.join(',');
switch(style){
case 'form':
return `${name}=${joinedValues}`;
case 'label':
return `.${joinedValues}`;
case 'matrix':
return `;${name}=${joinedValues}`;
default:
return joinedValues;
}
}
const separator = separatorObjectExplode(style);
const joinedValues = Object.entries(value).map(([key, v])=>serializePrimitiveParam({
allowReserved,
name: 'deepObject' === style ? `${name}[${key}]` : key,
value: v
})).join(separator);
return 'label' === style || 'matrix' === style ? separator + joinedValues : joinedValues;
};
const defaultPathSerializer = ({ path, url: _url })=>{
let url = _url;
const matches = _url.match(PATH_PARAM_RE);
if (matches) for (const match of matches){
let explode = false;
let name = match.substring(1, match.length - 1);
let style = 'simple';
if (name.endsWith('*')) {
explode = true;
name = name.substring(0, name.length - 1);
}
if (name.startsWith('.')) {
name = name.substring(1);
style = 'label';
} else if (name.startsWith(';')) {
name = name.substring(1);
style = 'matrix';
}
const value = path[name];
if (null == value) continue;
if (Array.isArray(value)) {
url = url.replace(match, serializeArrayParam({
explode,
name,
style,
value
}));
continue;
}
if ('object' == typeof value) {
url = url.replace(match, serializeObjectParam({
explode,
name,
style,
value: value
}));
continue;
}
if ('matrix' === style) {
url = url.replace(match, `;${serializePrimitiveParam({
name,
value: value
})}`);
continue;
}
const replaceValue = encodeURIComponent('label' === style ? `.${value}` : value);
url = url.replace(match, replaceValue);
}
return url;
};
const createQuerySerializer = ({ allowReserved, array, object } = {})=>{
const querySerializer = (queryParams)=>{
let search = [];
if (queryParams && 'object' == typeof queryParams) for(const name in queryParams){
const value = queryParams[name];
if (null != value) {
if (Array.isArray(value)) {
search = [
...search,
serializeArrayParam({
allowReserved,
explode: true,
name,
style: 'form',
value,
...array
})
];
continue;
}
if ('object' == typeof value) {
search = [
...search,
serializeObjectParam({
allowReserved,
explode: true,
name,
style: 'deepObject',
value: value,
...object
})
];
continue;
}
search = [
...search,
serializePrimitiveParam({
allowReserved,
name,
value: value
})
];
}
}
return search.join('&');
};
return querySerializer;
};
const getParseAs = (contentType)=>{
if (!contentType) return;
const cleanContent = contentType.split(';')[0].trim();
if (cleanContent.startsWith('application/json') || cleanContent.endsWith('+json')) return 'json';
if ('multipart/form-data' === cleanContent) return 'formData';
if ([
'application/',
'audio/',
'image/',
'video/'
].some((type)=>cleanContent.startsWith(type))) return 'blob';
if (cleanContent.startsWith('text/')) return 'text';
};
const getUrl = ({ baseUrl, path, query, querySerializer, url: _url })=>{
const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;
let url = baseUrl + pathUrl;
if (path) url = defaultPathSerializer({
path,
url
});
let search = query ? querySerializer(query) : '';
if (search.startsWith('?')) search = search.substring(1);
if (search) url += `?${search}`;
return url;
};
const mergeConfigs = (a, b)=>{
var _config_baseUrl;
const config = {
...a,
...b
};
if (null === (_config_baseUrl = config.baseUrl) || void 0 === _config_baseUrl ? void 0 : _config_baseUrl.endsWith('/')) config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
config.headers = mergeHeaders(a.headers, b.headers);
return config;
};
const mergeHeaders = (...headers)=>{
const mergedHeaders = new Headers();
for (const header of headers){
if (!header || 'object' != typeof header) continue;
const iterator = header instanceof Headers ? header.entries() : Object.entries(header);
for (const [key, value] of iterator)if (null === value) mergedHeaders.delete(key);
else if (Array.isArray(value)) for (const v of value)mergedHeaders.append(key, v);
else if (void 0 !== value) mergedHeaders.set(key, 'object' == typeof value ? JSON.stringify(value) : value);
}
return mergedHeaders;
};
class Interceptors {
clear() {
this._fns = [];
}
exists(fn) {
return -1 !== this._fns.indexOf(fn);
}
eject(fn) {
const index = this._fns.indexOf(fn);
if (-1 !== index) this._fns = [
...this._fns.slice(0, index),
...this._fns.slice(index + 1)
];
}
use(fn) {
this._fns = [
...this._fns,
fn
];
}
constructor(){
_define_property(this, "_fns", void 0);
this._fns = [];
}
}
const createInterceptors = ()=>({
error: new Interceptors(),
request: new Interceptors(),
response: new Interceptors()
});
const jsonBodySerializer = {
bodySerializer: (body)=>JSON.stringify(body)
};
const defaultQuerySerializer = createQuerySerializer({
allowReserved: false,
array: {
explode: true,
style: 'form'
},
object: {
explode: true,
style: 'deepObject'
}
});
const defaultHeaders = {
'Content-Type': 'application/json'
};
const createConfig = (override = {})=>({
...jsonBodySerializer,
baseUrl: '',
headers: defaultHeaders,
parseAs: 'auto',
querySerializer: defaultQuerySerializer,
...override
});
const createClient = (config = {})=>{
let _config = mergeConfigs(createConfig(), config);
const getConfig = ()=>({
..._config
});
const setConfig = (config)=>{
_config = mergeConfigs(_config, config);
return getConfig();
};
const buildUrl = (options)=>{
const url = getUrl({
baseUrl: options.baseUrl ?? '',
path: options.path,
query: options.query,
querySerializer: 'function' == typeof options.querySerializer ? options.querySerializer : createQuerySerializer(options.querySerializer),
url: options.url
});
return url;
};
const interceptors = createInterceptors();
const request = async (options)=>{
const opts = {
..._config,
...options,
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
headers: mergeHeaders(_config.headers, options.headers)
};
if (opts.body && opts.bodySerializer) opts.body = opts.bodySerializer(opts.body);
if (!opts.body) opts.headers.delete('Content-Type');
const url = buildUrl(opts);
const requestInit = {
redirect: 'follow',
...opts
};
let request = new Request(url, requestInit);
for (const fn of interceptors.request._fns)request = await fn(request, opts);
const _fetch = opts.fetch;
let response = await _fetch(request);
for (const fn of interceptors.response._fns)response = await fn(response, request, opts);
const result = {
request,
response
};
if (response.ok) {
if (204 === response.status || '0' === response.headers.get('Content-Length')) return {
data: {},
...result
};
if ('stream' === opts.parseAs) return {
data: response.body,
...result
};
const parseAs = ('auto' === opts.parseAs ? getParseAs(response.headers.get('Content-Type')) : opts.parseAs) ?? 'json';
let data = await response[parseAs]();
if ('json' === parseAs && opts.responseTransformer) data = await opts.responseTransformer(data);
return {
data,
...result
};
}
let error = await response.text();
try {
error = JSON.parse(error);
} catch {}
let finalError = error;
for (const fn of interceptors.error._fns)finalError = await fn(error, response, request, opts);
finalError = finalError || {};
if (opts.throwOnError) throw finalError;
return {
error: finalError,
...result
};
};
return {
buildUrl,
connect: (options)=>request({
...options,
method: 'CONNECT'
}),
delete: (options)=>request({
...options,
method: 'DELETE'
}),
get: (options)=>request({
...options,
method: 'GET'
}),
getConfig,
head: (options)=>request({
...options,
method: 'HEAD'
}),
interceptors,
options: (options)=>request({
...options,
method: 'OPTIONS'
}),
patch: (options)=>request({
...options,
method: 'PATCH'
}),
post: (options)=>request({
...options,
method: 'POST'
}),
put: (options)=>request({
...options,
method: 'PUT'
}),
request,
setConfig,
trace: (options)=>request({
...options,
method: 'TRACE'
})
};
};
const client = createClient(createConfig());
const userAuthenticateUsingPost = (options)=>((null == options ? void 0 : options.client) ?? client).post({
...options,
url: '/api/web/v1/authentication/users/authenticate/{authenticator}/complete'
});
const userAuthenticatorQueryUsingPost = (options)=>((null == options ? void 0 : options.client) ?? client).post({
...options,
url: '/api/web/v2/authentication/users'
});
const userChallengeUsingPost = (options)=>((null == options ? void 0 : options.client) ?? client).post({
...options,
url: '/api/web/v2/authentication/users/authenticate/{authenticator}'
});
const fetchOpenidConfiguration = async (issuerUrl)=>{
const wellKnownUrl = `${issuerUrl}/.well-known/openid-configuration`;
const response = await fetch(wellKnownUrl);
return await response.json();
};
const requestToken = async (tokenEndpoint, tokenRequest)=>{
const searchParams = new URLSearchParams({
...tokenRequest
});
const response = await fetch(tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: searchParams
});
return await response.json();
};
const getUserInfo = async (userInfoEndpoint, accessToken)=>{
const response = await fetch(userInfoEndpoint, {
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`
}
});
return await response.text();
};
const queryUserAuthOptions = async (requestBody, baseUrl)=>{
const { data, error } = await userAuthenticatorQueryUsingPost({
baseUrl,
body: {
...requestBody
}
});
if (error) throw parseResponseError(error);
return data;
};
const requestAuthChallenge = async (requestBody, authenticator, baseUrl)=>{
const { data, error } = await userChallengeUsingPost({
baseUrl,
body: {
...requestBody
},
path: {
authenticator
}
});
if (error) throw parseResponseError(error);
return data;
};
const submitAuthChallenge = async (requestBody, authenticator, authorization, baseUrl)=>{
const { data, error } = await userAuthenticateUsingPost({
baseUrl,
headers: {
Authorization: authorization
},
body: {
...requestBody
},
path: {
authenticator
}
});
if (error) throw parseResponseError(error);
return data;
};
const getAuthRequestId = async (endpoint)=>{
const response = await fetch(endpoint, {
method: "POST"
});
return await response.json();
};
const parseResponseError = (errorResponse)=>new Error(errorResponse.errorCode, {
cause: errorResponse.errorMessage
});
const DEFAULT_POPUP_TIMEOUT_SECONDS = 300;
const openPopup = (popupUrl)=>{
const width = 500;
const height = 700;
const left = window.screenX + (window.innerWidth - width) / 2;
const top = window.screenY + (window.innerHeight - height) / 2;
const popup = window.open(popupUrl, "idaas:authorize", `popup,left=${left},top=${top},width=${width},height=${height}`);
if (!popup) throw new Error("Unable to open popup, blocked by browser");
return popup;
};
const listenToAuthorizePopup = (popup, url)=>{
const expectedOrigin = new URL(url).origin;
return new Promise((resolve, reject)=>{
const popupListenerAbortController = new AbortController();
const popupWebMessageEventHandler = (event)=>{
const hasOriginAndData = event.origin === expectedOrigin && event.data;
const isAuthorizeEvent = hasOriginAndData && "authorization_response" === event.data.type;
if (!isAuthorizeEvent) return;
cleanUpPopup();
const response = event.data.response;
if (response.error) reject(new Error(response.error));
resolve(response);
};
const pollPopupInterval = setInterval(()=>{
if (popup.closed) {
cleanUpPopup();
reject(new Error("Authentication was cancelled by the user"));
}
}, 1000);
const popupTimeout = setTimeout(()=>{
cleanUpPopup();
reject(new Error("User took too long to authenticate"));
}, 1000 * DEFAULT_POPUP_TIMEOUT_SECONDS);
const cleanUpPopup = ()=>{
clearInterval(pollPopupInterval);
clearTimeout(popupTimeout);
popup.close();
popupListenerAbortController.abort();
};
window.addEventListener("message", popupWebMessageEventHandler, {
signal: popupListenerAbortController.signal
});
});
};
const browserSupportsPasskey = async ()=>!!window.PublicKeyCredential;
const createRandomString = ()=>{
const randomNumbers = window.crypto.getRandomValues(new Uint8Array(32));
return String.fromCharCode(...randomNumbers);
};
const base64UrlStringEncode = (str)=>btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const base64UrlOctetEncode = (array)=>base64UrlStringEncode(String.fromCharCode(...array));
const generateChallengeVerifierPair = async ()=>{
const randomString = createRandomString();
const codeVerifier = base64UrlStringEncode(randomString);
const codeChallenge = await createCodeChallenge(codeVerifier);
return {
codeVerifier,
codeChallenge
};
};
const createCodeChallenge = async (codeVerifier)=>{
const hash = await sha256(codeVerifier);
return base64UrlOctetEncode(hash);
};
const sha256 = async (string)=>{
const encoder = new TextEncoder();
const data = encoder.encode(string);
const hash = await window.crypto.subtle.digest("SHA-256", data);
return new Uint8Array(hash);
};
const formatUrl = (initialUrl)=>{
const input = initialUrl.includes("://") ? initialUrl : `https://${initialUrl}`;
const url = new URL(input);
if ("https:" !== url.protocol) {
if ("localhost" !== url.hostname || "http:" !== url.protocol) url.protocol = "https:";
}
const finalUrl = url.toString();
return finalUrl.endsWith("/") ? finalUrl.slice(0, -1) : finalUrl;
};
const calculateEpochExpiry = (expiresIn, authTime = Math.floor(Date.now() / 1000).toString())=>Number.parseInt(expiresIn) + Number.parseInt(authTime);
const sanitizeUri = (redirectUri)=>{
const sanitizedUrl = new URL(redirectUri);
sanitizedUrl.search = "";
return sanitizedUrl.toString();
};
const 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;
};
const 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, "");
};
const toPublicKeyCredentialDescriptor = (descriptor)=>{
const { id } = descriptor;
return {
...descriptor,
id: base64URLStringToBuffer(id),
transports: descriptor.transports
};
};
function AuthenticationTransaction_define_property(obj, key, value) {
if (key in obj) Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
else obj[key] = value;
return obj;
}
class AuthenticationTransaction {
async handlePasskeyLogin() {
if (!await browserSupportsPasskey()) throw new Error("This browser does not support passkey");
const { method } = this.authenticationDetails;
const token = this.token;
const fidoChallenge = this.fidoChallenge;
if (!(token && method && fidoChallenge)) throw new Error("Failed to retrieve required values");
const authChallenge = {
challenge: fidoChallenge.challenge ?? ""
};
this.publicKeyCredentialRequestOptions = this.getCredentialRequestOptions(authChallenge);
}
getCredentialRequestOptions(optionsJSON) {
var _optionsJSON_allowCredentials;
let allowCredentials;
if ((null === (_optionsJSON_allowCredentials = optionsJSON.allowCredentials) || void 0 === _optionsJSON_allowCredentials ? void 0 : _optionsJSON_allowCredentials.length) !== 0) {
var _optionsJSON_allowCredentials1;
allowCredentials = null === (_optionsJSON_allowCredentials1 = optionsJSON.allowCredentials) || void 0 === _optionsJSON_allowCredentials1 ? void 0 : _optionsJSON_allowCredentials1.map(toPublicKeyCredentialDescriptor);
}
const publicKey = {
...optionsJSON,
challenge: base64URLStringToBuffer(optionsJSON.challenge),
allowCredentials
};
return publicKey;
}
async requestAuthChallenge() {
const { url, codeVerifier } = await this.generateJwtAuthorizeUrl();
const { authRequestKey, applicationId } = await getAuthRequestId(url);
this.requiredDetails = {
authRequestKey,
applicationId,
codeVerifier
};
const { authenticationMethod: method, secondFactor } = await this.determineAuthenticationMethod();
this.authenticationDetails.method = method;
this.authenticationDetails.secondFactor = secondFactor;
const requestBody = this.constructUserChallengeParams();
const requestAuthChallengeResponse = await requestAuthChallenge(requestBody, method, this.issuerOrigin);
const { token, faceChallenge, fidoChallenge, kbaChallenge } = requestAuthChallengeResponse;
this.token = token;
this.fidoChallenge = fidoChallenge;
this.faceChallenge = faceChallenge;
this.kbaChallenge = kbaChallenge;
if ("PASSKEY" === method || "FIDO" === method) await this.handlePasskeyLogin();
const pollForCompletion = this.shouldPoll(method);
return {
...requestAuthChallengeResponse,
publicKeyCredentialRequestOptions: this.publicKeyCredentialRequestOptions,
pollForCompletion,
method,
userId: this.userId
};
}
async generateJwtAuthorizeUrl() {
const url = new URL(`${this.oidcConfig.issuer}/authorizejwt`);
const { codeVerifier, codeChallenge } = await generateChallengeVerifierPair();
const state = base64UrlStringEncode(createRandomString());
const nonce = base64UrlStringEncode(createRandomString());
const scope = this.authenticationDetails.scope ?? "";
const scopeAsArray = scope.split(" ");
scopeAsArray.push("openid");
if (this.useRefreshToken) scopeAsArray.push("offline_access");
const usedScope = [
...new Set(scopeAsArray)
].join(" ");
if (this.audience) url.searchParams.append("audience", this.audience);
if (this.maxAge) url.searchParams.append("max_age", this.maxAge.toString());
url.searchParams.append("state", state);
url.searchParams.append("nonce", nonce);
url.searchParams.append("scope", usedScope);
url.searchParams.append("client_id", this.clientId);
url.searchParams.append("code_challenge", codeChallenge);
url.searchParams.append("code_challenge_method", "S256");
this.authenticationDetails.scope = usedScope;
return {
url: url.toString(),
codeVerifier
};
}
async queryUserAuthenticators() {
if (!this.requiredDetails) throw new Error("Jwt params not initialized");
const queryUserAuthResponse = await queryUserAuthOptions({
transactionDetails: this.transactionDetails,
userId: this.userId,
authRequestKey: this.requiredDetails.authRequestKey,
applicationId: this.requiredDetails.applicationId,
origin: window.location.origin
}, this.issuerOrigin);
const { authenticationTypes, availableSecondFactor } = queryUserAuthResponse;
return {
authenticationTypes: authenticationTypes,
availableSecondFactor: availableSecondFactor
};
}
async determineAuthenticationMethod() {
const userId = this.userId;
const strict = this.strict;
const preferredAuthenticationMethod = this.preferredAuthenticationMethod;
if (!userId) return {
authenticationMethod: "PASSKEY",
secondFactor: void 0
};
if (strict && !preferredAuthenticationMethod) throw new Error("preferredAuthenticationMethod must be defined");
if (strict && preferredAuthenticationMethod && "PASSWORD_AND_SECONDFACTOR" !== preferredAuthenticationMethod) return {
authenticationMethod: preferredAuthenticationMethod,
secondFactor: void 0
};
const { authenticationTypes, availableSecondFactor } = await this.queryUserAuthenticators();
const secondFactor = availableSecondFactor ? availableSecondFactor[0] : void 0;
if (preferredAuthenticationMethod) {
const preferredMethodAvailable = authenticationTypes.includes(preferredAuthenticationMethod);
if (strict) return {
authenticationMethod: preferredAuthenticationMethod,
secondFactor
};
if (preferredMethodAvailable) return {
authenticationMethod: preferredAuthenticationMethod,
secondFactor
};
}
return {
authenticationMethod: authenticationTypes[0],
secondFactor
};
}
async requestSecondFactorAuth() {
const requestBody = this.constructUserChallengeParams();
const response = await requestAuthChallenge(requestBody, "PASSWORD_AND_SECONDFACTOR", this.issuerOrigin);
this.token = response.token;
return response;
}
parseKbaChallengeAnswers(answers) {
if (!(answers && this.kbaChallenge)) return;
for(let i = 0; i < answers.length; i++){
var _this_kbaChallenge;
const answer = answers[i];
if (null === (_this_kbaChallenge = this.kbaChallenge) || void 0 === _this_kbaChallenge ? void 0 : _this_kbaChallenge.userQuestions[i]) this.kbaChallenge.userQuestions[i].answer = answer;
else throw new Error("invalid user response");
}
}
async submitAuthChallenge({ response, kbaChallengeAnswers }) {
const { method } = this.authenticationDetails;
const token = this.token;
if (!(method && token)) throw new Error("Error parsing authentication params");
this.parseKbaChallengeAnswers(kbaChallengeAnswers);
const requestBody = this.constructUserAuthenticateParams("SUBMIT", response);
const authenticationResponse = await submitAuthChallenge(requestBody, method, token, this.issuerOrigin);
this.token = authenticationResponse.token;
if ("PASSWORD_AND_SECONDFACTOR" === method && !this.isSecondFactor) return await this.prepareForSecondFactorSubmission();
if (authenticationResponse.authenticationCompleted) await this.handleSuccessfulAuthentication();
return authenticationResponse;
}
async poll() {
const { method } = this.authenticationDetails;
const token = this.token;
if (!(token && method)) throw new Error("Error parsing authentication params");
const requestBody = this.constructUserAuthenticateParams("POLL");
return await submitAuthChallenge(requestBody, method, token, this.issuerOrigin);
}
async pollForAuthCompletion() {
this.continuePolling = true;
let authResponse = {};
while(this.continuePolling){
authResponse = await this.poll();
const { status } = authResponse;
switch(status){
case void 0:
throw new Error("The method of authentication requires a user response.");
case "NO_RESPONSE":
break;
default:
this.continuePolling = false;
break;
}
await new Promise((resolve)=>setTimeout(resolve, 1000));
}
if (authResponse.authenticationCompleted) {
this.token = authResponse.token;
await this.handleSuccessfulAuthentication();
}
return authResponse;
}
async cancelAuthChallenge() {
const { method } = this.authenticationDetails;
const token = this.token;
if (!(token && method)) throw new Error("error parsing authentication params");
if (this.abortController) this.abortController.abort("cancelled login ceremony");
this.continuePolling = false;
if ("PASSKEY" !== method) {
const requestBody = this.constructUserAuthenticateParams("CANCEL");
await submitAuthChallenge(requestBody, method, token, this.issuerOrigin);
}
}
constructor({ oidcConfig, userId, scope, useRefreshToken, clientId, preferredAuthenticationMethod, strict, faceBiometricOptions, tokenPushOptions, audience, maxAge, transactionDetails }){
AuthenticationTransaction_define_property(this, "clientId", void 0);
AuthenticationTransaction_define_property(this, "issuerOrigin", void 0);
AuthenticationTransaction_define_property(this, "faceBiometricOptions", void 0);
AuthenticationTransaction_define_property(this, "tokenPushOptions", void 0);
AuthenticationTransaction_define_property(this, "oidcConfig", void 0);
AuthenticationTransaction_define_property(this, "strict", void 0);
AuthenticationTransaction_define_property(this, "useRefreshToken", void 0);
AuthenticationTransaction_define_property(this, "userId", void 0);
AuthenticationTransaction_define_property(this, "audience", void 0);
AuthenticationTransaction_define_property(this, "maxAge", void 0);
AuthenticationTransaction_define_property(this, "preferredAuthenticationMethod", void 0);
AuthenticationTransaction_define_property(this, "transactionDetails", void 0);
AuthenticationTransaction_define_property(this, "authenticationDetails", void 0);
AuthenticationTransaction_define_property(this, "continuePolling", false);
AuthenticationTransaction_define_property(this, "isSecondFactor", false);
AuthenticationTransaction_define_property(this, "faceChallenge", void 0);
AuthenticationTransaction_define_property(this, "fidoChallenge", void 0);
AuthenticationTransaction_define_property(this, "fidoResponse", void 0);
AuthenticationTransaction_define_property(this, "kbaChallenge", void 0);
AuthenticationTransaction_define_property(this, "publicKeyCredentialRequestOptions", void 0);
AuthenticationTransaction_define_property(this, "requiredDetails", void 0);
AuthenticationTransaction_define_property(this, "token", void 0);
AuthenticationTransaction_define_property(this, "abortController", void 0);
AuthenticationTransaction_define_property(this, "handleSuccessfulAuthentication", async ()=>{
if (!this.requiredDetails) throw new Error("Jwt parameters not initialized");
const { authRequestKey, codeVerifier } = this.requiredDetails;
if (!this.token) throw new Error("IDaaS token not stored");
const requestBody = {
client_id: this.clientId,
code: authRequestKey,
code_verifier: codeVerifier,
grant_type: "jwt_idaas",
jwt: this.token
};
const { id_token, access_token, expires_in, refresh_token } = await requestToken(this.oidcConfig.token_endpoint, requestBody);
if (!(id_token && access_token)) throw new Error("failed to fetch id token and access token from IDaaS");
if (this.useRefreshToken && !refresh_token) throw new Error("failed to fetch refresh token from IDaaS");
this.authenticationDetails = {
...this.authenticationDetails,
idToken: id_token,
accessToken: access_token,
refreshToken: refresh_token,
expiresAt: calculateEpochExpiry(expires_in),
audience: this.audience,
maxAge: this.maxAge
};
});
AuthenticationTransaction_define_property(this, "prepareForSecondFactorSubmission", async ()=>{
this.isSecondFactor = true;
const secondFactor = this.authenticationDetails.secondFactor;
if (!secondFactor) throw new Error("error parsing authentication params");
const { method } = this.authenticationDetails;
const secondFactorRequest = await this.requestSecondFactorAuth();
const { faceChallenge, fidoChallenge, kbaChallenge, token: secondFactorToken } = secondFactorRequest;
this.fidoChallenge = fidoChallenge;
this.faceChallenge = faceChallenge;
this.kbaChallenge = kbaChallenge;
this.token = secondFactorToken;
if ("FIDO" === secondFactor) await this.handlePasskeyLogin();
const pollForCompletion = this.shouldPoll(secondFactor);
return {
...secondFactorRequest,
secondFactorMethod: secondFactor,
pollForCompletion,
method
};
});
AuthenticationTransaction_define_property(this, "shouldPoll", (method)=>"FACE" === method || "TOKENPUSH" === method || "SMARTCREDENTIALPUSH" === method);
AuthenticationTransaction_define_property(this, "getAuthenticationDetails", ()=>this.authenticationDetails);
AuthenticationTransaction_define_property(this, "constructUserChallengeParams", ()=>{
const { method, secondFactor } = this.authenticationDetails;
const token = this.token;
if (!this.requiredDetails) throw new Error("Jwt params not initialized");
if (!method) throw new Error("error parsing authentication params");
const requestBody = {
transactionDetails: this.transactionDetails,
applicationId: this.requiredDetails.applicationId,
userId: this.userId
};
if ("FIDO" === method) requestBody.origin = window.location.origin;
if ("TOKENPUSH" === method) requestBody.pushMutualChallengeEnabled = this.tokenPushOptions.mutualChallengeEnabled;
if ("FACE" === method) requestBody.pushMutualChallengeEnabled = this.faceBiometricOptions.mutualChallengeEnabled;
if (this.isSecondFactor) {
if (!(secondFactor && token)) throw new Error("Error parsing authentication params");
if ("TOKENPUSH" === secondFactor) requestBody.pushMutualChallengeEnabled = this.tokenPushOptions.mutualChallengeEnabled;
if ("FACE" === secondFactor) requestBody.pushMutualChallengeEnabled = this.faceBiometricOptions.mutualChallengeEnabled;
if ("FIDO" === secondFactor) requestBody.origin = window.location.origin;
requestBody.secondFactorAuthenticator = secondFactor;
requestBody.authToken = token;
}
return requestBody;
});
AuthenticationTransaction_define_property(this, "constructUserAuthenticateParams", (requestType, response)=>{
const { secondFactor, method } = this.authenticationDetails;
if (!this.requiredDetails) throw new Error("Required details not initialized");
const requestBody = {
transactionDetails: this.transactionDetails,
applicationId: this.requiredDetails.applicationId,
userId: this.userId
};
if (this.isSecondFactor) {
if (!secondFactor) throw new Error("Error parsing authentication params");
requestBody.secondFactorAuthenticator = secondFactor;
}
switch(requestType){
case "CANCEL":
requestBody.cancel = true;
break;
case "POLL":
if ("FACE" === method) {
var _this_faceChallenge;
requestBody.faceResponse = null === (_this_faceChallenge = this.faceChallenge) || void 0 === _this_faceChallenge ? void 0 : _this_faceChallenge.workflowRunId;
}
break;
case "SUBMIT":
requestBody.authRequestKey = this.requiredDetails.authRequestKey;
requestBody.response = response ?? void 0;
requestBody.kbaChallenge = this.kbaChallenge ?? void 0;
requestBody.fidoResponse = this.fidoResponse ?? void 0;
break;
}
return requestBody;
});
AuthenticationTransaction_define_property(this, "submitPasskey", async (credential)=>{
const { id, response } = credential;
let userHandle;
if (response.userHandle) userHandle = bufferToBase64URLString(response.userHandle);
this.fidoResponse = {
authenticatorData: bufferToBase64URLString(response.authenticatorData),
clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
credentialId: id,
signature: bufferToBase64URLString(response.signature),
userHandle
};
});
const { issuer } = oidcConfig;
this.authenticationDetails = {
scope
};
this.audience = audience;
this.clientId = clientId;
this.issuerOrigin = new URL(issuer).origin;
this.maxAge = maxAge;
this.tokenPushOptions = {
mutualChallengeEnabled: (null == tokenPushOptions ? void 0 : tokenPushOptions.mutualChallengeEnabled) ?? false
};
this.faceBiometricOptions = {
mutualChallengeEnabled: (null == faceBiometricOptions ? void 0 : faceBiometricOptions.mutualChallengeEnabled) ?? false
};
this.oidcConfig = oidcConfig;
this.preferredAuthenticationMethod = preferredAuthenticationMethod;
this.strict = strict ?? false;
this.transactionDetails = transactionDetails;
this.useRefreshToken = useRefreshToken ?? false;
this.userId = userId ?? "";
}
}
class LocalStorageStore {
get(key) {
return localStorage.getItem(key);
}
delete(key) {
return localStorage.removeItem(key);
}
save(key, data) {
return localStorage.setItem(key, data);
}
}
function MemoryStore_define_property(obj, key, value) {
if (key in obj) Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
else obj[key] = value;
return obj;
}
class InMemoryStore {
get(key) {
return this.cache.get(key) ?? null;
}
delete(key) {
this.cache.delete(key);
}
save(key, data) {
this.cache.set(key, data);
}
constructor(){
MemoryStore_define_property(this, "cache", new Map());
}
}
function StorageManager_define_property(obj, key, value) {
if (key in obj) Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
else obj[key] = value;
return obj;
}
class StorageManager {
save(storageKey, data) {
this.storage.save(storageKey, data);
}
saveClientParams(data) {
const stringifiedData = JSON.stringify(data);
this.storage.save(this.clientParamsStorageKey, stringifiedData);
}
saveIdToken(data) {
const stringifiedData = JSON.stringify(data);
this.storage.save(this.idTokenStorageKey, stringifiedData);
}
saveTokenParams(data) {
const stringifiedDate = JSON.stringify(data);
this.save(this.tokenParamsStorageKey, stringifiedDate);
}
saveAccessToken(data) {
const accessTokens = this.getAccessTokens();
if (!accessTokens) {
const stringifiedData = JSON.stringify([
data
]);
this.save(this.accessTokenStorageKey, stringifiedData);
return;
}
accessTokens.push(data);
const stringifiedData = JSON.stringify(accessTokens);
this.save(this.accessTokenStorageKey, stringifiedData);
}
removeAccessToken(removedToken) {
const accessTokens = this.getAccessTokens();
if (!accessTokens || 0 === accessTokens.length) return;
const index = accessTokens.findIndex((token)=>token.accessToken === removedToken.accessToken);
if (-1 === index) throw new Error("error removing access token, token not found");
accessTokens.splice(index, 1);
const stringifiedData = JSON.stringify(accessTokens);
this.save(this.accessTokenStorageKey, stringifiedData);
}
removeTokenParams() {
this.storage.delete(this.tokenParamsStorageKey);
}
get(storageKey) {
const data = this.storage.get(storageKey);
if (data) return JSON.parse(data);
}
getClientParams() {
return this.get(this.clientParamsStorageKey);
}
getAccessTokens() {
return this.get(this.accessTokenStorageKey) ?? [];
}
getTokenParams() {
return this.get(this.tokenParamsStorageKey);
}
getIdToken() {
return this.get(this.idTokenStorageKey);
}
remove() {
this.storage.delete(this.clientParamsStorageKey);
this.storage.delete(this.accessTokenStorageKey);
this.storage.delete(this.idTokenStorageKey);
this.storage.delete(this.tokenParamsStorageKey);
}
constructor(clientId, storageType){
StorageManager_define_property(this, "clientParamsStorageKey", void 0);
StorageManager_define_property(this, "accessTokenStorageKey", void 0);
StorageManager_define_property(this, "idTokenStorageKey", void 0);
StorageManager_define_property(this, "tokenParamsStorageKey", void 0);
StorageManager_define_property(this, "storage", void 0);
this.clientParamsStorageKey = `entrust.${clientId}.clientParams`;
this.accessTokenStorageKey = `entrust.${clientId}.accessTokens`;
this.idTokenStorageKey = `entrust.${clientId}.idToken`;
this.tokenParamsStorageKey = `entrust.${clientId}.tokenParams`;
this.storage = "memory" === storageType ? new InMemoryStore() : new LocalStorageStore();
}
}
const validateIdToken = ({ idToken, issuer, clientId, nonce, idTokenSigningAlgValuesSupported, acrValuesSupported })=>{
if (!idToken) throw new Error("No ID token supplied");
let stringifiedToken;
let decodedJwt;
let alg;
try {
if ("string" != typeof idToken) {
stringifiedToken = JSON.stringify(idToken);
decodedJwt = idToken;
alg = "none";
} else {
stringifiedToken = idToken;
decodedJwt = (0, __WEBPACK_EXTERNAL_MODULE_jose__.decodeJwt)(idToken);
alg = (0, __WEBPACK_EXTERNAL_MODULE_jose__.decodeProtectedHeader)(idToken).alg;
}
} catch {
throw new Error("ID token format is neither a valid JSON object nor a signed JWT");
}
if (!decodedJwt.sub) throw new Error("Subject (sub) claim is missing from ID token");
if (!decodedJwt.iat) throw new Error("Issued At (iat) claim is missing from ID token");
if (!decodedJwt.iss) throw new Error("Issuer (iss) claim is missing from ID token");
if (!decodedJwt.aud) throw new Error("Audience (aud) claim is missing from ID token");
if (!decodedJwt.exp) throw new Error("Expiration Time (exp) claim is missing from the ID token");
if (decodedJwt.iss !== issuer) throw new Error(`Issuer (iss) claim ${decodedJwt.iss} in the ID token does not match expected ${issuer}`);
if ("string" == typeof decodedJwt.aud && decodedJwt.aud !== clientId) throw new Error(`Audience (aud) claim ${decodedJwt.aud} in the ID token does not match expected ${clientId}`);
if (Array.isArray(decodedJwt.aud)) {
if (!decodedJwt.aud.includes(clientId)) throw new Error(`Audience (aud) claim array ${decodedJwt.aud} in the ID token does not include expected ${clientId}`);
if (decodedJwt.aud.length > 1) {
const azp = decodedJwt.azp;
if (!azp) throw new Error("Authorized Party (azp) claim is missing from ID token and must be present when there are multiple audiences");
if (azp !== clientId) throw new Error(`Authorized Party (azp) claim ${azp} in the ID token does not match expected ${clientId}`);
}
}
if (!alg) throw new Error("Algorithm (alg) claim is missing from ID token");
if (!idTokenSigningAlgValuesSupported.includes(alg)) throw new Error(`Algorithm (alg) claim ${alg} in the ID token ${alg} is not one of the supported ${idTokenSigningAlgValuesSupported}`);
const leeway = 15;
const now = new Date();
const expDate = new Date((decodedJwt.exp + leeway) * 1000);
if (now > expDate) throw new Error(`Expiration Time (exp) claim ${decodedJwt.exp} indicates that this token is now expired at ${now}`);
if (decodedJwt.nbf) {
const nbfDate = new Date((decodedJwt.nbf - leeway) * 1000);
if (now < nbfDate) throw new Error(`Not Before (nbf) claim ${decodedJwt.nbf} indicates that this token is not to be used yet at ${now}`);
}
const nonceClaim = decodedJwt.nonce;
if (!nonceClaim) throw new Error("Nonce (nonce) claim is missing from ID token");
if (nonceClaim !== nonce) throw new Error(`Nonce (nonce) claim ${nonceClaim} in the ID token does not match expected ${nonce}`);
const acrClaim = decodedJwt.acr;
if (acrClaim && !(null == acrValuesSupported ? void 0 : acrValuesSupported.includes(acrClaim))) throw new Error(`Authentication Context Class Reference (acr) claim ${acrClaim} is not one of the supported ${acrValuesSupported}`);
return {
idToken: stringifiedToken,
decodedJwt
};
};
const validateUserInfoToken = async ({ userInfoToken, issuer, clientId, jwksEndpoint })=>{
try {
(0, __WEBPACK_EXTERNAL_MODULE_jose__.decodeJwt)(userInfoToken);
} catch {
return null;
}
const jwks = (0, __WEBPACK_EXTERNAL_MODULE_jose__.createRemoteJWKSet)(new URL(jwksEndpoint));
const verifiedJwt = await (0, __WEBPACK_EXTERNAL_MODULE_jose__.jwtVerify)(userInfoToken, jwks, {
audience: clientId,
issuer
});
return verifiedJwt.payload;
};
const readAccessToken = (encodedToken)=>{
let decodedToken;
try {
decodedToken = (0, __WEBPACK_EXTERNAL_MODULE_jose__.decodeJwt)(encodedToken);
} catch {
return null;
}
return decodedToken;
};
function IdaasClient_define_property(obj, key, value) {
if (key in obj) Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
else obj[key] = value;
return obj;
}
class IdaasClient {
async login({ audience, scope, redirectUri, useRefreshToken = false, popup = false, acrValues, maxAge } = {}) {
if (popup) {
const popupWindow = openPopup("");
const { response_modes_supported } = await this.getConfig();
const popupSupported = null == response_modes_supported ? void 0 : respon