UNPKG

@axa-fr/oidc-client-service-worker

Version:

OpenID Connect & OAuth authentication service worker

946 lines (945 loc) 37.3 kB
const scriptFilename = "OidcTrustedDomains.js"; const acceptAnyDomainToken = "*"; const TOKEN = { REFRESH_TOKEN: "REFRESH_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER", ACCESS_TOKEN: "ACCESS_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER", NONCE_TOKEN: "NONCE_SECURED_BY_OIDC_SERVICE_WORKER", CODE_VERIFIER: "CODE_VERIFIER_SECURED_BY_OIDC_SERVICE_WORKER" }; const TokenRenewMode = { access_token_invalid: "access_token_invalid", id_token_invalid: "id_token_invalid" }; const openidWellknownUrlEndWith = "/.well-known/openid-configuration"; function strToUint8(str) { return new TextEncoder().encode(str); } function binToUrlBase64(bin) { return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+/g, ""); } function utf8ToBinaryString(str) { const escstr = encodeURIComponent(str); return escstr.replace(/%([0-9A-F]{2})/g, function(match, p1) { return String.fromCharCode(parseInt(p1, 16)); }); } const uint8ToUrlBase64 = (uint8) => { let bin = ""; uint8.forEach(function(code) { bin += String.fromCharCode(code); }); return binToUrlBase64(bin); }; function strToUrlBase64(str) { return binToUrlBase64(utf8ToBinaryString(str)); } const defaultDemonstratingProofOfPossessionConfiguration = { importKeyAlgorithm: { name: "ECDSA", namedCurve: "P-256", hash: { name: "ES256" } }, signAlgorithm: { name: "ECDSA", hash: { name: "SHA-256" } }, generateKeyAlgorithm: { name: "ECDSA", namedCurve: "P-256" }, digestAlgorithm: { name: "SHA-256" }, jwtHeaderAlgorithm: "ES256" }; const sign = (w) => async (jwk, headers, claims, demonstratingProofOfPossessionConfiguration, jwtHeaderType = "dpop+jwt") => { jwk = Object.assign({}, jwk); headers.typ = jwtHeaderType; headers.alg = demonstratingProofOfPossessionConfiguration.jwtHeaderAlgorithm; switch (headers.alg) { case "ES256": headers.jwk = { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y }; break; case "RS256": headers.jwk = { kty: jwk.kty, n: jwk.n, e: jwk.e, kid: headers.kid }; break; default: throw new Error("Unknown or not implemented JWS algorithm"); } const jws = { // @ts-ignore // JWT "headers" really means JWS "protected headers" protected: strToUrlBase64(JSON.stringify(headers)), // @ts-ignore // JWT "claims" are really a JSON-defined JWS "payload" payload: strToUrlBase64(JSON.stringify(claims)) }; const keyType = demonstratingProofOfPossessionConfiguration.importKeyAlgorithm; const exportable = true; const privileges = ["sign"]; const privateKey = await w.crypto.subtle.importKey("jwk", jwk, keyType, exportable, privileges); const data = strToUint8(`${jws.protected}.${jws.payload}`); const signatureType = demonstratingProofOfPossessionConfiguration.signAlgorithm; const signature = await w.crypto.subtle.sign(signatureType, privateKey, data); jws.signature = uint8ToUrlBase64(new Uint8Array(signature)); return `${jws.protected}.${jws.payload}.${jws.signature}`; }; const JWT = { sign }; const generate = (w) => async (generateKeyAlgorithm) => { const keyType = generateKeyAlgorithm; const exportable = true; const privileges = ["sign", "verify"]; const key = await w.crypto.subtle.generateKey(keyType, exportable, privileges); return await w.crypto.subtle.exportKey("jwk", key.privateKey); }; const neuter = (jwk) => { const copy = Object.assign({}, jwk); delete copy.d; copy.key_ops = ["verify"]; return copy; }; const EC = { generate, neuter }; const thumbprint = (w) => async (jwk, digestAlgorithm) => { let sortedPub; switch (jwk.kty) { case "EC": sortedPub = '{"crv":"CRV","kty":"EC","x":"X","y":"Y"}'.replace("CRV", jwk.crv).replace("X", jwk.x).replace("Y", jwk.y); break; case "RSA": sortedPub = '{"e":"E","kty":"RSA","n":"N"}'.replace("E", jwk.e).replace("N", jwk.n); break; default: throw new Error("Unknown or not implemented JWK type"); } const hash = await w.crypto.subtle.digest(digestAlgorithm, strToUint8(sortedPub)); return uint8ToUrlBase64(new Uint8Array(hash)); }; const JWK = { thumbprint }; const generateJwkAsync = (w) => async (generateKeyAlgorithm) => { const jwk = await EC.generate(w)(generateKeyAlgorithm); return jwk; }; const generateJwtDemonstratingProofOfPossessionAsync = (w) => (demonstratingProofOfPossessionConfiguration) => async (jwk, method = "POST", url, extrasClaims = {}) => { const claims = { // https://www.rfc-editor.org/rfc/rfc9449.html#name-concept jti: btoa(guid()), htm: method, htu: url, iat: Math.round(Date.now() / 1e3), ...extrasClaims }; const kid = await JWK.thumbprint(w)( jwk, demonstratingProofOfPossessionConfiguration.digestAlgorithm ); const jwt = await JWT.sign(w)( jwk, { kid }, claims, demonstratingProofOfPossessionConfiguration ); return jwt; }; const guid = () => { const guidHolder = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"; const hex = "0123456789abcdef"; let r = 0; let guidResponse = ""; for (let i = 0; i < 36; i++) { if (guidHolder[i] !== "-" && guidHolder[i] !== "4") { r = Math.random() * 16 | 0; } if (guidHolder[i] === "x") { guidResponse += hex[r]; } else if (guidHolder[i] === "y") { r &= 3; r |= 8; guidResponse += hex[r]; } else { guidResponse += guidHolder[i]; } } return guidResponse; }; function textEncodeLite(str) { const buf = new ArrayBuffer(str.length); const bufView = new Uint8Array(buf); for (let i = 0; i < str.length; i++) { bufView[i] = str.charCodeAt(i); } return bufView; } function base64urlOfHashOfASCIIEncodingAsync(code) { return new Promise((resolve, reject) => { crypto.subtle.digest("SHA-256", textEncodeLite(code)).then( (buffer) => { return resolve(uint8ToUrlBase64(new Uint8Array(buffer))); }, (error) => reject(error) ); }); } const isDpop = (trustedDomain) => { if (Array.isArray(trustedDomain)) { return false; } return trustedDomain.demonstratingProofOfPossession ?? false; }; const getDpopConfiguration = (trustedDomain) => { if (!isDpop(trustedDomain)) { return null; } if (Array.isArray(trustedDomain)) { return null; } return trustedDomain.demonstratingProofOfPossessionConfiguration ?? defaultDemonstratingProofOfPossessionConfiguration; }; const getDpopOnlyWhenDpopHeaderPresent = (trustedDomain) => { if (!isDpop(trustedDomain)) { return null; } if (Array.isArray(trustedDomain)) { return null; } return trustedDomain.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent ?? true; }; function normalizeUrl(url) { try { return new URL(url).toString(); } catch (error) { console.error(`Failed to normalize url: ${url}`, error); return url; } } function checkDomain(domains, endpoint) { if (!endpoint) { return; } const domain = domains.find((domain2) => { var _a; let testable; if (typeof domain2 === "string") { testable = new RegExp(`^${domain2}`); } else { testable = domain2; } return (_a = testable.test) == null ? void 0 : _a.call(testable, endpoint); }); if (!domain) { throw new Error( "Domain " + endpoint + " is not trusted, please add domain in " + scriptFilename ); } } const getDomains = (trustedDomain, type) => { if (Array.isArray(trustedDomain)) { return trustedDomain; } return trustedDomain[`${type}Domains`] ?? trustedDomain.domains ?? []; }; const getCurrentDatabaseDomain = (database2, url, trustedDomains2) => { var _a; if (url.endsWith(openidWellknownUrlEndWith)) { return null; } const datatases = []; for (const [key, currentDatabase] of Object.entries(database2)) { const oidcServerConfiguration = currentDatabase.oidcServerConfiguration; if (!oidcServerConfiguration) { continue; } if (oidcServerConfiguration.tokenEndpoint && url === normalizeUrl(oidcServerConfiguration.tokenEndpoint)) { continue; } if (oidcServerConfiguration.revocationEndpoint && url === normalizeUrl(oidcServerConfiguration.revocationEndpoint)) { continue; } const trustedDomain = trustedDomains2 == null ? [] : trustedDomains2[key.split("#")[0]]; const domains = getDomains(trustedDomain, "accessToken"); const domainsToSendTokens = oidcServerConfiguration.userInfoEndpoint ? [normalizeUrl(oidcServerConfiguration.userInfoEndpoint), ...domains] : [...domains]; let hasToSendToken = false; if (domainsToSendTokens.find((f) => f === acceptAnyDomainToken)) { hasToSendToken = true; } else { for (let i = 0; i < domainsToSendTokens.length; i++) { let domain = domainsToSendTokens[i]; if (typeof domain === "string") { domain = new RegExp(`^${domain}`); } if ((_a = domain.test) == null ? void 0 : _a.call(domain, url)) { hasToSendToken = true; break; } } } if (hasToSendToken) { if (currentDatabase.tokens) { datatases.push(currentDatabase); } } } return datatases; }; function serializeHeaders(headers) { const headersObj = {}; for (const key of headers.keys()) { if (headers.has(key)) { headersObj[key] = headers.get(key); } } return headersObj; } const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); function countLetter(str, find) { return str.split(find).length - 1; } const parseJwt = (payload) => { return JSON.parse(b64DecodeUnicode(payload.replaceAll(/-/g, "+").replaceAll(/_/g, "/"))); }; function b64DecodeUnicode(str) { return decodeURIComponent( Array.prototype.map.call(atob(str), (c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("") ); } function computeTimeLeft(refreshTimeBeforeTokensExpirationInSecond, expiresAt) { const currentTimeUnixSecond = (/* @__PURE__ */ new Date()).getTime() / 1e3; return Math.round(expiresAt - refreshTimeBeforeTokensExpirationInSecond - currentTimeUnixSecond); } function isTokensValid(tokens) { if (!tokens) { return false; } return computeTimeLeft(0, tokens.expiresAt) > 0; } const extractTokenPayload = (token) => { try { if (!token) { return null; } if (countLetter(token, ".") === 2) { return parseJwt(token.split(".")[1]); } else { return null; } } catch (e) { console.warn(e); } return null; }; const isTokensOidcValid = (tokens, nonce, oidcServerConfiguration) => { if (tokens.idTokenPayload) { const idTokenPayload = tokens.idTokenPayload; if (idTokenPayload && oidcServerConfiguration.issuer !== idTokenPayload.iss) { return { isValid: false, reason: `Issuer does not match (oidcServerConfiguration issuer) ${oidcServerConfiguration.issuer} !== (idTokenPayload issuer) ${idTokenPayload.iss}` }; } const currentTimeUnixSecond = (/* @__PURE__ */ new Date()).getTime() / 1e3; if (idTokenPayload && idTokenPayload.exp && idTokenPayload.exp < currentTimeUnixSecond) { return { isValid: false, reason: `Token expired at (idTokenPayload exp) ${idTokenPayload.exp} < (currentTimeUnixSecond) ${currentTimeUnixSecond}` }; } const timeInSevenDays = 60 * 60 * 24 * 7; if (idTokenPayload && idTokenPayload.iat && idTokenPayload.iat + timeInSevenDays < currentTimeUnixSecond) { return { isValid: false, reason: `Token is used from too long time (idTokenPayload iat + timeInSevenDays) ${idTokenPayload.iat + timeInSevenDays} < (currentTimeUnixSecond) ${currentTimeUnixSecond}` }; } if (idTokenPayload && nonce && idTokenPayload.nonce && idTokenPayload.nonce !== nonce) { return { isValid: false, reason: `Nonce does not match (nonce) ${nonce} !== (idTokenPayload nonce) ${idTokenPayload.nonce}` }; } } return { isValid: true, reason: "" }; }; function extractedIssueAt(tokens, accessTokenPayload, _idTokenPayload) { if (!tokens.issued_at) { if (accessTokenPayload && accessTokenPayload.iat) { return accessTokenPayload.iat; } else if (_idTokenPayload && _idTokenPayload.iat) { return _idTokenPayload.iat; } else { const currentTimeUnixSecond = (/* @__PURE__ */ new Date()).getTime() / 1e3; return currentTimeUnixSecond; } } else if (typeof tokens.issued_at == "string") { return parseInt(tokens.issued_at, 10); } return tokens.issued_at; } function _hideTokens(tokens, currentDatabaseElement, configurationName) { if (!tokens.issued_at) { const currentTimeUnixSecond = (/* @__PURE__ */ new Date()).getTime() / 1e3; tokens.issued_at = currentTimeUnixSecond; } else if (typeof tokens.issued_at == "string") { tokens.issued_at = parseInt(tokens.issued_at, 10); } const accessTokenPayload = extractTokenPayload(tokens.access_token); const secureTokens = { ...tokens, accessTokenPayload }; if (currentDatabaseElement.hideAccessToken) { secureTokens.access_token = `${TOKEN.ACCESS_TOKEN}_${configurationName}`; } tokens.accessTokenPayload = accessTokenPayload; const oldTokens = currentDatabaseElement.tokens; let id_token; if (oldTokens != null && "id_token" in oldTokens && !("id_token" in tokens)) { id_token = oldTokens.id_token; } else { id_token = tokens.id_token; } tokens.id_token = id_token; let _idTokenPayload = null; if (id_token) { _idTokenPayload = extractTokenPayload(id_token); tokens.idTokenPayload = _idTokenPayload != null ? { ..._idTokenPayload } : null; if (_idTokenPayload && _idTokenPayload.nonce && currentDatabaseElement.nonce != null) { const keyNonce = `${TOKEN.NONCE_TOKEN}_${currentDatabaseElement.configurationName}`; _idTokenPayload.nonce = keyNonce; } secureTokens.idTokenPayload = _idTokenPayload; } if (tokens.refresh_token) { secureTokens.refresh_token = `${TOKEN.REFRESH_TOKEN}_${configurationName}`; } tokens.issued_at = extractedIssueAt(tokens, accessTokenPayload, _idTokenPayload); const expireIn = typeof tokens.expires_in == "string" ? parseInt(tokens.expires_in, 10) : tokens.expires_in; const idTokenExpiresAt = _idTokenPayload && _idTokenPayload.exp ? _idTokenPayload.exp : Number.MAX_VALUE; const accessTokenExpiresAt = accessTokenPayload && accessTokenPayload.exp ? accessTokenPayload.exp : tokens.issued_at + expireIn; let expiresAt; const tokenRenewMode = currentDatabaseElement.oidcConfiguration.token_renew_mode; if (tokenRenewMode === TokenRenewMode.access_token_invalid) { expiresAt = accessTokenExpiresAt; } else if (tokenRenewMode === TokenRenewMode.id_token_invalid) { expiresAt = idTokenExpiresAt; } else { expiresAt = idTokenExpiresAt < accessTokenExpiresAt ? idTokenExpiresAt : accessTokenExpiresAt; } secureTokens.expiresAt = expiresAt; tokens.expiresAt = expiresAt; const nonce = currentDatabaseElement.nonce ? currentDatabaseElement.nonce.nonce : null; const { isValid, reason } = isTokensOidcValid( tokens, nonce, currentDatabaseElement.oidcServerConfiguration ); if (!isValid) { throw Error(`Tokens are not OpenID valid, reason: ${reason}`); } if (oldTokens != null && "refresh_token" in oldTokens && !("refresh_token" in tokens)) { const refreshToken = oldTokens.refresh_token; currentDatabaseElement.tokens = { ...tokens, refresh_token: refreshToken }; } else { currentDatabaseElement.tokens = tokens; } currentDatabaseElement.status = "LOGGED_IN"; return secureTokens; } const demonstratingProofOfPossessionNonceResponseHeader = "DPoP-Nonce"; function hideTokens(currentDatabaseElement) { const configurationName = currentDatabaseElement.configurationName; return (response) => { if (response.status !== 200) { return response; } const newHeaders = new Headers(response.headers); if (response.headers.has(demonstratingProofOfPossessionNonceResponseHeader)) { currentDatabaseElement.demonstratingProofOfPossessionNonce = response.headers.get( demonstratingProofOfPossessionNonceResponseHeader ); newHeaders.delete(demonstratingProofOfPossessionNonceResponseHeader); } return response.json().then((tokens) => { const secureTokens = _hideTokens(tokens, currentDatabaseElement, configurationName); const body = JSON.stringify(secureTokens); return new Response(body, { status: response.status, statusText: response.statusText, headers: newHeaders }); }); }; } const getMatchingOidcConfigurations = (database2, url) => { return Object.values(database2).filter((config) => { const { oidcServerConfiguration } = config || {}; const { tokenEndpoint, revocationEndpoint } = oidcServerConfiguration || {}; const normalizedUrl = normalizeUrl(url); return tokenEndpoint && normalizedUrl.startsWith(normalizeUrl(tokenEndpoint)) || revocationEndpoint && normalizedUrl.startsWith(normalizeUrl(revocationEndpoint)); }); }; function replaceCodeVerifier(codeVerifier, newCodeVerifier) { const regex = /[?&]code_verifier=([^&]+)/i; return codeVerifier.replace(regex, `&code_verifier=${newCodeVerifier}`); } const extractConfigurationNameFromCodeVerifier = (chaine) => { const regex = /[?&]code_verifier=CODE_VERIFIER_SECURED_BY_OIDC_SERVICE_WORKER_([^&]+)/; const match = chaine.match(regex); if (match && match.length > 0) { return decodeURIComponent(match[1]); } else { return ""; } }; const version = "7.25.13"; if (typeof trustedTypes !== "undefined" && typeof trustedTypes.createPolicy === "function") { trustedTypes.createPolicy("default", { createScriptURL: function(url) { if (url === scriptFilename) { return url; } else { throw new Error("Untrusted script URL blocked: " + url); } } }); } const _self = self; _self.importScripts(scriptFilename); const id = Math.round((/* @__PURE__ */ new Date()).getTime() / 1e3).toString(); console.log("init service worker with id", id); const keepAliveJsonFilename = "OidcKeepAliveServiceWorker.json"; const database = {}; const keepAliveAsync = async (event) => { const originalRequest = event.request; const isFromVanilla = originalRequest.headers.has("oidc-vanilla"); const init = { status: 200, statusText: "oidc-service-worker" }; const response = new Response("{}", init); if (!isFromVanilla) { const originalRequestUrl = new URL(originalRequest.url); const minSleepSeconds = Number(originalRequestUrl.searchParams.get("minSleepSeconds")) || 240; for (let i = 0; i < minSleepSeconds; i++) { await sleep(1e3 + Math.floor(Math.random() * 1e3)); const cache = await caches.open("oidc_dummy_cache"); await cache.put(event.request, response.clone()); } } return response; }; async function generateDpopAsync(originalRequest, currentDatabase, url, extrasClaims = {}) { const headersExtras = serializeHeaders(originalRequest.headers); if ((currentDatabase == null ? void 0 : currentDatabase.demonstratingProofOfPossessionConfiguration) && currentDatabase.demonstratingProofOfPossessionJwkJson && (!currentDatabase.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent || currentDatabase.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent && headersExtras.dpop)) { const dpopConfiguration = currentDatabase.demonstratingProofOfPossessionConfiguration; const jwk = currentDatabase.demonstratingProofOfPossessionJwkJson; const method = originalRequest.method; const dpop = await generateJwtDemonstratingProofOfPossessionAsync(self)(dpopConfiguration)( jwk, method, url, extrasClaims ); headersExtras.dpop = dpop; if (currentDatabase.demonstratingProofOfPossessionNonce != null) { headersExtras.nonce = currentDatabase.demonstratingProofOfPossessionNonce; } } return headersExtras; } const handleFetch = (event) => { event.respondWith( (async () => { var _a, _b; try { const originalRequest = event.request; const url = normalizeUrl(originalRequest.url); if (url.includes(keepAliveJsonFilename)) { return keepAliveAsync(event); } const currentDatabasesForRequestAccessToken = getCurrentDatabaseDomain( database, url, trustedDomains ); const authorization = originalRequest.headers.get("authorization"); let authenticationMode = "Bearer"; let key = "default"; if (authorization) { const split = authorization.split(" "); authenticationMode = split[0]; if ((_a = split[1]) == null ? void 0 : _a.includes("ACCESS_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER_")) { key = split[1].split("ACCESS_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER_")[1]; } } const currentDatabaseForRequestAccessToken = currentDatabasesForRequestAccessToken == null ? void 0 : currentDatabasesForRequestAccessToken.find( (c) => c.configurationName.endsWith(key) ); if ((_b = currentDatabaseForRequestAccessToken == null ? void 0 : currentDatabaseForRequestAccessToken.tokens) == null ? void 0 : _b.access_token) { while (currentDatabaseForRequestAccessToken.tokens && !isTokensValid(currentDatabaseForRequestAccessToken.tokens)) { await sleep(200); } let requestMode = originalRequest.mode; if (originalRequest.mode !== "navigate" && currentDatabaseForRequestAccessToken.convertAllRequestsToCorsExceptNavigate) { requestMode = "cors"; } let headers; if (originalRequest.mode === "navigate" && !currentDatabaseForRequestAccessToken.setAccessTokenToNavigateRequests) { headers = { ...serializeHeaders(originalRequest.headers) }; } else { if (authenticationMode.toLowerCase() === "dpop" || !currentDatabaseForRequestAccessToken.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent && currentDatabaseForRequestAccessToken.demonstratingProofOfPossessionConfiguration) { const claimsExtras = { ath: await base64urlOfHashOfASCIIEncodingAsync( currentDatabaseForRequestAccessToken.tokens.access_token ) }; const dpopHeaders = await generateDpopAsync( originalRequest, currentDatabaseForRequestAccessToken, url, claimsExtras ); headers = { ...dpopHeaders, authorization: `DPoP ${currentDatabaseForRequestAccessToken.tokens.access_token}` }; } else { headers = { ...serializeHeaders(originalRequest.headers), authorization: `${authenticationMode} ${currentDatabaseForRequestAccessToken.tokens.access_token}` }; } } let init; if (originalRequest.mode === "navigate") { init = { headers }; } else { init = { headers, mode: requestMode }; } const newRequest = new Request(originalRequest, init); return fetch(newRequest); } if (event.request.method !== "POST") { return fetch(originalRequest); } const currentDatabases = getMatchingOidcConfigurations(database, url); const numberDatabase = currentDatabases.length; if (numberDatabase > 0) { const responsePromise = new Promise((resolve, reject) => { const clonedRequest = originalRequest.clone(); clonedRequest.text().then(async (actualBody) => { var _a2; let currentDatabase = null; try { if (actualBody.includes(TOKEN.REFRESH_TOKEN) || actualBody.includes(TOKEN.ACCESS_TOKEN)) { let headers = serializeHeaders(originalRequest.headers); let newBody = actualBody; for (let i = 0; i < numberDatabase; i++) { const currentDb = currentDatabases[i]; if (currentDb == null ? void 0 : currentDb.tokens) { const claimsExtras = { ath: await base64urlOfHashOfASCIIEncodingAsync( currentDb.tokens.access_token ) }; headers = await generateDpopAsync( originalRequest, currentDb, url, claimsExtras ); const keyRefreshToken = encodeURIComponent( `${TOKEN.REFRESH_TOKEN}_${currentDb.configurationName}` ); if (actualBody.includes(keyRefreshToken)) { newBody = newBody.replace( keyRefreshToken, encodeURIComponent(currentDb.tokens.refresh_token) ); currentDatabase = currentDb; break; } const keyAccessToken = encodeURIComponent( `${TOKEN.ACCESS_TOKEN}_${currentDb.configurationName}` ); if (actualBody.includes(keyAccessToken)) { newBody = newBody.replace( keyAccessToken, encodeURIComponent(currentDb.tokens.access_token) ); currentDatabase = currentDb; break; } } } const fetchPromise = fetch(originalRequest, { body: newBody, method: clonedRequest.method, headers, mode: clonedRequest.mode, cache: clonedRequest.cache, redirect: clonedRequest.redirect, referrer: clonedRequest.referrer, credentials: clonedRequest.credentials, integrity: clonedRequest.integrity }); if (((_a2 = currentDatabase == null ? void 0 : currentDatabase.oidcServerConfiguration) == null ? void 0 : _a2.revocationEndpoint) && url.startsWith( normalizeUrl(currentDatabase.oidcServerConfiguration.revocationEndpoint) )) { const resp = await fetchPromise; const txt = await resp.text(); resolve(new Response(txt, resp)); return; } const hidden = await fetchPromise.then( hideTokens(currentDatabase) ); resolve(hidden); return; } const isCodeVerifier = actualBody.includes("code_verifier="); if (isCodeVerifier) { const currentLoginCallbackConfigurationName = extractConfigurationNameFromCodeVerifier(actualBody); if (!currentLoginCallbackConfigurationName || currentLoginCallbackConfigurationName === "") { throw new Error("No configuration name found in code_verifier"); } currentDatabase = database[currentLoginCallbackConfigurationName]; let newBody = actualBody; const codeVerifier = currentDatabase.codeVerifier; if (codeVerifier != null) { newBody = replaceCodeVerifier(newBody, codeVerifier); } const headersExtras = await generateDpopAsync( originalRequest, currentDatabase, url ); const resp = await fetch(originalRequest, { body: newBody, method: clonedRequest.method, headers: headersExtras, mode: clonedRequest.mode, cache: clonedRequest.cache, redirect: clonedRequest.redirect, referrer: clonedRequest.referrer, credentials: clonedRequest.credentials, integrity: clonedRequest.integrity }); const hidden = await hideTokens(currentDatabase)(resp); resolve(hidden); return; } const normalResp = await fetch(originalRequest, { body: actualBody, method: clonedRequest.method, headers: serializeHeaders(originalRequest.headers), mode: clonedRequest.mode, cache: clonedRequest.cache, redirect: clonedRequest.redirect, referrer: clonedRequest.referrer, credentials: clonedRequest.credentials, integrity: clonedRequest.integrity }); resolve(normalResp); } catch (err) { reject(err); } }).catch(reject); }); return responsePromise; } return fetch(originalRequest); } catch (err) { console.error("[OidcServiceWorker] handleFetch error:", err); return new Response("Service Worker Error", { status: 500 }); } })() ); }; const handleMessage = async (event) => { var _a, _b; const port = event.ports[0]; const data = event.data; if (((_a = event.data) == null ? void 0 : _a.type) === "SKIP_WAITING") { await _self.skipWaiting(); return; } else if (event.data.type === "claim") { _self.clients.claim().then(() => port.postMessage({})); return; } const configurationName = data.configurationName.split("#")[0]; if (trustedDomains == null) { trustedDomains = {}; } const trustedDomain = trustedDomains[configurationName]; const allowMultiTabLogin = Array.isArray(trustedDomain) ? false : trustedDomain.allowMultiTabLogin; const tabId = allowMultiTabLogin ? data.tabId : "default"; const configurationNameWithTabId = `${configurationName}#tabId=${tabId}`; let currentDatabase = database[configurationNameWithTabId]; if (!currentDatabase) { const showAccessToken = Array.isArray(trustedDomain) ? false : trustedDomain.showAccessToken; const doNotSetAccessTokenToNavigateRequests = Array.isArray(trustedDomain) ? true : trustedDomain.setAccessTokenToNavigateRequests; const convertAllRequestsToCorsExceptNavigate = Array.isArray(trustedDomain) ? false : trustedDomain.convertAllRequestsToCorsExceptNavigate; database[configurationNameWithTabId] = { tokens: null, state: null, codeVerifier: null, oidcServerConfiguration: null, oidcConfiguration: void 0, nonce: null, status: null, configurationName: configurationNameWithTabId, hideAccessToken: !showAccessToken, setAccessTokenToNavigateRequests: doNotSetAccessTokenToNavigateRequests ?? true, convertAllRequestsToCorsExceptNavigate: convertAllRequestsToCorsExceptNavigate ?? false, demonstratingProofOfPossessionNonce: null, demonstratingProofOfPossessionJwkJson: null, demonstratingProofOfPossessionConfiguration: null, demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: false, allowMultiTabLogin: allowMultiTabLogin ?? false }; currentDatabase = database[configurationNameWithTabId]; if (!trustedDomains[configurationName]) { trustedDomains[configurationName] = []; } } switch (data.type) { case "clear": currentDatabase.tokens = null; currentDatabase.state = null; currentDatabase.codeVerifier = null; currentDatabase.nonce = null; currentDatabase.demonstratingProofOfPossessionNonce = null; currentDatabase.demonstratingProofOfPossessionJwkJson = null; currentDatabase.demonstratingProofOfPossessionConfiguration = null; currentDatabase.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent = false; currentDatabase.status = data.data.status; port.postMessage({ configurationName }); return; case "init": { const oidcServerConfiguration = data.data.oidcServerConfiguration; const domains = getDomains(trustedDomain, "oidc"); if (!domains.some((domain) => domain === acceptAnyDomainToken)) { [ oidcServerConfiguration.tokenEndpoint, oidcServerConfiguration.revocationEndpoint, oidcServerConfiguration.userInfoEndpoint, oidcServerConfiguration.issuer ].forEach((u) => { checkDomain(domains, u); }); } currentDatabase.oidcServerConfiguration = oidcServerConfiguration; currentDatabase.oidcConfiguration = data.data.oidcConfiguration; if (currentDatabase.demonstratingProofOfPossessionConfiguration == null) { const demonstratingProofOfPossessionConfiguration = getDpopConfiguration(trustedDomain); if (demonstratingProofOfPossessionConfiguration != null) { if (currentDatabase.oidcConfiguration.demonstrating_proof_of_possession) { console.warn( "In service worker, demonstrating_proof_of_possession must be configured from trustedDomains file" ); } currentDatabase.demonstratingProofOfPossessionConfiguration = demonstratingProofOfPossessionConfiguration; currentDatabase.demonstratingProofOfPossessionJwkJson = await generateJwkAsync(self)( demonstratingProofOfPossessionConfiguration.generateKeyAlgorithm ); currentDatabase.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent = getDpopOnlyWhenDpopHeaderPresent(trustedDomain) ?? false; } } if (!currentDatabase.tokens) { port.postMessage({ tokens: null, status: currentDatabase.status, configurationName, version }); } else { const tokens = { ...currentDatabase.tokens }; if (currentDatabase.hideAccessToken) { tokens.access_token = `${TOKEN.ACCESS_TOKEN}_${configurationName}#tabId=${tabId}`; } if (tokens.refresh_token) { tokens.refresh_token = `${TOKEN.REFRESH_TOKEN}_${configurationName}#tabId=${tabId}`; } if (((_b = tokens == null ? void 0 : tokens.idTokenPayload) == null ? void 0 : _b.nonce) && currentDatabase.nonce != null) { tokens.idTokenPayload.nonce = `${TOKEN.NONCE_TOKEN}_${configurationName}#tabId=${tabId}`; } port.postMessage({ tokens, status: currentDatabase.status, configurationName, version }); } return; } case "setDemonstratingProofOfPossessionNonce": { currentDatabase.demonstratingProofOfPossessionNonce = data.data.demonstratingProofOfPossessionNonce; port.postMessage({ configurationName }); return; } case "getDemonstratingProofOfPossessionNonce": { const demonstratingProofOfPossessionNonce = currentDatabase.demonstratingProofOfPossessionNonce; port.postMessage({ configurationName, demonstratingProofOfPossessionNonce }); return; } case "setState": { currentDatabase.state = data.data.state; port.postMessage({ configurationName }); return; } case "getState": { const state = currentDatabase.state; port.postMessage({ configurationName, state }); return; } case "setCodeVerifier": { currentDatabase.codeVerifier = data.data.codeVerifier; port.postMessage({ configurationName }); return; } case "getCodeVerifier": { const codeVerifier = currentDatabase.codeVerifier != null ? `${TOKEN.CODE_VERIFIER}_${configurationName}#tabId=${tabId}` : null; port.postMessage({ configurationName, codeVerifier }); return; } case "setSessionState": { currentDatabase.sessionState = data.data.sessionState; port.postMessage({ configurationName }); return; } case "getSessionState": { const sessionState = currentDatabase.sessionState; port.postMessage({ configurationName, sessionState }); return; } case "setNonce": { const nonce = data.data.nonce; if (nonce) { currentDatabase.nonce = nonce; } port.postMessage({ configurationName }); return; } case "getNonce": { const keyNonce = `${TOKEN.NONCE_TOKEN}_${configurationName}#tabId=${tabId}`; const nonce = currentDatabase.nonce ? keyNonce : null; port.postMessage({ configurationName, nonce }); return; } default: return; } }; _self.addEventListener("fetch", handleFetch); _self.addEventListener("message", handleMessage); //# sourceMappingURL=OidcServiceWorker.js.map