diff --git a/.env b/.env index fdf6a3f3..02ef5961 100644 --- a/.env +++ b/.env @@ -6,3 +6,6 @@ REACT_APP_CERN_MODE=TRUE REACT_APP_KEYCLOAK_URL=https://auth.cern.ch/auth REACT_APP_KEYCLOAK_CLIENTID=eam-light REACT_APP_KEYCLOAK_REALM=cern + +REACT_APP_EAM_SERVICES_URL=/apis/cern-eam-services/rest +REACT_APP_KEYCLOAK_EAM_SERVICES_CLIENTID=cern-eam-services diff --git a/src/AuthWrapper.js b/src/AuthWrapper.js index 634327f0..6e983269 100644 --- a/src/AuthWrapper.js +++ b/src/AuthWrapper.js @@ -2,6 +2,7 @@ import React from "react"; import { ReactKeycloakProvider } from "@react-keycloak/web"; import { LinearProgress } from '@mui/material'; import Keycloak from "keycloak-js"; +import axios from "axios"; const keycloak = new Keycloak({ url: process.env.REACT_APP_KEYCLOAK_URL, @@ -9,10 +10,14 @@ const keycloak = new Keycloak({ clientId: process.env.REACT_APP_KEYCLOAK_CLIENTID, }); -let tokens; +let tokens = {}; -const handleTokens = (freshTokens) => { - tokens = freshTokens; +const keycloakAxios = axios.create({ + baseURL: `${process.env.REACT_APP_KEYCLOAK_URL}/realms/${process.env.REACT_APP_KEYCLOAK_REALM}`, +}); + +const handleTokens = (key, freshTokens) => { + tokens[key] = freshTokens; }; export default (props) => { @@ -22,7 +27,7 @@ export default (props) => { return ( handleTokens(process.env.REACT_APP_KEYCLOAK_CLIENTID, token)} initOptions={{ onLoad: "login-required" }} LoadingComponent={} > @@ -55,4 +60,47 @@ const logout = () => { } }; -export { tokens, keycloak, logout }; +const injectBearerToken = ({ config, clientID }) => { + const newConfig = config; + const clientTokens = tokens[clientID]; + if (!clientTokens) return newConfig; + const token = clientTokens.token || clientTokens.access_token; + if (!token) return newConfig; + newConfig.headers.Authorization = `Bearer ${token}`; + return newConfig; +}; + +const exchangeToken = async ({ sourceClient, targetClient }) => { + if(!tokens[sourceClient]) return null; + if(tokens[targetClient]) return tokens[targetClient]; + + const formData = { + client_id: sourceClient, + subject_token: tokens[sourceClient].token, + audience: targetClient, + grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', + request_type: 'urn:ietf:params:oauth:token-type:access_token', + }; + + try { + const response = await keycloakAxios.post( + `/protocol/openid-connect/token`, + Object.keys(formData) + .map(key => `${key}=${encodeURIComponent(formData[key])}`) + .join('&'), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + } + ); + + handleTokens(targetClient, response.data); + setTimeout(() => exchangeToken({ sourceClient, targetClient }), (response.data.expires_in - 20) * 1000); + return response.data; + } catch (error) { + console.error('Unable to exchange token: ' + error) + } +} + +export { tokens, keycloak, logout, injectBearerToken, exchangeToken }; diff --git a/src/index.js b/src/index.js index a064115b..27fe7e6e 100644 --- a/src/index.js +++ b/src/index.js @@ -22,10 +22,12 @@ import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns' // EAM-480, en-GB locale used in order to have monday as first day of the week import { enGB } from "date-fns/locale"; import { UPDATE_SCANNED_USER } from "./actions/scannedUserActions"; -import AuthWrapper, { tokens, keycloak } from "./AuthWrapper"; +import AuthWrapper, { keycloak, exchangeToken, injectBearerToken } from "./AuthWrapper"; const jss = create(jssPreset()); +let tokenExchange = {}; + unregister(); polyfill(); @@ -35,12 +37,24 @@ Ajax.getAxiosInstance().interceptors.request.use( return config; } // updateToken if it will last less than 5 minutes - return keycloak.updateToken(300).then(() => { - const newConfig = config; - if (tokens && tokens.token) { - newConfig.headers.Authorization = `Bearer ${tokens.token}`; + return keycloak.updateToken(300).then(async () => { + const clientID = config.clientIdExchange || process.env.REACT_APP_KEYCLOAK_CLIENTID; + if (clientID !== process.env.REACT_APP_KEYCLOAK_CLIENTID) { + if (!tokenExchange[clientID]) { + tokenExchange[clientID] = exchangeToken({ + sourceClient: process.env.REACT_APP_KEYCLOAK_CLIENTID, + targetClient: clientID, + }); + } + await tokenExchange[clientID]; } - return newConfig; + return injectBearerToken({ + config: { + ...config, + timeout: 300000, + }, + clientID, + }); }); }, (error) => { diff --git a/src/ui/pages/work/Workorder.js b/src/ui/pages/work/Workorder.js index ab086726..994e3fbc 100644 --- a/src/ui/pages/work/Workorder.js +++ b/src/ui/pages/work/Workorder.js @@ -398,6 +398,7 @@ const Workorder = () => { workorder={workorder.number} eqpToOtherId={otherIdMapping} printingChecklistLinkToAIS={applicationData.EL_PRTCL} + edmsLoginServletLink={applicationData.EL_EDMSS} maxExpandedChecklistItems={Math.abs(parseInt(applicationData.EL_MCHLS)) || 50} getWoLink={wo => '/workorder/' + wo} ref={checklists}