From 16315341df67318044d42452b1437e3e0fb6b704 Mon Sep 17 00:00:00 2001 From: David Antoon Date: Wed, 16 Oct 2024 06:27:14 +0300 Subject: [PATCH] Add support for application id and option to use appId in isolated mode --- .../example-app-directory/app/UserState.tsx | 33 ++++++++++++++++++- packages/example-app-directory/app/layout.tsx | 6 +--- .../api/frontegg/[...frontegg-middleware].ts | 28 ++++++++++++++++ packages/example-pages/pages/index.tsx | 28 ++++++++++++++++ packages/nextjs/src/api/utils.ts | 19 ++++++++++- packages/nextjs/src/config/constants.ts | 13 ++++++++ packages/nextjs/src/config/index.ts | 23 +++++++++++-- packages/nextjs/src/edge/getSessionOnEdge.ts | 7 +++- .../src/middleware/ProxyRequestCallback.ts | 13 ++++++-- .../pages/withFronteggApp/withFronteggApp.tsx | 3 +- packages/nextjs/src/types/index.ts | 1 + packages/nextjs/src/utils/cookies/helpers.ts | 20 ++++++++--- packages/nextjs/src/utils/cookies/index.ts | 20 +++++++++-- .../nextjs/src/utils/fetchUserData/index.ts | 7 +++- .../src/utils/initializeFronteggApp/index.ts | 1 + .../refreshAccessTokenIfNeeded/helpers.ts | 7 +++- 16 files changed, 206 insertions(+), 23 deletions(-) diff --git a/packages/example-app-directory/app/UserState.tsx b/packages/example-app-directory/app/UserState.tsx index af4dbf62..c18dac08 100644 --- a/packages/example-app-directory/app/UserState.tsx +++ b/packages/example-app-directory/app/UserState.tsx @@ -1,11 +1,18 @@ 'use client'; -import { useAuthUserOrNull, useLoginWithRedirect, AdminPortal, useLogoutHostedLogin } from '@frontegg/nextjs'; +import { + useAuthActions, + useAuthUserOrNull, + useLoginWithRedirect, + AdminPortal, + useLogoutHostedLogin, +} from '@frontegg/nextjs'; import Link from 'next/link'; export const UserState = () => { const user = useAuthUserOrNull(); const loginWithRedirect = useLoginWithRedirect(); const logoutHosted = useLogoutHostedLogin(); + const { switchTenant } = useAuthActions(); /* * Replace the elements below with your own. @@ -18,6 +25,30 @@ export const UserState = () => {

+
+

Tenants:

+ {(user?.tenants ?? []).map((tenant) => { + return ( +
+ Tenant Id: + {tenant['tenantId']} + + {tenant.tenantId === user?.tenantId ? ( + Current Tenant + ) : ( + + )} +
+ ); + })} +
+
+ )} + + ); + })} + +
); } diff --git a/packages/nextjs/src/api/utils.ts b/packages/nextjs/src/api/utils.ts index 2d5440a8..29c6206a 100644 --- a/packages/nextjs/src/api/utils.ts +++ b/packages/nextjs/src/api/utils.ts @@ -48,6 +48,7 @@ export function removeInvalidHeaders(headers: Record) { * These header is used to identify the tenant for login per tenant feature */ export const CUSTOM_LOGIN_HEADER = 'frontegg-login-alias'; + /** * Build fetch request headers, remove invalid http headers * @param headers - Incoming request headers @@ -56,10 +57,23 @@ export function buildRequestHeaders(headers: Record): Record `${key}=${value}`) + .map(([key, value]) => { + if (config.rewriteCookieByAppId && config.appId && key === `fe_refresh_${config.appId.replace('-', '')}`) { + return `fe_refresh_${config.clientId.replace('-', '')}=${value}`; + } else { + return `${key}=${value}`; + } + }) .join('; '); } @@ -77,6 +91,9 @@ export function buildRequestHeaders(headers: Record): Record = (p }); logger.debug(`${req.url} | proxy FronteggCookies (${fronteggCookiesNames.join(', ')})`); - fronteggCookiesNames.forEach((cookieName: string) => { - proxyReq.setHeader(cookieName, allCookies[cookieName]); + fronteggCookiesNames.forEach((requestCookieName: string) => { + let cookieName = requestCookieName; + if (config.rewriteCookieByAppId && config.appId) { + cookieName = requestCookieName + .replace(config.appId, config.clientId) + .replace(config.appId.replace(/-/g, ''), config.clientId.replace(/-/g, '')) + .replace(config.appId.replace('-', ''), config.clientId.replace('-', '')); + + logger.debug(`cookieName ${requestCookieName} replaced with appId ${cookieName}`); + } + proxyReq.setHeader(cookieName, allCookies[requestCookieName]); }); proxyReq.setHeader('x-frontegg-framework', req.headers['x-frontegg-framework'] ?? `next@${NextJsPkg.version}`); diff --git a/packages/nextjs/src/pages/withFronteggApp/withFronteggApp.tsx b/packages/nextjs/src/pages/withFronteggApp/withFronteggApp.tsx index 286906ed..c956883a 100644 --- a/packages/nextjs/src/pages/withFronteggApp/withFronteggApp.tsx +++ b/packages/nextjs/src/pages/withFronteggApp/withFronteggApp.tsx @@ -57,7 +57,7 @@ export const withFronteggApp = (app: FronteggCustomAppClass, options?: WithFront }; function CustomFronteggApp(appProps: AppProps) { - const { user, tenants, activeTenant, session, envAppUrl, envBaseUrl, envClientId, secureJwtEnabled } = + const { user, tenants, activeTenant, session, envAppUrl, envBaseUrl, envClientId, secureJwtEnabled, envAppId } = appProps.pageProps; return ( {app(appProps) as any} diff --git a/packages/nextjs/src/types/index.ts b/packages/nextjs/src/types/index.ts index 956929de..70526df8 100644 --- a/packages/nextjs/src/types/index.ts +++ b/packages/nextjs/src/types/index.ts @@ -70,6 +70,7 @@ export interface FronteggProviderOptions extends Omit; } diff --git a/packages/nextjs/src/utils/cookies/helpers.ts b/packages/nextjs/src/utils/cookies/helpers.ts index 195ff980..1932ec9e 100644 --- a/packages/nextjs/src/utils/cookies/helpers.ts +++ b/packages/nextjs/src/utils/cookies/helpers.ts @@ -55,8 +55,18 @@ export const getCookieHeader = (request: RequestType): string => { return cookieHeader; }; -export const getRefreshTokenCookieNameVariants = () => [ - `fe_refresh_${config.clientId}`, - `fe_refresh_${config.clientId.replace('-', '')}`, - `fe_refresh_${config.clientId.replace(/-/g, '')}`, -]; +export const getRefreshTokenCookieNameVariants = () => { + if (config.rewriteCookieByAppId && config.appId) { + return [ + `fe_refresh_${config.appId}`, + `fe_refresh_${config.appId.replace('-', '')}`, + `fe_refresh_${config.appId.replace(/-/g, '')}`, + ]; + } else { + return [ + `fe_refresh_${config.clientId}`, + `fe_refresh_${config.clientId.replace('-', '')}`, + `fe_refresh_${config.clientId.replace(/-/g, '')}`, + ]; + } +}; diff --git a/packages/nextjs/src/utils/cookies/index.ts b/packages/nextjs/src/utils/cookies/index.ts index 9758edc9..8af38c5f 100644 --- a/packages/nextjs/src/utils/cookies/index.ts +++ b/packages/nextjs/src/utils/cookies/index.ts @@ -17,7 +17,11 @@ class CookieManager { cookieNumber ? getIndexedCookieName(cookieNumber, cookieName) : cookieName; get refreshTokenKey(): string { - return `fe_refresh_${config.clientId}`.replace(/-/g, ''); + if (config.rewriteCookieByAppId && config.appId) { + return `fe_refresh_${config.appId.replace(/-/g, '')}`; + } else { + return `fe_refresh_${config.clientId.replace(/-/g, '')}`; + } } /** @@ -266,10 +270,20 @@ class CookieManager { return ( cookie .map((property) => { - if (property.toLowerCase() === `domain=${config.baseUrlHost}`) { + if (property.startsWith(`fe_refresh_${config.clientId.replace('-', '')}`)) { + if (config.rewriteCookieByAppId && config.appId) { + return property.replace( + `fe_refresh_${config.clientId.replace('-', '')}`, + `fe_refresh_${config.appId.replace('-', '')}` + ); + } else { + return property; + } + } else if (property.toLowerCase() === `domain=${config.baseUrlHost}`) { return `Domain=${config.cookieDomain}`; + } else { + return property; } - return property; }) .join(';') + ';' ); diff --git a/packages/nextjs/src/utils/fetchUserData/index.ts b/packages/nextjs/src/utils/fetchUserData/index.ts index 1e1d88a9..6267a11d 100644 --- a/packages/nextjs/src/utils/fetchUserData/index.ts +++ b/packages/nextjs/src/utils/fetchUserData/index.ts @@ -2,6 +2,7 @@ import { AllUserData, FronteggNextJSSession } from '../../types'; import { getTenants, getMe, getMeAuthorization, getEntitlements } from '../../api'; import { calculateExpiresInFromExp } from '../common'; import fronteggLogger from '../fronteggLogger'; +import config from '../../config'; const FULFILLED_STATUS = 'fulfilled'; @@ -23,7 +24,11 @@ export default async function fetchUserData(options: FetchUserDataOptions): Prom const { accessToken } = session; const reqHeaders = await getHeaders(); - const headers = { ...reqHeaders, authorization: `Bearer ${accessToken}` }; + const headers: Record = { ...reqHeaders, authorization: `Bearer ${accessToken}` }; + + if (config.appId) { + headers['frontegg-requested-application-id'] = config.appId; + } logger.debug('Retrieving user data...'); const [baseUserResult, tenantsResult, entitlementsResult, meAuthorizationResult] = await Promise.allSettled([ diff --git a/packages/nextjs/src/utils/initializeFronteggApp/index.ts b/packages/nextjs/src/utils/initializeFronteggApp/index.ts index b477c2fa..bd4915f7 100644 --- a/packages/nextjs/src/utils/initializeFronteggApp/index.ts +++ b/packages/nextjs/src/utils/initializeFronteggApp/index.ts @@ -57,6 +57,7 @@ const initializeFronteggApp = ({ } }, clientId: options.envClientId, + appId: options.envAppId, }; const tenantsState = { diff --git a/packages/nextjs/src/utils/refreshAccessTokenIfNeeded/helpers.ts b/packages/nextjs/src/utils/refreshAccessTokenIfNeeded/helpers.ts index 6c151f07..00151173 100644 --- a/packages/nextjs/src/utils/refreshAccessTokenIfNeeded/helpers.ts +++ b/packages/nextjs/src/utils/refreshAccessTokenIfNeeded/helpers.ts @@ -27,6 +27,9 @@ export async function refreshAccessTokenEmbedded(request: IncomingMessage): Prom logger.info('check if has refresh token headers'); if (hasRefreshTokenCookie(cookies)) { logger.info('going to refresh token (embedded mode)'); + if (config.appId) { + headers['frontegg-requested-application-id'] = config.appId; + } return await api.refreshTokenEmbedded(headers); } return null; @@ -45,7 +48,9 @@ export async function refreshAccessTokenHostedLogin(request: IncomingMessage): P logger.info('refresh token not found'); return null; } - + if (config.appId) { + headers['frontegg-requested-application-id'] = config.appId; + } if (config.secureJwtEnabled) { const clientId = config.clientId; const clientSecret = config.clientSecret;