@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
JavaScript
/*
* 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