From 92e162ac4ed0c21ebb0a1798ce695bf380e9a3fa Mon Sep 17 00:00:00 2001 From: purquijo <131868858+purquijo@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:49:39 +0200 Subject: [PATCH 1/5] [EAM-1675] Exchange token implementation --- src/AuthWrapper.js | 59 ++++++++++++++++++++++++++++++++++++++++++---- src/index.js | 27 ++++++++++++++++----- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/src/AuthWrapper.js b/src/AuthWrapper.js index 634327f0..2f213ba6 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,15 @@ 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; + console.log(tokens) }; export default (props) => { @@ -22,7 +28,7 @@ export default (props) => { return ( handleTokens(process.env.REACT_APP_KEYCLOAK_CLIENTID, token)} initOptions={{ onLoad: "login-required" }} LoadingComponent={} > @@ -55,4 +61,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 (_) { + console.log(_) + } +} + +export { tokens, keycloak, logout, injectBearerToken, exchangeToken }; diff --git a/src/index.js b/src/index.js index a064115b..c35188d6 100644 --- a/src/index.js +++ b/src/index.js @@ -22,25 +22,40 @@ 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()); unregister(); polyfill(); +const getClientID = ({url}) => { + if (url?.startsWith(process.env.REACT_APP_EAM_SERVICES_URL)) { + return process.env.REACT_APP_KEYCLOAK_EAM_SERVICES_CLIENTID; + } else return process.env.REACT_APP_KEYCLOAK_CLIENTID; +}; + Ajax.getAxiosInstance().interceptors.request.use( (config) => { if (process.env.REACT_APP_LOGIN_METHOD !== "OPENID") { 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 = getClientID({url: config.url}); + if (clientID !== process.env.REACT_APP_KEYCLOAK_CLIENTID) { + await exchangeToken({ + sourceClient: process.env.REACT_APP_KEYCLOAK_CLIENTID, + targetClient: clientID, + }); } - return newConfig; + return injectBearerToken({ + config: { + ...config, + timeout: 300000, + }, + clientID, + }); }); }, (error) => { From 7d8fec23745bb0ef6e7da0097a6694684f8ba117 Mon Sep 17 00:00:00 2001 From: purquijo <131868858+purquijo@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:52:06 +0200 Subject: [PATCH 2/5] Cern eam services variables --- .env | 3 +++ 1 file changed, 3 insertions(+) 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 From ec3dff6b53af7c812110bb11b8df5431d3de57d1 Mon Sep 17 00:00:00 2001 From: purquijo <131868858+purquijo@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:05:38 +0200 Subject: [PATCH 3/5] [EAM-1675] More felxible client id resolver and concurrent approach --- src/AuthWrapper.js | 1 - src/index.js | 21 ++++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/AuthWrapper.js b/src/AuthWrapper.js index 2f213ba6..408e1ad5 100644 --- a/src/AuthWrapper.js +++ b/src/AuthWrapper.js @@ -18,7 +18,6 @@ const keycloakAxios = axios.create({ const handleTokens = (key, freshTokens) => { tokens[key] = freshTokens; - console.log(tokens) }; export default (props) => { diff --git a/src/index.js b/src/index.js index c35188d6..27fe7e6e 100644 --- a/src/index.js +++ b/src/index.js @@ -26,15 +26,11 @@ import AuthWrapper, { keycloak, exchangeToken, injectBearerToken } from "./AuthW const jss = create(jssPreset()); +let tokenExchange = {}; + unregister(); polyfill(); -const getClientID = ({url}) => { - if (url?.startsWith(process.env.REACT_APP_EAM_SERVICES_URL)) { - return process.env.REACT_APP_KEYCLOAK_EAM_SERVICES_CLIENTID; - } else return process.env.REACT_APP_KEYCLOAK_CLIENTID; -}; - Ajax.getAxiosInstance().interceptors.request.use( (config) => { if (process.env.REACT_APP_LOGIN_METHOD !== "OPENID") { @@ -42,12 +38,15 @@ Ajax.getAxiosInstance().interceptors.request.use( } // updateToken if it will last less than 5 minutes return keycloak.updateToken(300).then(async () => { - const clientID = getClientID({url: config.url}); + const clientID = config.clientIdExchange || process.env.REACT_APP_KEYCLOAK_CLIENTID; if (clientID !== process.env.REACT_APP_KEYCLOAK_CLIENTID) { - await exchangeToken({ - sourceClient: process.env.REACT_APP_KEYCLOAK_CLIENTID, - targetClient: clientID, - }); + if (!tokenExchange[clientID]) { + tokenExchange[clientID] = exchangeToken({ + sourceClient: process.env.REACT_APP_KEYCLOAK_CLIENTID, + targetClient: clientID, + }); + } + await tokenExchange[clientID]; } return injectBearerToken({ config: { From c1fa434ee259e53ba2d2c2bf2816210f9b3f329b Mon Sep 17 00:00:00 2001 From: purquijo <131868858+purquijo@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:08:28 +0200 Subject: [PATCH 4/5] [EAM-1675] Error handling --- src/AuthWrapper.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AuthWrapper.js b/src/AuthWrapper.js index 408e1ad5..6e983269 100644 --- a/src/AuthWrapper.js +++ b/src/AuthWrapper.js @@ -98,8 +98,8 @@ const exchangeToken = async ({ sourceClient, targetClient }) => { handleTokens(targetClient, response.data); setTimeout(() => exchangeToken({ sourceClient, targetClient }), (response.data.expires_in - 20) * 1000); return response.data; - } catch (_) { - console.log(_) + } catch (error) { + console.error('Unable to exchange token: ' + error) } } From b4fad06e8738cfa9b8cce7e752785cf2669d6ee2 Mon Sep 17 00:00:00 2001 From: purquijo <131868858+purquijo@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:36:09 +0200 Subject: [PATCH 5/5] [EAM-1675] EDMS Login Servlet link passed as a prop --- src/ui/pages/work/Workorder.js | 1 + 1 file changed, 1 insertion(+) 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}