diff --git a/package-lock.json b/package-lock.json index 8e7d6393..fffb3936 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1964,9 +1964,9 @@ } }, "node_modules/@orchestrator-ui/orchestrator-ui-components": { - "version": "1.38.2", - "resolved": "https://registry.npmjs.org/@orchestrator-ui/orchestrator-ui-components/-/orchestrator-ui-components-1.38.2.tgz", - "integrity": "sha512-rApizAq00wqFW2ThVcGR4T4Z+qR4h/OU5MnOCWlFAXLNqeTnWnMjMQb1r2nkrwnVZq+s/lMoctOLDYpGBKMRxg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@orchestrator-ui/orchestrator-ui-components/-/orchestrator-ui-components-2.1.1.tgz", + "integrity": "sha512-bgeWZ2oFjDbEKaW8vy5dwbkfgDMCs8ocCEWKUqxALOBovbT+tl8tngnttUoX26fkvBD7zjAsvq4b74G4wVkYiw==", "dependencies": { "@elastic/eui": "^95.1.0", "@emotion/css": "^11.11.2", diff --git a/package.json b/package.json index 65b8deec..c8212cf1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wfo-ui", - "version": "1.0.0", + "version": "1.19.1", "private": true, "scripts": { "dev": "next dev", diff --git a/pages/api/auth/[...nextauth].ts b/pages/api/auth/[...nextauth].ts index 376be3b9..6707c4de 100644 --- a/pages/api/auth/[...nextauth].ts +++ b/pages/api/auth/[...nextauth].ts @@ -32,6 +32,69 @@ const token_endpoint_auth_method = OAUTH2_CLIENT_SECRET ? 'client_secret_basic' : 'none'; +const getCurrentDateInSeconds = () => Math.floor(Date.now() / 1000); + +const calculateExpirationDate = (expiresIn?: number) => { + if (!expiresIn) { + return undefined; + } + + return getCurrentDateInSeconds() + expiresIn; +}; +const getWellKnownData = async () => { + const wellKnownUrl = new URL(OIDC_CONF_FULL_WELL_KNOWN_URL); + const wellKnown = await fetch(wellKnownUrl); + return await wellKnown.json(); +}; +async function refreshAccessToken(token: JWT): Promise { + const { token_endpoint } = await getWellKnownData(); + + try { + const raw = { + client_id: OAUTH2_CLIENT_ID, + client_secret: OAUTH2_CLIENT_SECRET, + grant_type: 'refresh_token', + refresh_token: token.refreshToken as string, + }; + + const response = await fetch(token_endpoint, { + method: 'POST', + body: new URLSearchParams(raw), + }); + + if (response.ok) { + const data: { + access_token: string; + expires_in: number; + refresh_token: string; + refresh_expires_in: number; + } = await response.json(); + + return { + ...token, + accessToken: data.access_token, + accessTokenExpiresAt: calculateExpirationDate(data.expires_in), + refreshToken: data.refresh_token, + refreshTokenExpiresAt: calculateExpirationDate( + data.refresh_expires_in, + ), + }; + } else { + console.error( + 'An error occurred while refreshing the access token: ', + response.statusText, + ); + return token; + } + } catch (error) { + console.error( + 'An error occurred while refreshing the access token: ', + error, + ); + return token; + } +} + const wfoProvider: OAuthConfig = { id: NEXTAUTH_PROVIDER_ID, name: NEXTAUTH_PROVIDER_NAME, @@ -74,21 +137,46 @@ export const authOptions: AuthOptions = { providers: isOauth2Enabled ? [wfoProvider] : [], callbacks: { async jwt({ token, account, profile }) { + // First time after signing in // The "account" is only available right after signing in -- adding useful data to the token if (account) { - token.accessToken = account.access_token; - token.profile = profile; + return { + ...token, + accessToken: account.access_token, + refreshToken: account.refresh_token, + accessTokenExpiresAt: account.expires_at as number, + refreshTokenExpiresAt: calculateExpirationDate( + account.refresh_expires_in as number, + ), + profile, + }; } - return token; + + const now = getCurrentDateInSeconds(); + if ( + typeof token.accessTokenExpiresAt === 'number' && + now < token.accessTokenExpiresAt + ) { + return token; + } + + return await refreshAccessToken(token); }, - async session({ session, token }: { session: WfoSession; token: JWT }) { + async session({ + session, + token, + }: { + session: WfoSession; + token: JWT; + }): Promise { // Assign data to the session to be available in the client through the useSession hook - session.profile = token.profile as WfoUserProfile | undefined; - session.accessToken = token.accessToken - ? String(token.accessToken) - : ''; - - return session; + return { + ...session, + profile: token.profile as WfoUserProfile | undefined, + accessToken: token.accessToken ? String(token.accessToken) : '', + accessTokenExpiresAt: token.accessTokenExpiresAt as number, + refreshTokenExpiresAt: token.refreshTokenExpiresAt as number, + }; }, }, };