diff --git a/packages/context/.gitignore b/packages/context/.gitignore new file mode 100644 index 000000000..8a4f48506 --- /dev/null +++ b/packages/context/.gitignore @@ -0,0 +1,2 @@ +/public/OidcServiceWorker.js +/public/OidcKeepAliveServiceWorker.json \ No newline at end of file diff --git a/packages/context/README.md b/packages/context/README.md index e2752b8f1..875b64dfa 100644 --- a/packages/context/README.md +++ b/packages/context/README.md @@ -50,7 +50,7 @@ The only file you should edit is "OidcTrustedDomains.js" which will never be era #package.json { "scripts": { - "copy": "copyfiles -f ./node_modules/@axa-fr/react-oidc-context/dist/OidcServiceWorker.js ./public && copyfiles -f -s ./node_modules/@axa-fr/react-oidc-context/dist/OidcTrustedDomains.js ./public", + "copy": "copyfiles -f ./node_modules/@axa-fr/react-oidc-context/dist/OidcServiceWorker.js ./public && copyfiles -f -s ./node_modules/@axa-fr/react-oidc-context/dist/OidcTrustedDomains.js ./public && copyfiles -f ./node_modules/@axa-fr/react-oidc-context/dist/OidcKeepAliveServiceWorker.json ./public", "start:server": "react-scripts start", "build:server": "npm run copy && react-scripts build", "prepare": "npm run copy" diff --git a/packages/context/package.json b/packages/context/package.json index 6ac0601c3..9b042f68b 100644 --- a/packages/context/package.json +++ b/packages/context/package.json @@ -1,6 +1,6 @@ { "name": "@axa-fr/react-oidc-context", - "version": "4.0.3", + "version": "4.2.3-alpha.0", "private": false, "main": "dist/index.js", "jsnext:main": "dist/index.js", @@ -22,13 +22,13 @@ "react-component" ], "scripts": { - "copy": "copyfiles -f ./src/oidc/vanilla/OidcServiceWorker.js ./public && copyfiles -f -soft ./src/oidc/vanilla/OidcTrustedDomains.js ./public", + "copy": "copyfiles -f ./src/oidc/vanilla/OidcServiceWorker.js ./public && copyfiles -f -soft ./src/oidc/vanilla/OidcTrustedDomains.js ./public && copyfiles -f -soft ./src/oidc/vanilla/OidcKeepAliveServiceWorker.json ./public", "start": "npm run copy && set PORT=4200 && react-scripts start", "build": "npm i react react-dom && npm run copy && react-scripts build", "test": "react-scripts test --coverage", "eject": "react-scripts eject", "clean": "rimraf dist", - "prepare": "npm run clean && tsc --build \"./tsconfig.json\" && copyfiles -f ./src/oidc/vanilla/OidcServiceWorker.js ./dist && copyfiles -f ./src/oidc/vanilla/OidcTrustedDomains.js ./dist" + "prepare": "npm run clean && tsc --build \"./tsconfig.json\" && copyfiles -f ./src/oidc/vanilla/OidcServiceWorker.js ./dist && copyfiles -f ./src/oidc/vanilla/OidcTrustedDomains.js ./dist && copyfiles -f ./src/oidc/vanilla/OidcKeepAliveServiceWorker.json ./dist" }, "peerDependencies": { "react": ">=16.8.6", diff --git a/packages/context/public/OidcServiceWorker.js b/packages/context/public/OidcServiceWorker.js deleted file mode 100644 index c0ee6ed63..000000000 --- a/packages/context/public/OidcServiceWorker.js +++ /dev/null @@ -1,252 +0,0 @@ -this.importScripts('OidcTrustedDomains.js'); - -const id = Math.round(new Date().getTime() / 1000).toString(); - -const handleInstall = () => { - console.log('[OidcServiceWorker] service worker installed ' + id); -}; - -const handleActivate = () => { - console.log('[OidcServiceWorker] service worker activated ' + id); -}; - -let currentLoginCallbackConfigurationName = null; -let database = { - default: { - configurationName: "default", - tokens: null, - items:[], - oidcServerConfiguration: null - } -}; - -function extractAccessTokenPayload(accessToken) { - try{ - if (!accessToken) { - return null; - } - if(accessToken.includes('.')) { - return JSON.parse(atob(accessToken.split('.')[1])); - } else { - return null; - } - } catch (e) { - console.error(e); - } - return null; -} - -function hideTokens(currentDatabaseElement) { - const configurationName = currentDatabaseElement.configurationName; - return (response) => { - return response.json().then(tokens => { - currentDatabaseElement.tokens = tokens; - const secureTokens = { - ...tokens, - access_token: ACCESS_TOKEN +"_" + configurationName, - refresh_token: REFRESH_TOKEN + "_" + configurationName, - }; - const body = JSON.stringify(secureTokens) - return new Response(body, response); - }); - }; -} - -const getCurrentDatabasesTokenEndpoint = (database, url) => { - const databases = []; - for (const [key, value] of Object.entries(database)) { - if(value && value.oidcServerConfiguration !=null && url.startsWith(value.oidcServerConfiguration.tokenEndpoint)){ - databases.push(value); - } - } - return databases; -} - -const getCurrentDatabaseDomain = (database, url) => { - for (const [key, currentDatabase] of Object.entries(database)) { - - const oidcServerConfiguration = currentDatabase.oidcServerConfiguration; - const domainsToSendTokens = oidcServerConfiguration != null ? [ - oidcServerConfiguration.userInfoEndpoint, ...trustedDomains[key] - ] : [...trustedDomains[key]]; - - let hasToSendToken = false; - for(let i=0;i { - let headersObj = {}; - for (let key of headers.keys()) { - headersObj[key] = headers.get(key); - } - return headersObj; -}; - -const REFRESH_TOKEN = 'REFRESH_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER'; -const ACCESS_TOKEN = 'ACCESS_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER'; - -const handleFetch = async (event) => { - const originalRequest = event.request; - const currentDatabaseForRequestAccessToken = getCurrentDatabaseDomain(database, originalRequest.url); - if(currentDatabaseForRequestAccessToken && currentDatabaseForRequestAccessToken.tokens) { - const newRequest = new Request(originalRequest, { - headers: { - ...serializeHeaders(originalRequest.headers), - authorization: "Bearer " + currentDatabaseForRequestAccessToken.tokens.access_token - } - }); - event.waitUntil(event.respondWith(fetch(newRequest))); - } - - if(event.request.method !== "POST"){ - return; - } - let currentDatabase = null; - const currentDatabases = getCurrentDatabasesTokenEndpoint(database, originalRequest.url); - const numberDatabase = currentDatabases.length; - if(numberDatabase > 0) { - const maPromesse = new Promise((resolve, reject) => { - const response =originalRequest.text().then(actualBody => { - if(actualBody.includes(REFRESH_TOKEN)) { - let newBody = actualBody; - for(let i= 0;i { - if(r !== undefined){ - resolve(r); - } - }).catch(err => { - if(err !== undefined) { - reject(err); - } - }); - }); - event.waitUntil(event.respondWith(maPromesse)); - } -}; - -self.addEventListener('install', handleInstall); -self.addEventListener('activate', handleActivate); -self.addEventListener('fetch', handleFetch); - - -addEventListener('message', event => { - const port = event.ports[0]; - const data = event.data; - const configurationName = data.configurationName; - let currentDatabase = database[configurationName]; - - if(!currentDatabase){ - database[configurationName] = { - tokens: null, - items:[], - oidcServerConfiguration: null, - configurationName: configurationName, - }; - currentDatabase = database[configurationName]; - if(!trustedDomains[configurationName]) { - trustedDomains[configurationName] = []; - } - } - switch (data.type){ - case "skipWaiting": - self.skipWaiting().then(async () => { - await self.clients.claim(); - port.postMessage({configurationName}); - }); - return; - case "loadItems": - port.postMessage(database[configurationName].items); - return; - case "clear": - currentDatabase.tokens = null; - currentDatabase.items = null; - port.postMessage({configurationName}); - return; - case "init": - currentDatabase.oidcServerConfiguration = data.data.oidcServerConfiguration; - const where = data.data.where; - if(where === "loginCallbackAsync" || where === "tryKeepExistingSessionAsync") { - currentLoginCallbackConfigurationName = configurationName; - } else{ - currentLoginCallbackConfigurationName = null; - } - if(!currentDatabase.tokens){ - port.postMessage({ - tokens:null, configurationName}); - } else { - port.postMessage({ - tokens: { - ...currentDatabase.tokens, - refresh_token: REFRESH_TOKEN + "_" + configurationName, - access_token: ACCESS_TOKEN + "_" + configurationName - }, configurationName - }); - } - return; - - case "getAccessTokenPayload": - const accessTokenPayload = extractAccessTokenPayload(currentDatabase.tokens.access_token); - port.postMessage({configurationName, accessTokenPayload}); - return; - default: - currentDatabase.items = data.data; - port.postMessage({configurationName}); - return; - } -}); - diff --git a/packages/context/src/configurations.ts b/packages/context/src/configurations.ts index 002fb0d50..eeb8724a4 100644 --- a/packages/context/src/configurations.ts +++ b/packages/context/src/configurations.ts @@ -1,5 +1,5 @@ export const configurationIdentityServer = { - client_id: 'interactive.public.short', + client_id: 'interactive.public.short', // interactive.public redirect_uri: window.location.origin+'/authentication/callback', // http://localhost:4200/authentication/callback scope: 'openid profile email api offline_access', authority: 'https://demo.identityserver.io', diff --git a/packages/context/src/oidc/vanilla/OidcKeepAliveServiceWorker.json b/packages/context/src/oidc/vanilla/OidcKeepAliveServiceWorker.json new file mode 100644 index 000000000..22fdca1b2 --- /dev/null +++ b/packages/context/src/oidc/vanilla/OidcKeepAliveServiceWorker.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/context/src/oidc/vanilla/OidcServiceWorker.js b/packages/context/src/oidc/vanilla/OidcServiceWorker.js index 48017f1f2..bca0ec810 100644 --- a/packages/context/src/oidc/vanilla/OidcServiceWorker.js +++ b/packages/context/src/oidc/vanilla/OidcServiceWorker.js @@ -2,8 +2,17 @@ const id = Math.round(new Date().getTime() / 1000).toString(); -const handleInstall = () => { +const assetCacheName = "asset"; +const keepAliveJsonFilename = "OidcKeepAliveServiceWorker.json"; +const handleInstall = (event) => { console.log('[OidcServiceWorker] service worker installed ' + id); + event.waitUntil( + caches.open(assetCacheName).then(cache => { + return cache.addAll( + [ + keepAliveJsonFilename + ]); + })); }; const handleActivate = () => { @@ -103,6 +112,21 @@ const ACCESS_TOKEN = 'ACCESS_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER'; const handleFetch = async (event) => { const originalRequest = event.request; + + if(originalRequest.url.includes(keepAliveJsonFilename) ){ + event.respondWith( + caches.open(assetCacheName).then(function(cache) { + return cache.match(event.request).then(function (response) { + return response || fetch(event.request).then(function(response) { + cache.put(event.request, response.clone()); + return response; + }); + }); + }) + ); + return; + } + const currentDatabaseForRequestAccessToken = getCurrentDatabaseDomain(database, originalRequest.url); if(currentDatabaseForRequestAccessToken && currentDatabaseForRequestAccessToken.tokens) { const newRequest = new Request(originalRequest, { @@ -122,7 +146,7 @@ const handleFetch = async (event) => { const numberDatabase = currentDatabases.length; if(numberDatabase > 0) { const maPromesse = new Promise((resolve, reject) => { - const response =originalRequest.text().then(actualBody => { + const response = originalRequest.text().then(actualBody => { if(actualBody.includes(REFRESH_TOKEN)) { let newBody = actualBody; for(let i= 0;i { + if(keepAliveServiceWorkerTimeoutId == null) { + keepAliveServiceWorkerTimeoutId = setInterval(() => { + console.log('/OidcKeepAliveServiceWorker.json'); + fetch('OidcKeepAliveServiceWorker.json') + }, 20000); + } +} + const sendMessageAsync = (registration) => (data) =>{ return new Promise(function(resolve, reject) { const messageChannel = new MessageChannel(); @@ -127,8 +137,8 @@ export const initWorkerAsync = async(serviceWorkerRelativeUrl, configurationName const clearAsync=() =>{ return sendMessageAsync(registration)({type: "clear", data: null, configurationName}); } - - const initAsync=async (oidcServerConfiguration, where) => { + const initAsync= async (oidcServerConfiguration, where) => { + const result = await sendMessageAsync(registration)({ type: "init", data: {oidcServerConfiguration, where}, @@ -136,8 +146,8 @@ export const initWorkerAsync = async(serviceWorkerRelativeUrl, configurationName }); // @ts-ignore return { tokens : result.tokens, isUpdateDetected }; - } - + } + keepAliveServiceWorker(); return { saveItemsAsync, loadItemsAsync, clearAsync, initAsync, getAccessTokenPayloadAsync, updateAsync }; } diff --git a/packages/context/src/oidc/vanilla/oidc.ts b/packages/context/src/oidc/vanilla/oidc.ts index 6843a103b..b56159f34 100644 --- a/packages/context/src/oidc/vanilla/oidc.ts +++ b/packages/context/src/oidc/vanilla/oidc.ts @@ -72,7 +72,8 @@ const loginCallbackWithAutoTokensRenewAsync = async (oidc) => { oidc.timeoutId = await autoRenewTokensAsync(oidc, tokens.refreshToken, tokens.expiresIn) return response.state; } -const autoRenewTokensAsync = async (oidc, refreshToken, intervalSeconds) =>{ + +const autoRenewTokensAsync = async (oidc, refreshToken, intervalSeconds) => { const refreshTimeBeforeTokensExpirationInSecond = oidc.configuration.refresh_time_before_tokens_expiration_in_second ?? 60; return setTimeout(async () => { const tokens = await oidc.refreshTokensAsync(refreshToken); @@ -88,7 +89,7 @@ const autoRenewTokensAsync = async (oidc, refreshToken, intervalSeconds) =>{ }, (intervalSeconds- refreshTimeBeforeTokensExpirationInSecond) *1000); } -const userInfoAsync = async (oidc)=> { +const userInfoAsync = async (oidc) => { if(oidc.userInfo != null){ return oidc.userInfo; } @@ -226,7 +227,7 @@ export class Oidc { const oidcServerConfiguration = await this.initAsync(configuration.authority); serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName); if(serviceWorker) { - const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "tryKeepExistingSessionAsync"); + const { tokens } = await serviceWorker.initAsync(oidcServerConfiguration, "tryKeepExistingSessionAsync"); if (tokens) { const updatedTokens = await this.refreshTokensAsync(tokens.refresh_token, true); // @ts-ignore diff --git a/packages/vanilla/package.json b/packages/vanilla/package.json index eaa5d7269..dbae7d101 100644 --- a/packages/vanilla/package.json +++ b/packages/vanilla/package.json @@ -1,6 +1,6 @@ { "name": "@axa-fr/vanilla-oidc", - "version": "4.0.3", + "version": "4.2.3-alpha.0", "private": false, "main": "dist/index.js", "jsnext:main": "dist/index.js", diff --git a/readme.md b/readme.md index 3a9139fee..582f73010 100644 --- a/readme.md +++ b/readme.md @@ -68,7 +68,7 @@ The only file you should edit is "OidcTrustedDomains.js" which will never be era #package.json { "scripts": { - "copy": "copyfiles -f ./node_modules/@axa-fr/react-oidc-context/dist/OidcServiceWorker.js ./public && copyfiles -f -s ./node_modules/@axa-fr/react-oidc-context/dist/OidcTrustedDomains.js ./public", + "copy": "copyfiles -f ./node_modules/@axa-fr/react-oidc-context/dist/OidcServiceWorker.js ./public && copyfiles -f -s ./node_modules/@axa-fr/react-oidc-context/dist/OidcTrustedDomains.js ./public && copyfiles -f ./node_modules/@axa-fr/react-oidc-context/dist/OidcKeepAliveServiceWorker.json ./public", "start:server": "react-scripts start", "build:server": "npm run copy && react-scripts build", "prepare": "npm run copy"