UNPKG

@tidecloak/js

Version:

Lightweight browser SDK for integrating TideCloak SSO into any JavaScript application-vanilla, SPA, or framework-agnostic.

1,071 lines 88.3 kB
/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * Modifications Copyright (C) 2025 Tide Foundation Ltd * Tide Protocol - Infrastructure for a TRUE Zero-Trust paradigm * * This modified version is subject to the terms of the Tide Community Open * Code License 2.0 as published by Tide Foundation Limited. You may modify * and redistribute it in accordance with and subject to the terms of that License. * * This program is free software and is subject to the terms of the * Tide Community Open Code License as published by the Tide Foundation Limited. * You may modify it and redistribute it in accordance with and subject to the * terms of that License. This program is distributed WITHOUT WARRANTY of any * kind, including without any implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * See the Tide Community Open Code License for more details. * You should have received a copy of the Tide Community Open Code License along * with this program. If not, see https://tide.org/licenses_tcoc2-0-0-en */ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _TideCloak_instances, _TideCloak_refreshQueue, _TideCloak_adapter, _TideCloak_useNonce, _TideCloak_callbackStorage, _TideCloak_logInfo, _TideCloak_logWarn, _TideCloak_loginIframe, _TideCloak_config, _TideCloak_loadAdapter, _TideCloak_loadDefaultAdapter, _TideCloak_loadCordovaAdapter, _TideCloak_loadCordovaNativeAdapter, _TideCloak_loadConfig, _TideCloak_setupEndpoints, _TideCloak_loadOidcConfig, _TideCloak_setupOidcEndpoints, _TideCloak_check3pCookiesSupported, _TideCloak_processInit, _TideCloak_setupCheckLoginIframe, _TideCloak_checkLoginIframe, _TideCloak_checkSsoSilently, _TideCloak_parseCallback, _TideCloak_parseCallbackUrl, _TideCloak_parseCallbackParams, _TideCloak_processCallback, _TideCloak_scheduleCheckIframe, _TideCloak_getVoucherUrl, _TideCloak_setToken, _TideCloak_getRealmUrl, _TideCloak_createLogger, _LocalStorage_instances, _LocalStorage_clearInvalidValues, _LocalStorage_clearAllValues, _LocalStorage_getStoredEntries, _LocalStorage_parseExpiry, _CookieStorage_instances, _CookieStorage_getCookie, _CookieStorage_setCookie, _CookieStorage_cookieExpiration; // MODIFIED: Added dependency to external Tide helper libraries. import { RequestEnclave, ApprovalEnclave, ApprovalEnclaveNew } from "heimdall-tide"; const CONTENT_TYPE_JSON = 'application/json'; /** * @typedef {Object} Endpoints * @property {() => string} authorize * @property {() => string} token * @property {() => string} logout * @property {() => string} checkSessionIframe * @property {() => string=} thirdPartyCookiesIframe * @property {() => string} register * @property {() => string} userinfo */ /** * @typedef {Object} LoginIframe * @property {boolean} enable * @property {((error: Error | null, value?: boolean) => void)[]} callbackList * @property {number} interval * @property {HTMLIFrameElement=} iframe * @property {string=} iframeOrigin */ export { RequestEnclave, ApprovalEnclave, ApprovalEnclaveNew, TideMemory, BaseTideRequest, PolicySignRequest, Policy, PolicyParameters } from "heimdall-tide"; class TideCloak { /** * @param {KeycloakConfig} config */ constructor(config) { _TideCloak_instances.add(this); /** @type {Pick<PromiseWithResolvers<boolean>, 'resolve' | 'reject'>[]} */ _TideCloak_refreshQueue.set(this, [] /** @type {KeycloakAdapter} */ ); /** @type {KeycloakAdapter} */ _TideCloak_adapter.set(this, void 0); /** @type {boolean} */ _TideCloak_useNonce.set(this, true /** @type {CallbackStorage} */ ); /** @type {CallbackStorage} */ _TideCloak_callbackStorage.set(this, void 0); _TideCloak_logInfo.set(this, __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_createLogger).call(this, console.info)); _TideCloak_logWarn.set(this, __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_createLogger).call(this, console.warn) /** @type {LoginIframe} */ ); /** @type {LoginIframe} */ _TideCloak_loginIframe.set(this, { enable: true, callbackList: [], interval: 5 } /** @type {KeycloakConfig} config */ ); /** @type {KeycloakConfig} config */ _TideCloak_config.set(this, void 0); this.didInitialize = false; this.authenticated = false; this.loginRequired = false; /** @type {KeycloakResponseMode} */ this.responseMode = 'fragment'; /** @type {KeycloakResponseType} */ this.responseType = 'code'; /** @type {KeycloakFlow} */ this.flow = 'standard'; /** @type {boolean} */ this.silentCheckSsoFallback = true; /** @type {KeycloakPkceMethod} */ this.pkceMethod = 'S256'; this.enableLogging = false; /** @type {'GET' | 'POST'} */ this.logoutMethod = 'GET'; this.messageReceiveTimeout = 10000; if (typeof config !== 'string' && !isObject(config)) { throw new Error("The 'TideCloak' constructor must be provided with a configuration object, or a URL to a JSON configuration file."); } // if (isObject(config)) { // const requiredProperties = 'oidcProvider' in config // ? ['clientId'] // : ['url', 'realm', 'clientId', 'homeOrkUrl', 'vendorId', 'clientOriginAuth'] // for (const property of requiredProperties) { // if (!config[property]) { // throw new Error(`The configuration object is missing the required '${property}' property.`) // } // } // } if (!globalThis.isSecureContext) { __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, "[TIDECLOAK] TideCloak JS must be used in a 'secure context' to function properly as it relies on browser APIs that are otherwise not available.\n" + 'Continuing to run your application insecurely will lead to unexpected behavior and breakage.\n\n' + 'For more information see: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts'); } __classPrivateFieldSet(this, _TideCloak_config, config, "f"); } /** * @param {KeycloakInitOptions} initOptions * @returns {Promise<boolean>} */ async init(initOptions = {}) { var _a; if (this.didInitialize) { throw new Error("A 'TideCloak' instance can only be initialized once."); } this.didInitialize = true; __classPrivateFieldSet(this, _TideCloak_callbackStorage, createCallbackStorage(), "f"); const adapters = ['default', 'cordova', 'cordova-native']; if (typeof initOptions.adapter === 'string' && adapters.includes(initOptions.adapter)) { __classPrivateFieldSet(this, _TideCloak_adapter, __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_loadAdapter).call(this, initOptions.adapter), "f"); } else if (typeof initOptions.adapter === 'object') { __classPrivateFieldSet(this, _TideCloak_adapter, initOptions.adapter, "f"); } else if ('Cordova' in window || 'cordova' in window) { __classPrivateFieldSet(this, _TideCloak_adapter, __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_loadAdapter).call(this, 'cordova'), "f"); } else { __classPrivateFieldSet(this, _TideCloak_adapter, __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_loadAdapter).call(this, 'default'), "f"); } if (typeof initOptions.useNonce !== 'undefined') { __classPrivateFieldSet(this, _TideCloak_useNonce, initOptions.useNonce, "f"); } if (typeof initOptions.checkLoginIframe !== 'undefined') { __classPrivateFieldGet(this, _TideCloak_loginIframe, "f").enable = initOptions.checkLoginIframe; } if (initOptions.checkLoginIframeInterval) { __classPrivateFieldGet(this, _TideCloak_loginIframe, "f").interval = initOptions.checkLoginIframeInterval; } if (initOptions.onLoad === 'login-required') { this.loginRequired = true; } if (initOptions.responseMode) { if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') { this.responseMode = initOptions.responseMode; } else { throw new Error('Invalid value for responseMode'); } } if (initOptions.flow) { switch (initOptions.flow) { case 'standard': this.responseType = 'code'; break; case 'implicit': this.responseType = 'id_token token'; break; case 'hybrid': this.responseType = 'code id_token token'; break; default: throw new Error('Invalid value for flow'); } this.flow = initOptions.flow; } if (typeof initOptions.timeSkew === 'number') { this.timeSkew = initOptions.timeSkew; } if (initOptions.redirectUri) { this.redirectUri = initOptions.redirectUri; } if (initOptions.silentCheckSsoRedirectUri) { this.silentCheckSsoRedirectUri = initOptions.silentCheckSsoRedirectUri; } if (typeof initOptions.silentCheckSsoFallback === 'boolean') { this.silentCheckSsoFallback = initOptions.silentCheckSsoFallback; } if (typeof initOptions.pkceMethod !== 'undefined') { if (initOptions.pkceMethod !== 'S256' && initOptions.pkceMethod !== false) { throw new TypeError(`Invalid value for pkceMethod', expected 'S256' or false but got ${initOptions.pkceMethod}.`); } this.pkceMethod = initOptions.pkceMethod; } if (typeof initOptions.enableLogging === 'boolean') { this.enableLogging = initOptions.enableLogging; } if (initOptions.logoutMethod === 'POST') { this.logoutMethod = 'POST'; } if (typeof initOptions.scope === 'string') { this.scope = initOptions.scope; } if (typeof initOptions.acrValues === 'string') { this.acrValues = initOptions.acrValues; } if (typeof initOptions.messageReceiveTimeout === 'number' && initOptions.messageReceiveTimeout > 0) { this.messageReceiveTimeout = initOptions.messageReceiveTimeout; } await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_loadConfig).call(this); await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_check3pCookiesSupported).call(this); await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processInit).call(this, initOptions); (_a = this.onReady) === null || _a === void 0 ? void 0 : _a.call(this, this.authenticated); return this.authenticated; } ; /** * @param {KeycloakLoginOptions} [options] * @returns {Promise<void>} */ login(options) { return __classPrivateFieldGet(this, _TideCloak_adapter, "f").login(options); } /** * Ensure the access token is valid, refreshing if needed. * @returns {Promise<void>} */ async ensureTokenReady() { if (!this.tokenParsed) return; if (this.isTokenExpired()) { await this.updateToken(-1); } } /** * @param {KeycloakLoginOptions} [options] * @returns {Promise<string>} */ async createLoginUrl(options) { const state = createUUID(); const nonce = createUUID(); const redirectUri = __classPrivateFieldGet(this, _TideCloak_adapter, "f").redirectUri(options); /** @type {CallbackState} */ const callbackState = { state, nonce, redirectUri, loginOptions: options }; if (options === null || options === void 0 ? void 0 : options.prompt) { callbackState.prompt = options.prompt; } const url = (options === null || options === void 0 ? void 0 : options.action) === 'register' ? this.endpoints.register() : this.endpoints.authorize(); let scope = (options === null || options === void 0 ? void 0 : options.scope) || this.scope; const scopeValues = scope ? scope.split(' ') : []; // Ensure the 'openid' scope is always included. if (!scopeValues.includes('openid')) { scopeValues.unshift('openid'); } scope = scopeValues.join(' '); const params = new URLSearchParams([ ['client_id', /** @type {string} */ (this.clientId)], // The endpoint URI MUST NOT include a fragment component. // https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2 ['redirect_uri', stripHash(redirectUri)], ['state', state], ['response_mode', this.responseMode], ['response_type', this.responseType], ['scope', scope] ]); if (__classPrivateFieldGet(this, _TideCloak_useNonce, "f")) { params.append('nonce', nonce); } if (options === null || options === void 0 ? void 0 : options.prompt) { params.append('prompt', options.prompt); } if (typeof (options === null || options === void 0 ? void 0 : options.maxAge) === 'number') { params.append('max_age', options.maxAge.toString()); } if (options === null || options === void 0 ? void 0 : options.loginHint) { params.append('login_hint', options.loginHint); } if (options === null || options === void 0 ? void 0 : options.idpHint) { params.append('kc_idp_hint', options.idpHint); } if ((options === null || options === void 0 ? void 0 : options.action) && options.action !== 'register') { params.append('kc_action', options.action); } if (options === null || options === void 0 ? void 0 : options.locale) { params.append('ui_locales', options.locale); } if (options === null || options === void 0 ? void 0 : options.acr) { params.append('claims', buildClaimsParameter(options.acr)); } if ((options === null || options === void 0 ? void 0 : options.acrValues) || this.acrValues) { params.append('acr_values', options.acrValues || this.acrValues); } if (this.pkceMethod) { try { const codeVerifier = generateCodeVerifier(96); const pkceChallenge = await generatePkceChallenge(this.pkceMethod, codeVerifier); callbackState.pkceCodeVerifier = codeVerifier; params.append('code_challenge', pkceChallenge); params.append('code_challenge_method', this.pkceMethod); } catch (error) { throw new Error('Failed to generate PKCE challenge.', { cause: error }); } } __classPrivateFieldGet(this, _TideCloak_callbackStorage, "f").add(callbackState); return `${url}?${params.toString()}`; } /** * @param {KeycloakLogoutOptions} [options] * @returns {Promise<void>} */ logout(options) { return __classPrivateFieldGet(this, _TideCloak_adapter, "f").logout(options); } /** * @param {KeycloakLogoutOptions} [options] * @returns {string} */ createLogoutUrl(options) { var _a; const logoutMethod = (_a = options === null || options === void 0 ? void 0 : options.logoutMethod) !== null && _a !== void 0 ? _a : this.logoutMethod; const url = this.endpoints.logout(); if (logoutMethod === 'POST') { return url; } const params = new URLSearchParams([ ['client_id', /** @type {string} */ (this.clientId)], ['post_logout_redirect_uri', __classPrivateFieldGet(this, _TideCloak_adapter, "f").redirectUri(options)] ]); if (this.idToken) { params.append('id_token_hint', this.idToken); } return `${url}?${params.toString()}`; } /** * @param {KeycloakRegisterOptions} [options] * @returns {Promise<void>} */ register(options) { return __classPrivateFieldGet(this, _TideCloak_adapter, "f").register(options); } /** * @param {KeycloakRegisterOptions} [options] * @returns {Promise<string>} */ createRegisterUrl(options) { return this.createLoginUrl({ ...options, action: 'register' }); } /** * @param {KeycloakAccountOptions} [options] * @returns {string} */ createAccountUrl(options) { const url = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getRealmUrl).call(this); if (!url) { throw new Error('Unable to create account URL, make sure the adapter is not configured using a generic OIDC provider.'); } const params = new URLSearchParams([ ['referrer', /** @type {string} */ (this.clientId)], ['referrer_uri', __classPrivateFieldGet(this, _TideCloak_adapter, "f").redirectUri(options)] ]); return `${url}/account?${params.toString()}`; } /** * @returns {Promise<void>} */ accountManagement() { return __classPrivateFieldGet(this, _TideCloak_adapter, "f").accountManagement(); } /** * @param {string} role * @returns {boolean} */ hasRealmRole(role) { const access = this.realmAccess; return !!access && access.roles.indexOf(role) >= 0; } /** * @param {string} role * @param {string} [resource] * @returns {boolean} */ hasResourceRole(role, resource) { if (!this.resourceAccess) { return false; } const access = this.resourceAccess[resource || /** @type {string} */ (this.clientId)]; return !!access && access.roles.indexOf(role) >= 0; } /** * @returns {Promise<KeycloakProfile>} */ async loadUserProfile() { const realmUrl = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getRealmUrl).call(this); if (!realmUrl) { throw new Error('Unable to load user profile, make sure the adapter is not configured using a generic OIDC provider.'); } const url = `${realmUrl}/account`; /** @type {KeycloakProfile} */ const profile = await fetchJSON(url, { headers: [buildAuthorizationHeader(this.token)] }); return (this.profile = profile); } /** * @returns {Promise<KeycloakUserInfo>} */ async loadUserInfo() { const url = this.endpoints.userinfo(); /** @type {KeycloakUserInfo} */ const userInfo = await fetchJSON(url, { headers: [buildAuthorizationHeader(this.token)] }); return (this.userInfo = userInfo); } /** * @param {number} [minValidity] * @returns {boolean} */ isTokenExpired(minValidity) { if (!this.tokenParsed || (!this.refreshToken && this.flow !== 'implicit')) { throw new Error('Not authenticated'); } if (this.timeSkew == null) { __classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDECLOAK] Unable to determine if token is expired as timeskew is not set'); return true; } if (typeof this.tokenParsed.exp !== 'number') { return false; } let expiresIn = this.tokenParsed.exp - Math.ceil(new Date().getTime() / 1000) + this.timeSkew; if (minValidity) { if (isNaN(minValidity)) { throw new Error('Invalid minValidity'); } expiresIn -= minValidity; } return expiresIn < 0; } /** * Matches Keycloak: minValidity is optional. * @param {number} [minValidity] * @returns {Promise<boolean>} */ async updateToken(minValidity) { var _a, _b; if (!this.refreshToken) { throw new Error('Unable to update token, no refresh token available.'); } minValidity = minValidity || 5; if (__classPrivateFieldGet(this, _TideCloak_loginIframe, "f").enable) { await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_checkLoginIframe).call(this); } let refreshToken = false; if (minValidity === -1) { refreshToken = true; __classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDECLOAK] Refreshing token: forced refresh'); } else if (!this.tokenParsed || this.isTokenExpired(minValidity)) { refreshToken = true; __classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDECLOAK] Refreshing token: token expired'); } if (!refreshToken) { return false; } /** @type {PromiseWithResolvers<boolean>} */ const { promise, resolve, reject } = Promise.withResolvers(); __classPrivateFieldGet(this, _TideCloak_refreshQueue, "f").push({ resolve, reject }); if (__classPrivateFieldGet(this, _TideCloak_refreshQueue, "f").length === 1) { const url = this.endpoints.token(); let timeLocal = new Date().getTime(); try { const response = await fetchRefreshToken(url, this.refreshToken, /** @type {string} */ (this.clientId)); __classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDECLOAK] Token refreshed'); timeLocal = (timeLocal + new Date().getTime()) / 2; __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_setToken).call(this, response.access_token, response.refresh_token, response.id_token, timeLocal, response.doken); (_a = this.onAuthRefreshSuccess) === null || _a === void 0 ? void 0 : _a.call(this); for (let p = __classPrivateFieldGet(this, _TideCloak_refreshQueue, "f").pop(); p != null; p = __classPrivateFieldGet(this, _TideCloak_refreshQueue, "f").pop()) { p.resolve(true); } } catch (error) { __classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDECLOAK] Failed to refresh token'); if (error instanceof NetworkError && error.response.status === 400) { this.clearToken(); } (_b = this.onAuthRefreshError) === null || _b === void 0 ? void 0 : _b.call(this); for (let p = __classPrivateFieldGet(this, _TideCloak_refreshQueue, "f").pop(); p != null; p = __classPrivateFieldGet(this, _TideCloak_refreshQueue, "f").pop()) { p.reject(error); } } } return await promise; } clearToken() { var _a; if (this.token) { __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_setToken).call(this); (_a = this.onAuthLogout) === null || _a === void 0 ? void 0 : _a.call(this); if (this.loginRequired) { this.login(); } } } /** * Initialize Tide RequestEnclave. */ initRequestEnclave() { if (!this.doken) throw new Error('[TIDECLOAK] No doken found'); if (!this.dokenParsed) throw new Error('[TIDECLOAK] Token not parsed'); if (!this.requestEnclave) { this.requestEnclave = new RequestEnclave({ homeOrkOrigin: this.dokenParsed['t.uho'], signed_client_origin: __classPrivateFieldGet(this, _TideCloak_config, "f")['clientOriginAuth'], vendorId: __classPrivateFieldGet(this, _TideCloak_config, "f").vendorId, voucherURL: __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getVoucherUrl).call(this) }).init({ doken: this.doken, dokenRefreshCallback: async () => { await this.ensureTokenReady(); if (!this.doken) throw new Error('[TIDECLOAK] No doken found'); return this.doken; }, requireReloginCallback: async () => { await this.login({ idpHint: 'tide', prompt: 'login', redirectUri: window.location.href }); } }); } } /** * Initialize Tide ApprovalEnclave. */ initApprovalEnclave() { if (!this.doken) throw new Error('[TIDECLOAK] No doken found'); if (!this.dokenParsed) throw new Error('[TIDECLOAK] Token not parsed'); if (!this.approvalEnclave) { this.approvalEnclave = new ApprovalEnclaveNew({ homeOrkOrigin: this.dokenParsed['t.uho'], signed_client_origin: __classPrivateFieldGet(this, _TideCloak_config, "f")['clientOriginAuth'], vendorId: __classPrivateFieldGet(this, _TideCloak_config, "f").vendorId, voucherURL: __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getVoucherUrl).call(this) }).init({ doken: this.doken, dokenRefreshCallback: async () => { await this.ensureTokenReady(); if (!this.doken) throw new Error('[TIDECLOAK] No doken found'); return this.doken; }, requireReloginCallback: async () => { await this.login({ idpHint: 'tide', prompt: 'login', redirectUri: window.location.href }); } }); } } /** * Role-based encryption via Tide RequestEnclave. * @param {{ data: string | Uint8Array, tags: string[] }[]} toEncrypt * @returns {Promise<(string | Uint8Array)[]>} */ async encrypt(toEncrypt) { await this.ensureTokenReady(); if (!Array.isArray(toEncrypt)) { throw new Error('Pass array as parameter'); } if (!this.tokenParsed) { throw new Error('Not authenticated'); } const dataToSend = toEncrypt.map((e) => { if (!isObject(e)) throw new Error('All entries must be an object to encrypt'); for (const property of ['data', 'tags']) { if (!e[property]) { throw new Error(`The configuration object is missing the required '${property}' property.`); } } if (!Array.isArray(e.tags)) throw new Error('tags must be provided as a string array in object to encrypt'); if (typeof e.data !== 'string' && !(e.data instanceof Uint8Array)) { throw new Error('data must be provided as string or Uint8Array in object to encrypt'); } for (const tag of e.tags) { if (typeof tag !== 'string') throw new Error('tags must be provided as an array of strings'); const tagAccess = this.hasRealmRole(`_tide_${tag}.selfencrypt`); if (!tagAccess) throw new Error(`User has not been given any access to '${tag}'`); } return { data: typeof e.data === 'string' ? StringToUint8Array(e.data) : e.data, tags: e.tags, isRaw: typeof e.data === 'string' ? false : true }; }); this.initRequestEnclave(); const encrypted = await this.requestEnclave.encrypt(dataToSend); return encrypted.map((cipher, i) => (dataToSend[i].isRaw ? cipher : bytesToBase64(cipher))); } /** * Initialize a Tide request that requires operator approvals. * @param {Uint8Array} encodedRequest * @returns {Promise<Uint8Array>} */ async createTideRequest(encodedRequest) { await this.ensureTokenReady(); this.initRequestEnclave(); return await this.requestEnclave.initializeRequest(encodedRequest); } /** * Request Tide operator approval. * @param {{id: string, request: Uint8Array}[]} requests * @returns {Promise<{approved: {id: string, request: Uint8Array}[], denied: {id: string}[], pending: {id: string}[]}>} */ async requestTideOperatorApproval(requests) { await this.ensureTokenReady(); this.initApprovalEnclave(); return await this.approvalEnclave.approve(requests); } /** * Execute a Tide Sign Request * @param {Uint8Array} request * @returns Array of signatures */ async executeSignRequest(request) { await this.ensureTokenReady(); this.initRequestEnclave(); return await this.requestEnclave.execute(request); } /** * Role-based decryption via Tide RequestEnclave. * @param {{ encrypted: string | Uint8Array, tags: string[] }[]} toDecrypt * @returns {Promise<(string | Uint8Array)[]>} */ async decrypt(toDecrypt) { await this.ensureTokenReady(); if (!Array.isArray(toDecrypt)) { throw new Error('Pass array as parameter'); } if (!this.tokenParsed) { throw new Error('Not authenticated'); } const dataToSend = toDecrypt.map((e) => { if (!isObject(e)) throw new Error('All entries must be an object to decrypt'); for (const property of ['encrypted', 'tags']) { if (!e[property]) { throw new Error(`The configuration object is missing the required '${property}' property.`); } } if (!Array.isArray(e.tags)) throw new Error('tags must be provided as a string array in object to decrypt'); if (typeof e.encrypted !== 'string' && !(e.encrypted instanceof Uint8Array)) { throw new Error('encrypted must be provided as string or Uint8Array in object to decrypt'); } for (const tag of e.tags) { if (typeof tag !== 'string') throw new Error('tags must be provided as an array of strings'); const tagAccess = this.hasRealmRole(`_tide_${tag}.selfdecrypt`); if (!tagAccess) throw new Error(`User has not been given any access to '${tag}'`); } return { encrypted: typeof e.encrypted === 'string' ? base64ToBytes(e.encrypted) : e.encrypted, tags: e.tags, isRaw: typeof e.encrypted === 'string' ? false : true }; }); this.initRequestEnclave(); const decrypted = await this.requestEnclave.decrypt(dataToSend); return decrypted.map((d, i) => (dataToSend[i].isRaw ? d : StringFromUint8Array(d))); } } _TideCloak_refreshQueue = new WeakMap(), _TideCloak_adapter = new WeakMap(), _TideCloak_useNonce = new WeakMap(), _TideCloak_callbackStorage = new WeakMap(), _TideCloak_logInfo = new WeakMap(), _TideCloak_logWarn = new WeakMap(), _TideCloak_loginIframe = new WeakMap(), _TideCloak_config = new WeakMap(), _TideCloak_instances = new WeakSet(), _TideCloak_loadAdapter = function _TideCloak_loadAdapter(type) { if (type === 'default') { return __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_loadDefaultAdapter).call(this); } if (type === 'cordova') { __classPrivateFieldGet(this, _TideCloak_loginIframe, "f").enable = false; return __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_loadCordovaAdapter).call(this); } if (type === 'cordova-native') { __classPrivateFieldGet(this, _TideCloak_loginIframe, "f").enable = false; return __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_loadCordovaNativeAdapter).call(this); } throw new Error('invalid adapter type: ' + type); }, _TideCloak_loadDefaultAdapter = function _TideCloak_loadDefaultAdapter() { /** @type {KeycloakAdapter['redirectUri']}{} */ const redirectUri = (options) => { return (options === null || options === void 0 ? void 0 : options.redirectUri) || this.redirectUri || globalThis.location.href; }; return { login: async (options) => { window.location.assign(await this.createLoginUrl(options)); return await new Promise(() => { }); }, logout: async (options) => { var _a; const logoutMethod = (_a = options === null || options === void 0 ? void 0 : options.logoutMethod) !== null && _a !== void 0 ? _a : this.logoutMethod; if (logoutMethod === 'GET') { window.location.replace(this.createLogoutUrl(options)); return; } // Create form to send POST request. const form = document.createElement('form'); form.setAttribute('method', 'POST'); form.setAttribute('action', this.createLogoutUrl(options)); form.style.display = 'none'; // Add data to form as hidden input fields. const data = { id_token_hint: this.idToken, client_id: this.clientId, post_logout_redirect_uri: redirectUri(options) }; for (const [name, value] of Object.entries(data)) { const input = document.createElement('input'); input.setAttribute('type', 'hidden'); input.setAttribute('name', name); input.setAttribute('value', /** @type {string} */ (value)); form.appendChild(input); } // Append form to page and submit it to perform logout and redirect. document.body.appendChild(form); form.submit(); }, register: async (options) => { window.location.assign(await this.createRegisterUrl(options)); return await new Promise(() => { }); }, accountManagement: async () => { const accountUrl = this.createAccountUrl(); if (typeof accountUrl !== 'undefined') { window.location.href = accountUrl; } else { throw new Error('Not supported by the OIDC server'); } return await new Promise(() => { }); }, redirectUri }; }, _TideCloak_loadCordovaAdapter = function _TideCloak_loadCordovaAdapter() { /** * @param {string} loginUrl * @param {string} target * @param {string} options * @returns {WindowProxy | null} */ const cordovaOpenWindowWrapper = (loginUrl, target, options) => { if (window.cordova && window.cordova.InAppBrowser) { // Use inappbrowser for IOS and Android if available return window.cordova.InAppBrowser.open(loginUrl, target, options); } else { return window.open(loginUrl, target, options); } }; const shallowCloneCordovaOptions = (userOptions) => { if (userOptions && userOptions.cordovaOptions) { return Object.keys(userOptions.cordovaOptions).reduce((options, optionName) => { options[optionName] = userOptions.cordovaOptions[optionName]; return options; }, {}); } else { return {}; } }; const formatCordovaOptions = (cordovaOptions) => { return Object.keys(cordovaOptions).reduce((options, optionName) => { options.push(optionName + '=' + cordovaOptions[optionName]); return options; }, []).join(','); }; const createCordovaOptions = (userOptions) => { const cordovaOptions = shallowCloneCordovaOptions(userOptions); cordovaOptions.location = 'no'; if (userOptions && userOptions.prompt === 'none') { cordovaOptions.hidden = 'yes'; } return formatCordovaOptions(cordovaOptions); }; const getCordovaRedirectUri = () => { return this.redirectUri || 'http://localhost'; }; return { login: async (options) => { const cordovaOptions = createCordovaOptions(options); const loginUrl = await this.createLoginUrl(options); const ref = cordovaOpenWindowWrapper(loginUrl, '_blank', cordovaOptions); let completed = false; let closed = false; function closeBrowser() { closed = true; ref.close(); } ; return await new Promise((resolve, reject) => { ref.addEventListener('loadstart', async (event) => { if (event.url.indexOf(getCordovaRedirectUri()) === 0) { const callback = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallback).call(this, event.url); try { await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processCallback).call(this, callback); resolve(); } catch (error) { reject(error); } closeBrowser(); completed = true; } }); ref.addEventListener('loaderror', async (event) => { if (!completed) { if (event.url.indexOf(getCordovaRedirectUri()) === 0) { const callback = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallback).call(this, event.url); try { await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processCallback).call(this, callback); resolve(); } catch (error) { reject(error); } closeBrowser(); completed = true; } else { reject(new Error('Unable to process login.')); closeBrowser(); } } }); ref.addEventListener('exit', function (event) { if (!closed) { reject(new Error('User closed the login window.')); } }); }); }, logout: async (options) => { const logoutUrl = this.createLogoutUrl(options); const ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes,clearcache=yes'); let error = false; ref.addEventListener('loadstart', (event) => { if (event.url.indexOf(getCordovaRedirectUri()) === 0) { ref.close(); } }); ref.addEventListener('loaderror', (event) => { if (event.url.indexOf(getCordovaRedirectUri()) === 0) { ref.close(); } else { error = true; ref.close(); } }); await new Promise((resolve, reject) => { ref.addEventListener('exit', () => { if (error) { reject(new Error('User closed the login window.')); } else { this.clearToken(); resolve(); } }); }); }, register: async (options) => { const registerUrl = await this.createRegisterUrl(); const cordovaOptions = createCordovaOptions(options); const ref = cordovaOpenWindowWrapper(registerUrl, '_blank', cordovaOptions); /** @type {Promise<void>} */ const promise = new Promise((resolve, reject) => { ref.addEventListener('loadstart', async (event) => { if (event.url.indexOf(getCordovaRedirectUri()) === 0) { ref.close(); const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallback).call(this, event.url); try { await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processCallback).call(this, oauth); resolve(); } catch (error) { reject(error); } } }); }); await promise; }, accountManagement: async () => { const accountUrl = this.createAccountUrl(); if (typeof accountUrl !== 'undefined') { const ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no'); ref.addEventListener('loadstart', function (event) { if (event.url.indexOf(getCordovaRedirectUri()) === 0) { ref.close(); } }); } else { throw new Error('Not supported by the OIDC server'); } }, redirectUri: () => { return getCordovaRedirectUri(); } }; }, _TideCloak_loadCordovaNativeAdapter = function _TideCloak_loadCordovaNativeAdapter() { /* global universalLinks */ return { login: async (options) => { const loginUrl = await this.createLoginUrl(options); await new Promise((resolve, reject) => { universalLinks.subscribe('keycloak', async (event) => { universalLinks.unsubscribe('keycloak'); window.cordova.plugins.browsertab.close(); const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallback).call(this, event.url); try { await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processCallback).call(this, oauth); resolve(); } catch (error) { reject(error); } }); window.cordova.plugins.browsertab.openUrl(loginUrl); }); }, logout: async (options) => { const logoutUrl = this.createLogoutUrl(options); await new Promise((resolve) => { universalLinks.subscribe('keycloak', () => { universalLinks.unsubscribe('keycloak'); window.cordova.plugins.browsertab.close(); this.clearToken(); resolve(); }); window.cordova.plugins.browsertab.openUrl(logoutUrl); }); }, register: async (options) => { const registerUrl = await this.createRegisterUrl(options); await new Promise((resolve, reject) => { universalLinks.subscribe('keycloak', async (event) => { universalLinks.unsubscribe('keycloak'); window.cordova.plugins.browsertab.close(); const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallback).call(this, event.url); try { await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processCallback).call(this, oauth); resolve(); } catch (error) { reject(error); } }); window.cordova.plugins.browsertab.openUrl(registerUrl); }); }, accountManagement: async () => { const accountUrl = this.createAccountUrl(); if (typeof accountUrl !== 'undefined') { window.cordova.plugins.browsertab.openUrl(accountUrl); } else { throw new Error('Not supported by the OIDC server'); } }, redirectUri: (options) => { if (options && options.redirectUri) { return options.redirectUri; } else if (this.redirectUri) { return this.redirectUri; } else { return 'http://localhost'; } } }; }, _TideCloak_loadConfig = /** * @returns {Promise<void>} */ async function _TideCloak_loadConfig() { if (typeof __classPrivateFieldGet(this, _TideCloak_config, "f") === 'string') { const jsonConfig = await fetchJsonConfig(__classPrivateFieldGet(this, _TideCloak_config, "f")); this.authServerUrl = jsonConfig['auth-server-url']; this.realm = jsonConfig.realm; this.clientId = jsonConfig.resource; __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_setupEndpoints).call(this); } else { this.clientId = __classPrivateFieldGet(this, _TideCloak_config, "f").clientId; if ('oidcProvider' in __classPrivateFieldGet(this, _TideCloak_config, "f")) { await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_loadOidcConfig).call(this, __classPrivateFieldGet(this, _TideCloak_config, "f").oidcProvider); } else { this.authServerUrl = __classPrivateFieldGet(this, _TideCloak_config, "f").url; this.realm = __classPrivateFieldGet(this, _TideCloak_config, "f").realm; __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_setupEndpoints).call(this); } } }, _TideCloak_setupEndpoints = function _TideCloak_setupEndpoints() { this.endpoints = { authorize: () => { return __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getRealmUrl).call(this) + '/protocol/openid-connect/auth'; }, token: () => { return __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getRealmUrl).call(this) + '/protocol/openid-connect/token'; }, logout: () => { return __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getRealmUrl).call(this) + '/protocol/openid-connect/logout'; }, checkSessionIframe: () => { return __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getRealmUrl).call(this) + '/protocol/openid-connect/login-status-iframe.html'; }, thirdPartyCookiesIframe: () => { return __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getRealmUrl).call(this) + '/protocol/openid-connect/3p-cookies/step1.html'; }, register: () => { return __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getRealmUrl).call(this) + '/protocol/openid-connect/registrations'; }, userinfo: () => { return __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getRealmUrl).call(this) + '/protocol/openid-connect/userinfo'; } }; }, _TideCloak_loadOidcConfig = /** * @param {string | OpenIdProviderMetadata} oidcProvider * @returns {Promise<void>} */ async function _TideCloak_loadOidcConfig(oidcProvider) { if (typeof oidcProvider === 'string') { const url = `${stripTrailingSlash(oidcProvider)}/.well-known/openid-configuration`; const openIdConfig = await fetchOpenIdConfig(url); __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_setupOidcEndpoints).call(this, openIdConfig); } else { __classPriv