UNPKG

@entrustcorp/idaas-auth-js

Version:
1,197 lines 69.1 kB
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