@thunderid/nextjs 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ThunderIDNextClient.js +1 -1
- package/dist/ThunderIDNextClient.js.map +1 -1
- package/dist/cjs/ThunderIDNextClient.cjs +1 -1
- package/dist/cjs/ThunderIDNextClient.cjs.map +1 -1
- package/dist/cjs/client/components/presentation/SignIn/SignIn.cjs.map +1 -1
- package/dist/cjs/client/components/presentation/SignUp/SignUp.cjs +3 -2
- package/dist/cjs/client/components/presentation/SignUp/SignUp.cjs.map +1 -1
- package/dist/cjs/client/contexts/ThunderID/ThunderIDProvider.cjs +3 -2
- package/dist/cjs/client/contexts/ThunderID/ThunderIDProvider.cjs.map +1 -1
- package/dist/cjs/client/fetchFlowMetaFromServer.cjs +22 -0
- package/dist/cjs/client/fetchFlowMetaFromServer.cjs.map +1 -0
- package/dist/cjs/server/ThunderIDProvider.cjs +0 -2
- package/dist/cjs/server/ThunderIDProvider.cjs.map +1 -1
- package/dist/cjs/server/actions/getFlowMetaAction.cjs +30 -12
- package/dist/cjs/server/actions/getFlowMetaAction.cjs.map +1 -1
- package/dist/cjs/server/actions/handleOAuthCallbackAction.cjs +1 -0
- package/dist/cjs/server/actions/handleOAuthCallbackAction.cjs.map +1 -1
- package/dist/cjs/server/proxy/createRouteMatcher.cjs +21 -4
- package/dist/cjs/server/proxy/createRouteMatcher.cjs.map +1 -1
- package/dist/cjs/utils/SessionManager.cjs +11 -7
- package/dist/cjs/utils/SessionManager.cjs.map +1 -1
- package/dist/client/components/presentation/SignIn/SignIn.d.ts +1 -1
- package/dist/client/components/presentation/SignIn/SignIn.d.ts.map +1 -1
- package/dist/client/components/presentation/SignIn/SignIn.js.map +1 -1
- package/dist/client/components/presentation/SignUp/SignUp.d.ts.map +1 -1
- package/dist/client/components/presentation/SignUp/SignUp.js +3 -2
- package/dist/client/components/presentation/SignUp/SignUp.js.map +1 -1
- package/dist/client/contexts/ThunderID/ThunderIDProvider.d.ts +0 -11
- package/dist/client/contexts/ThunderID/ThunderIDProvider.d.ts.map +1 -1
- package/dist/client/contexts/ThunderID/ThunderIDProvider.js +3 -2
- package/dist/client/contexts/ThunderID/ThunderIDProvider.js.map +1 -1
- package/dist/client/fetchFlowMetaFromServer.d.ts +12 -0
- package/dist/client/fetchFlowMetaFromServer.d.ts.map +1 -0
- package/dist/client/fetchFlowMetaFromServer.js +20 -0
- package/dist/client/fetchFlowMetaFromServer.js.map +1 -0
- package/dist/server/ThunderIDProvider.d.ts.map +1 -1
- package/dist/server/ThunderIDProvider.js +0 -2
- package/dist/server/ThunderIDProvider.js.map +1 -1
- package/dist/server/actions/getFlowMetaAction.d.ts +16 -1
- package/dist/server/actions/getFlowMetaAction.d.ts.map +1 -1
- package/dist/server/actions/getFlowMetaAction.js +31 -13
- package/dist/server/actions/getFlowMetaAction.js.map +1 -1
- package/dist/server/actions/handleOAuthCallbackAction.d.ts.map +1 -1
- package/dist/server/actions/handleOAuthCallbackAction.js +1 -0
- package/dist/server/actions/handleOAuthCallbackAction.js.map +1 -1
- package/dist/server/proxy/createRouteMatcher.d.ts +0 -19
- package/dist/server/proxy/createRouteMatcher.d.ts.map +1 -1
- package/dist/server/proxy/createRouteMatcher.js +21 -4
- package/dist/server/proxy/createRouteMatcher.js.map +1 -1
- package/dist/utils/SessionManager.d.ts +1 -1
- package/dist/utils/SessionManager.d.ts.map +1 -1
- package/dist/utils/SessionManager.js +3 -1
- package/dist/utils/SessionManager.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getFlowMetaAction.cjs","names":["config: ThunderIDNextConfig","getClient","applicationId: string | undefined","FlowMetaType"],"sources":["../../../../src/server/actions/getFlowMetaAction.ts"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use server';\n\nimport {FlowMetadataResponse
|
|
1
|
+
{"version":3,"file":"getFlowMetaAction.cjs","names":["config: ThunderIDNextConfig","getClient","url: string | undefined","applicationId: string | undefined","FlowMetaType","ThunderIDAPIError"],"sources":["../../../../src/server/actions/getFlowMetaAction.ts"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use server';\n\nimport {\n FlowMetadataResponse,\n FlowMetaType,\n getFlowMeta,\n resolveResourceEndpoint,\n ThunderIDAPIError,\n} from '@thunderid/node';\nimport {ThunderIDNextConfig} from '../../models/config';\nimport logger from '../../utils/logger';\nimport getClient from '../getClient';\n\n/**\n * What {@link getFlowMetaAction} returns: the flow metadata, or a plain failure. It never throws, because a thrown\n * error reaches the browser as a generic 500 and loses its prototype chain crossing the server action boundary. A\n * failure carries a message built on the server and the status, never the text of the upstream reply.\n */\nexport type GetFlowMetaActionResult =\n | {meta: FlowMetadataResponse; success: true}\n | {message: string; statusCode?: number; success: false};\n\n/**\n * Server Action that fetches `GET /flow/meta` server-side.\n *\n * `FlowMetaProvider` (from `@thunderid/react`) otherwise fetches this endpoint directly from the\n * browser against `baseUrl`, which requires the ThunderID server to allow CORS for the app's\n * origin. Routing it through this action instead means the browser never talks to `baseUrl`\n * directly — the request happens server-to-server, same as `signInAction`/`signUpAction`.\n *\n * Never throws across the server action boundary; failures are mapped to a plain result.\n *\n * @param language - Optional language override, used by `FlowMetaProvider.switchLanguage()`.\n */\nconst getFlowMetaAction = async (params?: {\n applicationId?: string;\n language?: string;\n}): Promise<GetFlowMetaActionResult> => {\n try {\n const client = getClient();\n const config: ThunderIDNextConfig = await client.getConfiguration();\n\n const url: string | undefined = resolveResourceEndpoint('flowMeta', config);\n\n if (!url && !config?.baseUrl) {\n return {message: 'Failed to load flow metadata: no base URL is configured.', success: false};\n }\n\n const applicationId: string | undefined = params?.applicationId ?? config?.applicationId;\n\n const meta: FlowMetadataResponse = await getFlowMeta({\n baseUrl: config?.baseUrl,\n url,\n ...(applicationId ? {id: applicationId, type: FlowMetaType.App} : {}),\n ...(params?.language ? {language: params.language} : {}),\n });\n\n return {meta, success: true};\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(`[getFlowMetaAction] Error fetching flow metadata: ${message}`);\n\n if (error instanceof ThunderIDAPIError && error.statusCode !== undefined) {\n return {\n message: `Failed to load flow metadata (HTTP ${error.statusCode}).`,\n statusCode: error.statusCode,\n success: false,\n };\n }\n\n return {message: 'Failed to load flow metadata.', success: false};\n }\n};\n\nexport default getFlowMetaAction;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAM,oBAAoB,OAAO,WAGO;AACtC,KAAI;EAEF,MAAMA,SAA8B,MADrBC,2BAAW,CACuB,kBAAkB;EAEnE,MAAMC,oDAAkD,YAAY,OAAO;AAE3E,MAAI,CAAC,OAAO,CAAC,QAAQ,QACnB,QAAO;GAAC,SAAS;GAA4D,SAAS;GAAM;EAG9F,MAAMC,gBAAoC,QAAQ,iBAAiB,QAAQ;AAS3E,SAAO;GAAC,MAP2B,wCAAkB;IACnD,SAAS,QAAQ;IACjB;IACA,GAAI,gBAAgB;KAAC,IAAI;KAAe,MAAMC,8BAAa;KAAI,GAAG,EAAE;IACpE,GAAI,QAAQ,WAAW,EAAC,UAAU,OAAO,UAAS,GAAG,EAAE;IACxD,CAAC;GAEY,SAAS;GAAK;UACrB,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,yBAAO,MAAM,qDAAqD,UAAU;AAE5E,MAAI,iBAAiBC,sCAAqB,MAAM,eAAe,OAC7D,QAAO;GACL,SAAS,sCAAsC,MAAM,WAAW;GAChE,YAAY,MAAM;GAClB,SAAS;GACV;AAGH,SAAO;GAAC,SAAS;GAAiC,SAAS;GAAM;;;AAIrE,gCAAe"}
|
|
@@ -26,6 +26,7 @@ const handleOAuthCallbackAction = async (code, state) => {
|
|
|
26
26
|
success: false
|
|
27
27
|
};
|
|
28
28
|
const thunderIDClient = require_getClient.default();
|
|
29
|
+
if (!thunderIDClient.isInitialized) await thunderIDClient.initialize({});
|
|
29
30
|
if (!thunderIDClient.isInitialized) return {
|
|
30
31
|
error: "ThunderID client is not initialized",
|
|
31
32
|
success: false
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleOAuthCallbackAction.cjs","names":["getClient","cookieStore: RequestCookies","sessionId: string | undefined","tempSessionToken: string | undefined","SessionManager","signInResult: Record<string, unknown>","config: ThunderIDNextConfig","idToken: IdToken","accessToken: string","refreshToken: string","userIdFromToken: string","scopes: string","organizationId: string | undefined","expiresIn: number","sessionCookieExpiryTime: number","sessionToken: string"],"sources":["../../../../src/server/actions/handleOAuthCallbackAction.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use server';\n\nimport {IdToken} from '@thunderid/node';\nimport {cookies} from 'next/headers';\nimport {ThunderIDNextConfig} from '../../models/config';\nimport {setChunkedCookie} from '../../utils/chunkedCookie';\nimport logger from '../../utils/logger';\nimport SessionManager from '../../utils/SessionManager';\nimport getClient from '../getClient';\n\ntype RequestCookies = Awaited<ReturnType<typeof cookies>>;\n\n/**\n * Server action to handle OAuth callback with authorization code.\n * This action processes the authorization code received from the OAuth provider\n * and exchanges it for tokens to complete the authentication flow.\n *\n * @param code - Authorization code from OAuth provider\n * @param state - State parameter from OAuth provider for CSRF protection\n * @returns Promise that resolves with success status and optional error message\n */\nconst handleOAuthCallbackAction = async (\n code: string,\n state: string,\n): Promise<{\n error?: string;\n redirectUrl?: string;\n success: boolean;\n}> => {\n try {\n if (!code || !state) {\n return {\n error: 'Missing required OAuth parameters: code and state are required',\n success: false,\n };\n }\n\n const thunderIDClient = getClient();\n\n if (!thunderIDClient.isInitialized) {\n return {\n error: 'ThunderID client is not initialized',\n success: false,\n };\n }\n\n const cookieStore: RequestCookies = await cookies();\n let sessionId: string | undefined;\n\n const tempSessionToken: string | undefined = cookieStore.get(SessionManager.getTempSessionCookieName())?.value;\n\n if (tempSessionToken) {\n try {\n const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempSessionToken);\n sessionId = tempSession.sessionId;\n } catch {\n logger.error(\n '[handleOAuthCallbackAction] Invalid temporary session token, falling back to session ID from cookies.',\n );\n }\n }\n\n if (!sessionId) {\n logger.error('[handleOAuthCallbackAction] No session ID found in cookies or temporary session token.');\n\n return {\n error: 'No session found. Please start the authentication flow again.',\n success: false,\n };\n }\n\n // Exchange the authorization code for tokens\n const signInResult: Record<string, unknown> = await thunderIDClient.signIn(\n {\n code,\n state,\n } as any,\n {},\n sessionId,\n );\n\n const config: ThunderIDNextConfig = await thunderIDClient.getConfiguration();\n\n if (signInResult) {\n try {\n const idToken: IdToken = await thunderIDClient.getDecodedIdToken(\n sessionId,\n (signInResult['id_token'] || signInResult['idToken']) as string,\n );\n const accessToken: string = (signInResult['accessToken'] || signInResult['access_token']) as string;\n const refreshToken: string = (signInResult['refreshToken'] as string | undefined) ?? '';\n const userIdFromToken: string = (idToken.sub || signInResult['sub'] || sessionId) as string;\n const scopes: string = signInResult['scope'] as string;\n const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as\n | string\n | undefined;\n const expiresIn: number = signInResult['expiresIn'] as number;\n const sessionCookieExpiryTime: number = SessionManager.resolveSessionCookieExpiry(\n config.sessionCookie?.expiryTime,\n );\n\n const sessionToken: string = await SessionManager.createSessionToken(\n accessToken,\n userIdFromToken,\n sessionId,\n scopes,\n expiresIn,\n refreshToken,\n organizationId,\n {sid: typeof idToken['sid'] === 'string' ? idToken['sid'] : undefined},\n );\n\n setChunkedCookie(\n cookieStore,\n SessionManager.getSessionCookieName(),\n sessionToken,\n SessionManager.getSessionCookieOptions(sessionCookieExpiryTime),\n );\n\n cookieStore.delete(SessionManager.getTempSessionCookieName());\n } catch (error) {\n logger.error(\n `[handleOAuthCallbackAction] Failed to create JWT session, continuing with legacy session:\n ${typeof error === 'string' ? error : JSON.stringify(error)}`,\n );\n }\n }\n\n const afterSignInUrl: string = config.afterSignInUrl || '/';\n\n return {\n redirectUrl: afterSignInUrl,\n success: true,\n };\n } catch (error) {\n let errorMessage = 'Authentication failed';\n\n if (error instanceof Error) {\n errorMessage = error.message;\n } else if (error && typeof error === 'object' && 'message' in error) {\n errorMessage = String((error as {message: unknown}).message);\n } else if (typeof error === 'string') {\n errorMessage = error;\n }\n\n return {\n error: errorMessage,\n success: false,\n };\n }\n};\n\nexport default handleOAuthCallbackAction;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwBA,MAAM,4BAA4B,OAChC,MACA,UAKI;AACJ,KAAI;AACF,MAAI,CAAC,QAAQ,CAAC,MACZ,QAAO;GACL,OAAO;GACP,SAAS;GACV;EAGH,MAAM,kBAAkBA,2BAAW;
|
|
1
|
+
{"version":3,"file":"handleOAuthCallbackAction.cjs","names":["getClient","cookieStore: RequestCookies","sessionId: string | undefined","tempSessionToken: string | undefined","SessionManager","signInResult: Record<string, unknown>","config: ThunderIDNextConfig","idToken: IdToken","accessToken: string","refreshToken: string","userIdFromToken: string","scopes: string","organizationId: string | undefined","expiresIn: number","sessionCookieExpiryTime: number","sessionToken: string"],"sources":["../../../../src/server/actions/handleOAuthCallbackAction.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use server';\n\nimport {IdToken} from '@thunderid/node';\nimport {cookies} from 'next/headers';\nimport {ThunderIDNextConfig} from '../../models/config';\nimport {setChunkedCookie} from '../../utils/chunkedCookie';\nimport logger from '../../utils/logger';\nimport SessionManager from '../../utils/SessionManager';\nimport getClient from '../getClient';\n\ntype RequestCookies = Awaited<ReturnType<typeof cookies>>;\n\n/**\n * Server action to handle OAuth callback with authorization code.\n * This action processes the authorization code received from the OAuth provider\n * and exchanges it for tokens to complete the authentication flow.\n *\n * @param code - Authorization code from OAuth provider\n * @param state - State parameter from OAuth provider for CSRF protection\n * @returns Promise that resolves with success status and optional error message\n */\nconst handleOAuthCallbackAction = async (\n code: string,\n state: string,\n): Promise<{\n error?: string;\n redirectUrl?: string;\n success: boolean;\n}> => {\n try {\n if (!code || !state) {\n return {\n error: 'Missing required OAuth parameters: code and state are required',\n success: false,\n };\n }\n\n const thunderIDClient = getClient();\n\n // A server action can run in a module context that no provider render has initialized, for\n // example after the dev server recompiles, so initialize from the environment first.\n if (!thunderIDClient.isInitialized) {\n await thunderIDClient.initialize({} as ThunderIDNextConfig);\n }\n\n if (!thunderIDClient.isInitialized) {\n return {\n error: 'ThunderID client is not initialized',\n success: false,\n };\n }\n\n const cookieStore: RequestCookies = await cookies();\n let sessionId: string | undefined;\n\n const tempSessionToken: string | undefined = cookieStore.get(SessionManager.getTempSessionCookieName())?.value;\n\n if (tempSessionToken) {\n try {\n const tempSession: {sessionId: string} = await SessionManager.verifyTempSession(tempSessionToken);\n sessionId = tempSession.sessionId;\n } catch {\n logger.error(\n '[handleOAuthCallbackAction] Invalid temporary session token, falling back to session ID from cookies.',\n );\n }\n }\n\n if (!sessionId) {\n logger.error('[handleOAuthCallbackAction] No session ID found in cookies or temporary session token.');\n\n return {\n error: 'No session found. Please start the authentication flow again.',\n success: false,\n };\n }\n\n // Exchange the authorization code for tokens\n const signInResult: Record<string, unknown> = await thunderIDClient.signIn(\n {\n code,\n state,\n } as any,\n {},\n sessionId,\n );\n\n const config: ThunderIDNextConfig = await thunderIDClient.getConfiguration();\n\n if (signInResult) {\n try {\n const idToken: IdToken = await thunderIDClient.getDecodedIdToken(\n sessionId,\n (signInResult['id_token'] || signInResult['idToken']) as string,\n );\n const accessToken: string = (signInResult['accessToken'] || signInResult['access_token']) as string;\n const refreshToken: string = (signInResult['refreshToken'] as string | undefined) ?? '';\n const userIdFromToken: string = (idToken.sub || signInResult['sub'] || sessionId) as string;\n const scopes: string = signInResult['scope'] as string;\n const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as\n | string\n | undefined;\n const expiresIn: number = signInResult['expiresIn'] as number;\n const sessionCookieExpiryTime: number = SessionManager.resolveSessionCookieExpiry(\n config.sessionCookie?.expiryTime,\n );\n\n const sessionToken: string = await SessionManager.createSessionToken(\n accessToken,\n userIdFromToken,\n sessionId,\n scopes,\n expiresIn,\n refreshToken,\n organizationId,\n {sid: typeof idToken['sid'] === 'string' ? idToken['sid'] : undefined},\n );\n\n setChunkedCookie(\n cookieStore,\n SessionManager.getSessionCookieName(),\n sessionToken,\n SessionManager.getSessionCookieOptions(sessionCookieExpiryTime),\n );\n\n cookieStore.delete(SessionManager.getTempSessionCookieName());\n } catch (error) {\n logger.error(\n `[handleOAuthCallbackAction] Failed to create JWT session, continuing with legacy session:\n ${typeof error === 'string' ? error : JSON.stringify(error)}`,\n );\n }\n }\n\n const afterSignInUrl: string = config.afterSignInUrl || '/';\n\n return {\n redirectUrl: afterSignInUrl,\n success: true,\n };\n } catch (error) {\n let errorMessage = 'Authentication failed';\n\n if (error instanceof Error) {\n errorMessage = error.message;\n } else if (error && typeof error === 'object' && 'message' in error) {\n errorMessage = String((error as {message: unknown}).message);\n } else if (typeof error === 'string') {\n errorMessage = error;\n }\n\n return {\n error: errorMessage,\n success: false,\n };\n }\n};\n\nexport default handleOAuthCallbackAction;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwBA,MAAM,4BAA4B,OAChC,MACA,UAKI;AACJ,KAAI;AACF,MAAI,CAAC,QAAQ,CAAC,MACZ,QAAO;GACL,OAAO;GACP,SAAS;GACV;EAGH,MAAM,kBAAkBA,2BAAW;AAInC,MAAI,CAAC,gBAAgB,cACnB,OAAM,gBAAgB,WAAW,EAAE,CAAwB;AAG7D,MAAI,CAAC,gBAAgB,cACnB,QAAO;GACL,OAAO;GACP,SAAS;GACV;EAGH,MAAMC,cAA8B,iCAAe;EACnD,IAAIC;EAEJ,MAAMC,mBAAuC,YAAY,IAAIC,+BAAe,0BAA0B,CAAC,EAAE;AAEzG,MAAI,iBACF,KAAI;AAEF,gBADyC,MAAMA,+BAAe,kBAAkB,iBAAiB,EACzE;UAClB;AACN,0BAAO,MACL,wGACD;;AAIL,MAAI,CAAC,WAAW;AACd,0BAAO,MAAM,yFAAyF;AAEtG,UAAO;IACL,OAAO;IACP,SAAS;IACV;;EAIH,MAAMC,eAAwC,MAAM,gBAAgB,OAClE;GACE;GACA;GACD,EACD,EAAE,EACF,UACD;EAED,MAAMC,SAA8B,MAAM,gBAAgB,kBAAkB;AAE5E,MAAI,aACF,KAAI;GACF,MAAMC,UAAmB,MAAM,gBAAgB,kBAC7C,WACC,aAAa,eAAe,aAAa,WAC3C;GACD,MAAMC,cAAuB,aAAa,kBAAkB,aAAa;GACzE,MAAMC,eAAwB,aAAa,mBAA0C;GACrF,MAAMC,kBAA2B,QAAQ,OAAO,aAAa,UAAU;GACvE,MAAMC,SAAiB,aAAa;GACpC,MAAMC,iBAAsC,QAAQ,eAAe,QAAQ;GAG3E,MAAMC,YAAoB,aAAa;GACvC,MAAMC,0BAAkCV,+BAAe,2BACrD,OAAO,eAAe,WACvB;GAED,MAAMW,eAAuB,MAAMX,+BAAe,mBAChD,aACA,iBACA,WACA,QACA,WACA,cACA,gBACA,EAAC,KAAK,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS,QAAU,CACvE;AAED,0CACE,aACAA,+BAAe,sBAAsB,EACrC,cACAA,+BAAe,wBAAwB,wBAAwB,CAChE;AAED,eAAY,OAAOA,+BAAe,0BAA0B,CAAC;WACtD,OAAO;AACd,0BAAO,MACL;YACE,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,MAAM,GAC5D;;AAML,SAAO;GACL,aAH6B,OAAO,kBAAkB;GAItD,SAAS;GACV;UACM,OAAO;EACd,IAAI,eAAe;AAEnB,MAAI,iBAAiB,MACnB,gBAAe,MAAM;WACZ,SAAS,OAAO,UAAU,YAAY,aAAa,MAC5D,gBAAe,OAAQ,MAA6B,QAAQ;WACnD,OAAO,UAAU,SAC1B,gBAAe;AAGjB,SAAO;GACL,OAAO;GACP,SAAS;GACV;;;AAIL,wCAAe"}
|
|
@@ -19,11 +19,28 @@
|
|
|
19
19
|
* }
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Converts one route pattern to a regular expression source. A parenthesized group such as `(.*)` is kept as
|
|
24
|
+
* regex, so `/dashboard(.*)` matches `/dashboard` and everything under it. Outside a group a `.` is a literal dot
|
|
25
|
+
* and a `*` matches any run of characters.
|
|
26
|
+
*/
|
|
27
|
+
const toRegexSource = (pattern) => {
|
|
28
|
+
let source = "";
|
|
29
|
+
let depth = 0;
|
|
30
|
+
for (const char of pattern) if (char === "(") {
|
|
31
|
+
depth += 1;
|
|
32
|
+
source += char;
|
|
33
|
+
} else if (char === ")") {
|
|
34
|
+
depth = Math.max(depth - 1, 0);
|
|
35
|
+
source += char;
|
|
36
|
+
} else if (depth > 0) source += char;
|
|
37
|
+
else if (char === ".") source += "\\.";
|
|
38
|
+
else if (char === "*") source += ".*";
|
|
39
|
+
else source += char;
|
|
40
|
+
return source;
|
|
41
|
+
};
|
|
22
42
|
const createRouteMatcher = (patterns) => {
|
|
23
|
-
const regexPatterns = patterns.map((pattern) => {
|
|
24
|
-
const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\(\.\*\)/g, "(.*)");
|
|
25
|
-
return /* @__PURE__ */ new RegExp(`^${regexPattern}$`);
|
|
26
|
-
});
|
|
43
|
+
const regexPatterns = patterns.map((pattern) => /* @__PURE__ */ new RegExp(`^${toRegexSource(pattern)}$`));
|
|
27
44
|
return (req) => {
|
|
28
45
|
const { pathname } = req.nextUrl;
|
|
29
46
|
return regexPatterns.some((regex) => regex.test(pathname));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createRouteMatcher.cjs","names":["regexPatterns: RegExp[]"
|
|
1
|
+
{"version":3,"file":"createRouteMatcher.cjs","names":["regexPatterns: RegExp[]"],"sources":["../../../../src/server/proxy/createRouteMatcher.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {NextRequest} from 'next/server';\n\n/**\n * Creates a route matcher function that tests if a request matches any of the given patterns.\n *\n * @param patterns - Array of route patterns to match. Supports glob-like patterns.\n * @returns Function that tests if a request matches any of the patterns\n *\n * @example\n * ```typescript\n * const isProtectedRoute = createRouteMatcher([\n * '/dashboard(.*)',\n * '/admin(.*)',\n * '/profile'\n * ]);\n *\n * if (isProtectedRoute(req)) {\n * // Route is protected\n * }\n * ```\n */\n/**\n * Converts one route pattern to a regular expression source. A parenthesized group such as `(.*)` is kept as\n * regex, so `/dashboard(.*)` matches `/dashboard` and everything under it. Outside a group a `.` is a literal dot\n * and a `*` matches any run of characters.\n */\nconst toRegexSource = (pattern: string): string => {\n let source = '';\n let depth = 0;\n\n for (const char of pattern) {\n if (char === '(') {\n depth += 1;\n source += char;\n } else if (char === ')') {\n depth = Math.max(depth - 1, 0);\n source += char;\n } else if (depth > 0) {\n source += char;\n } else if (char === '.') {\n source += '\\\\.';\n } else if (char === '*') {\n source += '.*';\n } else {\n source += char;\n }\n }\n\n return source;\n};\n\nconst createRouteMatcher = (patterns: string[]): ((req: NextRequest) => boolean) => {\n const regexPatterns: RegExp[] = patterns.map((pattern: string): RegExp => new RegExp(`^${toRegexSource(pattern)}$`));\n\n return (req: NextRequest): boolean => {\n const {pathname} = req.nextUrl;\n return regexPatterns.some((regex: RegExp): boolean => regex.test(pathname));\n };\n};\n\nexport default createRouteMatcher;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAM,iBAAiB,YAA4B;CACjD,IAAI,SAAS;CACb,IAAI,QAAQ;AAEZ,MAAK,MAAM,QAAQ,QACjB,KAAI,SAAS,KAAK;AAChB,WAAS;AACT,YAAU;YACD,SAAS,KAAK;AACvB,UAAQ,KAAK,IAAI,QAAQ,GAAG,EAAE;AAC9B,YAAU;YACD,QAAQ,EACjB,WAAU;UACD,SAAS,IAClB,WAAU;UACD,SAAS,IAClB,WAAU;KAEV,WAAU;AAId,QAAO;;AAGT,MAAM,sBAAsB,aAAwD;CAClF,MAAMA,gBAA0B,SAAS,KAAK,4BAA4B,IAAI,OAAO,IAAI,cAAc,QAAQ,CAAC,GAAG,CAAC;AAEpH,SAAQ,QAA8B;EACpC,MAAM,EAAC,aAAY,IAAI;AACvB,SAAO,cAAc,MAAM,UAA2B,MAAM,KAAK,SAAS,CAAC;;;AAI/E,iCAAe"}
|
|
@@ -3,8 +3,12 @@ const require_loggedOutSessions = require('./loggedOutSessions.cjs');
|
|
|
3
3
|
const require_sessionConstants = require('../constants/sessionConstants.cjs');
|
|
4
4
|
let __thunderid_node = require("@thunderid/node");
|
|
5
5
|
__thunderid_node = require_rolldown_runtime.__toESM(__thunderid_node);
|
|
6
|
-
let
|
|
7
|
-
|
|
6
|
+
let jose_jws_compact_verify = require("jose/jws/compact/verify");
|
|
7
|
+
jose_jws_compact_verify = require_rolldown_runtime.__toESM(jose_jws_compact_verify);
|
|
8
|
+
let jose_jwt_sign = require("jose/jwt/sign");
|
|
9
|
+
jose_jwt_sign = require_rolldown_runtime.__toESM(jose_jwt_sign);
|
|
10
|
+
let jose_jwt_verify = require("jose/jwt/verify");
|
|
11
|
+
jose_jwt_verify = require_rolldown_runtime.__toESM(jose_jwt_verify);
|
|
8
12
|
|
|
9
13
|
//#region src/utils/SessionManager.ts
|
|
10
14
|
/**
|
|
@@ -29,7 +33,7 @@ var SessionManager = class {
|
|
|
29
33
|
*/
|
|
30
34
|
static async createTempSession(sessionId) {
|
|
31
35
|
const secret = this.getSecret();
|
|
32
|
-
return await new
|
|
36
|
+
return await new jose_jwt_sign.SignJWT({
|
|
33
37
|
sessionId,
|
|
34
38
|
type: "temp"
|
|
35
39
|
}).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime("15m").sign(secret);
|
|
@@ -53,7 +57,7 @@ var SessionManager = class {
|
|
|
53
57
|
}
|
|
54
58
|
static async createSessionToken(accessToken, userId, sessionId, scopes, accessTokenTtlSeconds, refreshToken, organizationId, serverSession) {
|
|
55
59
|
const secret = this.getSecret();
|
|
56
|
-
return await new
|
|
60
|
+
return await new jose_jwt_sign.SignJWT({
|
|
57
61
|
accessToken,
|
|
58
62
|
organizationId,
|
|
59
63
|
refreshToken,
|
|
@@ -69,7 +73,7 @@ var SessionManager = class {
|
|
|
69
73
|
*/
|
|
70
74
|
static async verifySessionToken(token) {
|
|
71
75
|
try {
|
|
72
|
-
const { payload } = await (0,
|
|
76
|
+
const { payload } = await (0, jose_jwt_verify.jwtVerify)(token, this.getSecret());
|
|
73
77
|
if (payload["type"] !== "session") throw new Error("Invalid token type");
|
|
74
78
|
await this.refuseLoggedOutSession(payload);
|
|
75
79
|
return payload;
|
|
@@ -90,7 +94,7 @@ var SessionManager = class {
|
|
|
90
94
|
*/
|
|
91
95
|
static async verifySessionTokenForRefresh(token) {
|
|
92
96
|
try {
|
|
93
|
-
const { payload: rawPayload } = await (0,
|
|
97
|
+
const { payload: rawPayload } = await (0, jose_jws_compact_verify.compactVerify)(token, this.getSecret());
|
|
94
98
|
const payload = JSON.parse(new TextDecoder().decode(rawPayload));
|
|
95
99
|
if (payload.type !== "session") throw new Error("Invalid token type");
|
|
96
100
|
await this.refuseLoggedOutSession(payload);
|
|
@@ -104,7 +108,7 @@ var SessionManager = class {
|
|
|
104
108
|
*/
|
|
105
109
|
static async verifyTempSession(token) {
|
|
106
110
|
try {
|
|
107
|
-
const { payload } = await (0,
|
|
111
|
+
const { payload } = await (0, jose_jwt_verify.jwtVerify)(token, this.getSecret());
|
|
108
112
|
if (payload["type"] !== "temp") throw new Error("Invalid token type");
|
|
109
113
|
return { sessionId: payload["sessionId"] };
|
|
110
114
|
} catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionManager.cjs","names":["secret: string | undefined","ThunderIDRuntimeError","secret: Uint8Array","SignJWT","envValue: string | undefined","parsed: number","DEFAULT_SESSION_COOKIE_EXPIRY_TIME","payload: SessionTokenPayload","getLoggedOutSessions","CookieConfig"],"sources":["../../../src/utils/SessionManager.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {ThunderIDRuntimeError, CookieConfig} from '@thunderid/node';\nimport {SignJWT, jwtVerify, compactVerify, JWTPayload} from 'jose';\nimport {getLoggedOutSessions} from './loggedOutSessions';\nimport {DEFAULT_SESSION_COOKIE_EXPIRY_TIME} from '../constants/sessionConstants';\n\n/**\n * Session token payload interface\n */\nexport interface SessionTokenPayload extends JWTPayload {\n /** Expiration timestamp — doubles as the access token expiry (JWT exp == access token exp) */\n exp: number;\n /** Issued at timestamp */\n iat: number;\n /** Organization ID if applicable */\n organizationId?: string;\n /** The refresh token; empty string if not provided by the auth server */\n refreshToken: string;\n /** OAuth scopes */\n scopes: string[];\n /** Session ID */\n sessionId: string;\n /** The server session this session joined, from the `sid` of the ID token. */\n sid?: string;\n /** When the user signed in, in seconds. Kept across token refresh, unlike `iat`. */\n signedInAt?: number;\n /** User ID */\n sub: string;\n /** Token type discriminant — must be 'session' for access-session JWTs */\n type: 'session';\n}\n\n/**\n * Session management utility class for JWT-based session cookies\n */\nclass SessionManager {\n /**\n * Get the signing secret from environment variable\n * Throws error in production if not set\n */\n private static getSecret(): Uint8Array {\n const secret: string | undefined = process.env['THUNDERID_SECRET'];\n\n if (!secret) {\n if (process.env['NODE_ENV'] === 'production') {\n throw new ThunderIDRuntimeError(\n 'THUNDERID_SECRET environment variable is required in production',\n 'session-secret-required',\n 'nextjs',\n 'Set the THUNDERID_SECRET environment variable with a secure random string',\n );\n }\n // Use a default secret for development (not secure)\n // eslint-disable-next-line no-console\n console.warn('Using default secret for development. Set THUNDERID_SECRET for production!');\n return new TextEncoder().encode('development-secret-not-for-production');\n }\n\n return new TextEncoder().encode(secret);\n }\n\n /**\n * Create a temporary session cookie for login initiation\n */\n static async createTempSession(sessionId: string): Promise<string> {\n const secret: Uint8Array = this.getSecret();\n\n const jwt: string = await new SignJWT({\n sessionId,\n type: 'temp',\n })\n .setProtectedHeader({alg: 'HS256'})\n .setIssuedAt()\n .setExpirationTime('15m')\n .sign(secret);\n\n return jwt;\n }\n\n /**\n * Resolve the session cookie expiry time in seconds.\n *\n * Resolution order (first defined value wins):\n * 1. `configuredExpiry` — value from `ThunderIDNodeConfig.sessionCookie?.expiryTime`\n * 2. `THUNDERID_SESSION_COOKIE_EXPIRY_TIME` environment variable\n * 3. `DEFAULT_SESSION_COOKIE_EXPIRY_TIME` (24 hours)\n */\n static resolveSessionCookieExpiry(configuredExpiry?: number): number {\n if (configuredExpiry != null && configuredExpiry > 0) {\n return configuredExpiry;\n }\n\n const envValue: string | undefined = process.env['THUNDERID_SESSION_COOKIE_EXPIRY_TIME'];\n\n if (envValue) {\n const parsed: number = parseInt(envValue, 10);\n\n if (!Number.isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n\n return DEFAULT_SESSION_COOKIE_EXPIRY_TIME;\n }\n\n static async createSessionToken(\n accessToken: string,\n userId: string,\n sessionId: string,\n scopes: string,\n accessTokenTtlSeconds: number,\n refreshToken: string,\n organizationId?: string,\n serverSession?: {sid?: string; signedInAt?: number},\n ): Promise<string> {\n const secret: Uint8Array = this.getSecret();\n\n const jwt: string = await new SignJWT({\n accessToken,\n organizationId,\n refreshToken,\n scopes,\n sessionId,\n // What a back-channel logout matches this session by.\n sid: serverSession?.sid,\n signedInAt: serverSession?.signedInAt ?? Math.floor(Date.now() / 1000),\n type: 'session',\n } as Omit<SessionTokenPayload, 'sub' | 'iat' | 'exp'>)\n .setProtectedHeader({alg: 'HS256'})\n .setSubject(userId)\n .setIssuedAt()\n .setExpirationTime(Math.floor(Date.now() / 1000) + accessTokenTtlSeconds)\n .sign(secret);\n\n return jwt;\n }\n\n /**\n * Verify and decode a session token\n */\n static async verifySessionToken(token: string): Promise<SessionTokenPayload> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload} = await jwtVerify(token, secret);\n\n if (payload['type'] !== 'session') {\n throw new Error('Invalid token type');\n }\n\n await this.refuseLoggedOutSession(payload as SessionTokenPayload);\n\n return payload as SessionTokenPayload;\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-session-token',\n 'nextjs',\n 'Session token verification failed',\n );\n }\n }\n\n /**\n * Verify a session token for refresh. Validates the HMAC signature and the\n * `type === 'session'` discriminant but intentionally skips the `exp` check\n * so an expired access token can still be exchanged for a new one.\n *\n * Session lifetime is still bounded — the cookie's `maxAge` is set from\n * `sessionCookieExpiryTime`, so the browser drops an over-age session regardless\n * of the access-token exp embedded in the JWT.\n *\n * Never use the returned payload for authorization.\n */\n static async verifySessionTokenForRefresh(token: string): Promise<SessionTokenPayload> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload: rawPayload} = await compactVerify(token, secret);\n const payload: SessionTokenPayload = JSON.parse(new TextDecoder().decode(rawPayload)) as SessionTokenPayload;\n\n if (payload.type !== 'session') {\n throw new Error('Invalid token type');\n }\n\n await this.refuseLoggedOutSession(payload);\n\n return payload;\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-session-token-for-refresh',\n 'nextjs',\n 'Session token signature or type check failed during refresh',\n );\n }\n }\n\n /**\n * Verify and decode a temporary session token\n */\n static async verifyTempSession(token: string): Promise<{sessionId: string}> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload} = await jwtVerify(token, secret);\n\n if (payload['type'] !== 'temp') {\n throw new Error('Invalid token type');\n }\n\n return {sessionId: payload['sessionId'] as string};\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid temporary session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-temp-session-token',\n 'nextjs',\n 'Temporary session token verification failed',\n );\n }\n }\n\n /**\n * Get session cookie options\n */\n /**\n * Throws when a back-channel logout has ended the session this cookie carries. The cookie is\n * still correctly signed, so this is the only thing that stops it from being used.\n */\n private static async refuseLoggedOutSession(payload: SessionTokenPayload): Promise<void> {\n const loggedOut: boolean = await getLoggedOutSessions().isLoggedOut({\n sid: payload.sid,\n startedAt: payload.signedInAt ?? payload.iat,\n sub: payload.sub,\n });\n\n if (loggedOut) {\n throw new Error('The session was ended by a back-channel logout');\n }\n }\n\n static getSessionCookieOptions(maxAge: number): {\n httpOnly: boolean;\n maxAge: number;\n path: string;\n sameSite: 'lax';\n secure: boolean;\n } {\n return {\n httpOnly: true,\n maxAge,\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env['NODE_ENV'] === 'production',\n };\n }\n\n /**\n * Get temporary session cookie options\n */\n static getTempSessionCookieOptions(): {\n httpOnly: boolean;\n maxAge: number;\n path: string;\n sameSite: 'lax';\n secure: boolean;\n } {\n return {\n httpOnly: true,\n maxAge: 15 * 60,\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env['NODE_ENV'] === 'production',\n };\n }\n\n /**\n * Get session cookie name\n */\n static getSessionCookieName(): string {\n return CookieConfig.getSessionCookieName();\n }\n\n /**\n * Get temporary session cookie name\n */\n static getTempSessionCookieName(): string {\n return CookieConfig.getTempSessionCookieName();\n }\n}\n\nexport default SessionManager;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionManager.cjs","names":["secret: string | undefined","ThunderIDRuntimeError","secret: Uint8Array","SignJWT","envValue: string | undefined","parsed: number","DEFAULT_SESSION_COOKIE_EXPIRY_TIME","payload: SessionTokenPayload","getLoggedOutSessions","CookieConfig"],"sources":["../../../src/utils/SessionManager.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {ThunderIDRuntimeError, CookieConfig} from '@thunderid/node';\nimport type {JWTPayload} from 'jose';\n// Subpath imports keep the proxy's bundle free of jose's JWE code, whose CompressionStream use the Edge runtime rejects.\nimport {compactVerify} from 'jose/jws/compact/verify';\nimport {SignJWT} from 'jose/jwt/sign';\nimport {jwtVerify} from 'jose/jwt/verify';\nimport {getLoggedOutSessions} from './loggedOutSessions';\nimport {DEFAULT_SESSION_COOKIE_EXPIRY_TIME} from '../constants/sessionConstants';\n\n/**\n * Session token payload interface\n */\nexport interface SessionTokenPayload extends JWTPayload {\n /** Expiration timestamp — doubles as the access token expiry (JWT exp == access token exp) */\n exp: number;\n /** Issued at timestamp */\n iat: number;\n /** Organization ID if applicable */\n organizationId?: string;\n /** The refresh token; empty string if not provided by the auth server */\n refreshToken: string;\n /** OAuth scopes */\n scopes: string[];\n /** Session ID */\n sessionId: string;\n /** The server session this session joined, from the `sid` of the ID token. */\n sid?: string;\n /** When the user signed in, in seconds. Kept across token refresh, unlike `iat`. */\n signedInAt?: number;\n /** User ID */\n sub: string;\n /** Token type discriminant — must be 'session' for access-session JWTs */\n type: 'session';\n}\n\n/**\n * Session management utility class for JWT-based session cookies\n */\nclass SessionManager {\n /**\n * Get the signing secret from environment variable\n * Throws error in production if not set\n */\n private static getSecret(): Uint8Array {\n const secret: string | undefined = process.env['THUNDERID_SECRET'];\n\n if (!secret) {\n if (process.env['NODE_ENV'] === 'production') {\n throw new ThunderIDRuntimeError(\n 'THUNDERID_SECRET environment variable is required in production',\n 'session-secret-required',\n 'nextjs',\n 'Set the THUNDERID_SECRET environment variable with a secure random string',\n );\n }\n // Use a default secret for development (not secure)\n // eslint-disable-next-line no-console\n console.warn('Using default secret for development. Set THUNDERID_SECRET for production!');\n return new TextEncoder().encode('development-secret-not-for-production');\n }\n\n return new TextEncoder().encode(secret);\n }\n\n /**\n * Create a temporary session cookie for login initiation\n */\n static async createTempSession(sessionId: string): Promise<string> {\n const secret: Uint8Array = this.getSecret();\n\n const jwt: string = await new SignJWT({\n sessionId,\n type: 'temp',\n })\n .setProtectedHeader({alg: 'HS256'})\n .setIssuedAt()\n .setExpirationTime('15m')\n .sign(secret);\n\n return jwt;\n }\n\n /**\n * Resolve the session cookie expiry time in seconds.\n *\n * Resolution order (first defined value wins):\n * 1. `configuredExpiry` — value from `ThunderIDNodeConfig.sessionCookie?.expiryTime`\n * 2. `THUNDERID_SESSION_COOKIE_EXPIRY_TIME` environment variable\n * 3. `DEFAULT_SESSION_COOKIE_EXPIRY_TIME` (24 hours)\n */\n static resolveSessionCookieExpiry(configuredExpiry?: number): number {\n if (configuredExpiry != null && configuredExpiry > 0) {\n return configuredExpiry;\n }\n\n const envValue: string | undefined = process.env['THUNDERID_SESSION_COOKIE_EXPIRY_TIME'];\n\n if (envValue) {\n const parsed: number = parseInt(envValue, 10);\n\n if (!Number.isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n\n return DEFAULT_SESSION_COOKIE_EXPIRY_TIME;\n }\n\n static async createSessionToken(\n accessToken: string,\n userId: string,\n sessionId: string,\n scopes: string,\n accessTokenTtlSeconds: number,\n refreshToken: string,\n organizationId?: string,\n serverSession?: {sid?: string; signedInAt?: number},\n ): Promise<string> {\n const secret: Uint8Array = this.getSecret();\n\n const jwt: string = await new SignJWT({\n accessToken,\n organizationId,\n refreshToken,\n scopes,\n sessionId,\n // What a back-channel logout matches this session by.\n sid: serverSession?.sid,\n signedInAt: serverSession?.signedInAt ?? Math.floor(Date.now() / 1000),\n type: 'session',\n } as Omit<SessionTokenPayload, 'sub' | 'iat' | 'exp'>)\n .setProtectedHeader({alg: 'HS256'})\n .setSubject(userId)\n .setIssuedAt()\n .setExpirationTime(Math.floor(Date.now() / 1000) + accessTokenTtlSeconds)\n .sign(secret);\n\n return jwt;\n }\n\n /**\n * Verify and decode a session token\n */\n static async verifySessionToken(token: string): Promise<SessionTokenPayload> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload} = await jwtVerify(token, secret);\n\n if (payload['type'] !== 'session') {\n throw new Error('Invalid token type');\n }\n\n await this.refuseLoggedOutSession(payload as SessionTokenPayload);\n\n return payload as SessionTokenPayload;\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-session-token',\n 'nextjs',\n 'Session token verification failed',\n );\n }\n }\n\n /**\n * Verify a session token for refresh. Validates the HMAC signature and the\n * `type === 'session'` discriminant but intentionally skips the `exp` check\n * so an expired access token can still be exchanged for a new one.\n *\n * Session lifetime is still bounded — the cookie's `maxAge` is set from\n * `sessionCookieExpiryTime`, so the browser drops an over-age session regardless\n * of the access-token exp embedded in the JWT.\n *\n * Never use the returned payload for authorization.\n */\n static async verifySessionTokenForRefresh(token: string): Promise<SessionTokenPayload> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload: rawPayload} = await compactVerify(token, secret);\n const payload: SessionTokenPayload = JSON.parse(new TextDecoder().decode(rawPayload)) as SessionTokenPayload;\n\n if (payload.type !== 'session') {\n throw new Error('Invalid token type');\n }\n\n await this.refuseLoggedOutSession(payload);\n\n return payload;\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-session-token-for-refresh',\n 'nextjs',\n 'Session token signature or type check failed during refresh',\n );\n }\n }\n\n /**\n * Verify and decode a temporary session token\n */\n static async verifyTempSession(token: string): Promise<{sessionId: string}> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload} = await jwtVerify(token, secret);\n\n if (payload['type'] !== 'temp') {\n throw new Error('Invalid token type');\n }\n\n return {sessionId: payload['sessionId'] as string};\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid temporary session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-temp-session-token',\n 'nextjs',\n 'Temporary session token verification failed',\n );\n }\n }\n\n /**\n * Get session cookie options\n */\n /**\n * Throws when a back-channel logout has ended the session this cookie carries. The cookie is\n * still correctly signed, so this is the only thing that stops it from being used.\n */\n private static async refuseLoggedOutSession(payload: SessionTokenPayload): Promise<void> {\n const loggedOut: boolean = await getLoggedOutSessions().isLoggedOut({\n sid: payload.sid,\n startedAt: payload.signedInAt ?? payload.iat,\n sub: payload.sub,\n });\n\n if (loggedOut) {\n throw new Error('The session was ended by a back-channel logout');\n }\n }\n\n static getSessionCookieOptions(maxAge: number): {\n httpOnly: boolean;\n maxAge: number;\n path: string;\n sameSite: 'lax';\n secure: boolean;\n } {\n return {\n httpOnly: true,\n maxAge,\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env['NODE_ENV'] === 'production',\n };\n }\n\n /**\n * Get temporary session cookie options\n */\n static getTempSessionCookieOptions(): {\n httpOnly: boolean;\n maxAge: number;\n path: string;\n sameSite: 'lax';\n secure: boolean;\n } {\n return {\n httpOnly: true,\n maxAge: 15 * 60,\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env['NODE_ENV'] === 'production',\n };\n }\n\n /**\n * Get session cookie name\n */\n static getSessionCookieName(): string {\n return CookieConfig.getSessionCookieName();\n }\n\n /**\n * Get temporary session cookie name\n */\n static getTempSessionCookieName(): string {\n return CookieConfig.getTempSessionCookieName();\n }\n}\n\nexport default SessionManager;\n"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { FC } from 'react';
|
|
|
4
4
|
* Props for the SignIn component.
|
|
5
5
|
* Extends BaseSignInProps for full compatibility with the React BaseSignIn component
|
|
6
6
|
*/
|
|
7
|
-
export type SignInProps = Pick<BaseSignInProps, 'className' | 'id' | 'onSuccess' | 'onError' | 'variant' | 'size'>;
|
|
7
|
+
export type SignInProps = Pick<BaseSignInProps, 'children' | 'className' | 'id' | 'onSuccess' | 'onError' | 'variant' | 'size'>;
|
|
8
8
|
/**
|
|
9
9
|
* A SignIn component for Next.js that provides native authentication flow.
|
|
10
10
|
* This component delegates to the BaseSignIn from @thunderid/react. Unlike BaseSignUp,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignIn.d.ts","sourceRoot":"","sources":["../../../../../src/client/components/presentation/SignIn/SignIn.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAa,eAAe,EAAwC,MAAM,kBAAkB,CAAC;AACpG,OAAO,EAAC,EAAE,EAA8B,MAAM,OAAO,CAAC;AAGtD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"SignIn.d.ts","sourceRoot":"","sources":["../../../../../src/client/components/presentation/SignIn/SignIn.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAa,eAAe,EAAwC,MAAM,kBAAkB,CAAC;AACpG,OAAO,EAAC,EAAE,EAA8B,MAAM,OAAO,CAAC;AAGtD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,IAAI,CAC5B,eAAe,EACf,UAAU,GAAG,WAAW,GAAG,IAAI,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAC/E,CAAC;AAEF;;;;;GAKG;AACH,QAAA,MAAM,MAAM,EAAE,EAAE,CAAC,WAAW,CAmG3B,CAAC;AAIF,eAAe,MAAM,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignIn.js","names":["SignIn: FC<SignInProps>","useThunderID","normalizedError: Error","response: any"],"sources":["../../../../../src/client/components/presentation/SignIn/SignIn.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use client';\n\nimport {EmbeddedFlowComponent, EmbeddedFlowType, ThunderIDRuntimeError} from '@thunderid/node';\nimport {BaseSignIn, BaseSignInProps, normalizeFlowResponse, useTranslation} from '@thunderid/react';\nimport {FC, useEffect, useRef, useState} from 'react';\nimport useThunderID from '../../../contexts/ThunderID/useThunderID';\n\n/**\n * Props for the SignIn component.\n * Extends BaseSignInProps for full compatibility with the React BaseSignIn component\n */\nexport type SignInProps = Pick
|
|
1
|
+
{"version":3,"file":"SignIn.js","names":["SignIn: FC<SignInProps>","useThunderID","normalizedError: Error","response: any"],"sources":["../../../../../src/client/components/presentation/SignIn/SignIn.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use client';\n\nimport {EmbeddedFlowComponent, EmbeddedFlowType, ThunderIDRuntimeError} from '@thunderid/node';\nimport {BaseSignIn, BaseSignInProps, normalizeFlowResponse, useTranslation} from '@thunderid/react';\nimport {FC, useEffect, useRef, useState} from 'react';\nimport useThunderID from '../../../contexts/ThunderID/useThunderID';\n\n/**\n * Props for the SignIn component.\n * Extends BaseSignInProps for full compatibility with the React BaseSignIn component\n */\nexport type SignInProps = Pick<\n BaseSignInProps,\n 'children' | 'className' | 'id' | 'onSuccess' | 'onError' | 'variant' | 'size'\n>;\n\n/**\n * A SignIn component for Next.js that provides native authentication flow.\n * This component delegates to the BaseSignIn from @thunderid/react. Unlike BaseSignUp,\n * BaseSignIn does not manage its own flow state, so this component initiates the flow\n * (using the server-action-backed `signIn`) and tracks `components`/`executionId` itself.\n */\nconst SignIn: FC<SignInProps> = ({size = 'medium', variant = 'outlined', onError, ...rest}: SignInProps) => {\n const {signIn, applicationId, scopes} = useThunderID();\n const {t} = useTranslation();\n\n const [components, setComponents] = useState<EmbeddedFlowComponent[]>([]);\n const [additionalData, setAdditionalData] = useState<Record<string, any>>({});\n const [executionId, setExecutionId] = useState<string | undefined>(undefined);\n const [isFlowInitialized, setIsFlowInitialized] = useState(false);\n const [flowError, setFlowError] = useState<Error | null>(null);\n const initializationAttemptedRef = useRef(false);\n // A fresh challenge token is required on every step to prevent replay attacks; the server\n // rotates it on each response, so it's tracked in a ref (not state) to avoid a stale value\n // from a closure captured before the previous response's state update committed.\n const challengeTokenRef = useRef<string | undefined>(undefined);\n\n const applyFlowResponse = (response: any): void => {\n const normalized = normalizeFlowResponse(response, t, {resolveTranslations: false});\n\n challengeTokenRef.current = response?.challengeToken ?? undefined;\n setExecutionId(normalized.executionId ?? undefined);\n setComponents(normalized.components);\n setAdditionalData(normalized.additionalData);\n };\n\n const handleFlowError = (error: unknown): void => {\n const normalizedError: Error = error instanceof Error ? error : new Error(String(error));\n\n setFlowError(normalizedError);\n onError?.(normalizedError);\n };\n\n useEffect(() => {\n if (initializationAttemptedRef.current || !signIn) {\n return;\n }\n\n initializationAttemptedRef.current = true;\n\n (async (): Promise<void> => {\n try {\n const response: any = await signIn(\n {\n applicationId,\n flowType: EmbeddedFlowType.Authentication,\n ...(scopes && {scopes}),\n },\n {},\n );\n\n // `response` is undefined when the provider already redirected\n // (OAuth-redirect or completed flows are handled there directly).\n if (response) {\n applyFlowResponse(response);\n }\n } catch (error) {\n handleFlowError(error);\n } finally {\n setIsFlowInitialized(true);\n }\n })();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [signIn]);\n\n const handleOnSubmit = async (payload: any): Promise<void> => {\n if (!signIn) {\n throw new ThunderIDRuntimeError(\n '`signIn` function is not available.',\n 'SignIn-handleOnSubmit-RuntimeError-001',\n 'nextjs',\n );\n }\n\n const response: any = await signIn(\n {\n ...payload,\n executionId,\n ...(challengeTokenRef.current ? {challengeToken: challengeTokenRef.current} : {}),\n },\n {},\n );\n\n if (response) {\n applyFlowResponse(response);\n }\n };\n\n return (\n <BaseSignIn\n additionalData={additionalData}\n components={components}\n error={flowError}\n isLoading={!isFlowInitialized}\n onError={onError}\n onSubmit={handleOnSubmit}\n size={size}\n variant={variant}\n {...rest}\n />\n );\n};\n\nSignIn.displayName = 'SignIn';\n\nexport default SignIn;\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,MAAMA,UAA2B,EAAC,OAAO,UAAU,UAAU,YAAY,QAAS,GAAG,WAAuB;CAC1G,MAAM,EAAC,QAAQ,eAAe,WAAUC,sBAAc;CACtD,MAAM,EAAC,MAAK,gBAAgB;CAE5B,MAAM,CAAC,YAAY,iBAAiB,SAAkC,EAAE,CAAC;CACzE,MAAM,CAAC,gBAAgB,qBAAqB,SAA8B,EAAE,CAAC;CAC7E,MAAM,CAAC,aAAa,kBAAkB,SAA6B,OAAU;CAC7E,MAAM,CAAC,mBAAmB,wBAAwB,SAAS,MAAM;CACjE,MAAM,CAAC,WAAW,gBAAgB,SAAuB,KAAK;CAC9D,MAAM,6BAA6B,OAAO,MAAM;CAIhD,MAAM,oBAAoB,OAA2B,OAAU;CAE/D,MAAM,qBAAqB,aAAwB;EACjD,MAAM,aAAa,sBAAsB,UAAU,GAAG,EAAC,qBAAqB,OAAM,CAAC;AAEnF,oBAAkB,UAAU,UAAU,kBAAkB;AACxD,iBAAe,WAAW,eAAe,OAAU;AACnD,gBAAc,WAAW,WAAW;AACpC,oBAAkB,WAAW,eAAe;;CAG9C,MAAM,mBAAmB,UAAyB;EAChD,MAAMC,kBAAyB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AAExF,eAAa,gBAAgB;AAC7B,YAAU,gBAAgB;;AAG5B,iBAAgB;AACd,MAAI,2BAA2B,WAAW,CAAC,OACzC;AAGF,6BAA2B,UAAU;AAErC,GAAC,YAA2B;AAC1B,OAAI;IACF,MAAMC,WAAgB,MAAM,OAC1B;KACE;KACA,UAAU,iBAAiB;KAC3B,GAAI,UAAU,EAAC,QAAO;KACvB,EACD,EAAE,CACH;AAID,QAAI,SACF,mBAAkB,SAAS;YAEtB,OAAO;AACd,oBAAgB,MAAM;aACd;AACR,yBAAqB,KAAK;;MAE1B;IAEH,CAAC,OAAO,CAAC;CAEZ,MAAM,iBAAiB,OAAO,YAAgC;AAC5D,MAAI,CAAC,OACH,OAAM,IAAI,sBACR,uCACA,0CACA,SACD;EAGH,MAAMA,WAAgB,MAAM,OAC1B;GACE,GAAG;GACH;GACA,GAAI,kBAAkB,UAAU,EAAC,gBAAgB,kBAAkB,SAAQ,GAAG,EAAE;GACjF,EACD,EAAE,CACH;AAED,MAAI,SACF,mBAAkB,SAAS;;AAI/B,QACE,oBAAC;EACiB;EACJ;EACZ,OAAO;EACP,WAAW,CAAC;EACH;EACT,UAAU;EACJ;EACG;EACT,GAAI;GACJ;;AAIN,OAAO,cAAc;AAErB,qBAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignUp.d.ts","sourceRoot":"","sources":["../../../../../src/client/components/presentation/SignUp/SignUp.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAa,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAC,EAAE,EAAC,MAAM,OAAO,CAAC;AAGzB;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,QAAA,MAAM,MAAM,EAAE,EAAE,CAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"SignUp.d.ts","sourceRoot":"","sources":["../../../../../src/client/components/presentation/SignUp/SignUp.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAa,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAC,EAAE,EAAC,MAAM,OAAO,CAAC;AAGzB;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,QAAA,MAAM,MAAM,EAAE,EAAE,CAAC,WAAW,CA2D3B,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
|
@@ -37,7 +37,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
37
37
|
* };
|
|
38
38
|
* ```
|
|
39
39
|
*/
|
|
40
|
-
const SignUp = ({ className, size = "medium", variant = "outlined", afterSignUpUrl, onError }) => {
|
|
40
|
+
const SignUp = ({ className, size = "medium", variant = "outlined", afterSignUpUrl, onError, children }) => {
|
|
41
41
|
const { signUp, applicationId: contextApplicationId, scopes } = useThunderID_default();
|
|
42
42
|
/**
|
|
43
43
|
* Initialize the sign-up flow.
|
|
@@ -66,7 +66,8 @@ const SignUp = ({ className, size = "medium", variant = "outlined", afterSignUpU
|
|
|
66
66
|
className,
|
|
67
67
|
size,
|
|
68
68
|
variant,
|
|
69
|
-
isInitialized: true
|
|
69
|
+
isInitialized: true,
|
|
70
|
+
children
|
|
70
71
|
});
|
|
71
72
|
};
|
|
72
73
|
var SignUp_default = SignUp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignUp.js","names":["SignUp: FC<SignUpProps>","useThunderID"],"sources":["../../../../../src/client/components/presentation/SignUp/SignUp.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use client';\n\nimport {ThunderIDRuntimeError, EmbeddedFlowType} from '@thunderid/node';\nimport {BaseSignUp, BaseSignUpProps} from '@thunderid/react';\nimport {FC} from 'react';\nimport useThunderID from '../../../contexts/ThunderID/useThunderID';\n\n/**\n * Props for the SignUp component.\n */\nexport type SignUpProps = BaseSignUpProps;\n\n/**\n * A styled SignUp component that provides embedded sign-up flow with pre-built styling.\n * This component handles the API calls for sign-up and delegates UI logic to BaseSignUp.\n *\n * @example\n * ```tsx\n * import { SignUp } from '@thunderid/react';\n *\n * const App = () => {\n * return (\n * <SignUp\n * onSuccess={(response) => {\n * console.log('Sign-up successful:', response);\n * // Handle successful sign-up (e.g., redirect, show confirmation)\n * }}\n * onError={(error) => {\n * console.error('Sign-up failed:', error);\n * }}\n * onComplete={(redirectUrl) => {\n * // Platform-specific redirect handling (e.g., Next.js router.push)\n * router.push(redirectUrl); // or window.location.href = redirectUrl\n * }}\n * size=\"medium\"\n * variant=\"outlined\"\n * afterSignUpUrl=\"/welcome\"\n * />\n * );\n * };\n * ```\n */\nconst SignUp: FC<SignUpProps> = ({\n className,\n size = 'medium',\n variant = 'outlined',\n afterSignUpUrl,\n onError,\n}: SignUpProps) => {\n const {signUp, applicationId: contextApplicationId, scopes} = useThunderID();\n\n /**\n * Initialize the sign-up flow.\n */\n const handleInitialize = async (payload?: any): Promise<any> => {\n if (!signUp) {\n throw new ThunderIDRuntimeError(\n '`signUp` function is not available.',\n 'SignUp-handleInitialize-RuntimeError-001',\n 'nextjs',\n );\n }\n\n return (await signUp({\n flowType: EmbeddedFlowType.Registration,\n ...(contextApplicationId && {applicationId: contextApplicationId}),\n ...(scopes && {scopes}),\n ...payload,\n })) as unknown as Promise<any>;\n };\n\n /**\n * Handle sign-up steps.\n */\n const handleOnSubmit = async (payload: any): Promise<any> => {\n if (!signUp) {\n throw new ThunderIDRuntimeError(\n '`signUp` function is not available.',\n 'SignUp-handleOnSubmit-RuntimeError-001',\n 'nextjs',\n );\n }\n\n return (await signUp(payload)) as unknown as Promise<any>;\n };\n\n return (\n <BaseSignUp\n afterSignUpUrl={afterSignUpUrl}\n onInitialize={handleInitialize}\n onSubmit={handleOnSubmit}\n onError={onError}\n className={className}\n size={size}\n variant={variant}\n isInitialized={true}\n
|
|
1
|
+
{"version":3,"file":"SignUp.js","names":["SignUp: FC<SignUpProps>","useThunderID"],"sources":["../../../../../src/client/components/presentation/SignUp/SignUp.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use client';\n\nimport {ThunderIDRuntimeError, EmbeddedFlowType} from '@thunderid/node';\nimport {BaseSignUp, BaseSignUpProps} from '@thunderid/react';\nimport {FC} from 'react';\nimport useThunderID from '../../../contexts/ThunderID/useThunderID';\n\n/**\n * Props for the SignUp component.\n */\nexport type SignUpProps = BaseSignUpProps;\n\n/**\n * A styled SignUp component that provides embedded sign-up flow with pre-built styling.\n * This component handles the API calls for sign-up and delegates UI logic to BaseSignUp.\n *\n * @example\n * ```tsx\n * import { SignUp } from '@thunderid/react';\n *\n * const App = () => {\n * return (\n * <SignUp\n * onSuccess={(response) => {\n * console.log('Sign-up successful:', response);\n * // Handle successful sign-up (e.g., redirect, show confirmation)\n * }}\n * onError={(error) => {\n * console.error('Sign-up failed:', error);\n * }}\n * onComplete={(redirectUrl) => {\n * // Platform-specific redirect handling (e.g., Next.js router.push)\n * router.push(redirectUrl); // or window.location.href = redirectUrl\n * }}\n * size=\"medium\"\n * variant=\"outlined\"\n * afterSignUpUrl=\"/welcome\"\n * />\n * );\n * };\n * ```\n */\nconst SignUp: FC<SignUpProps> = ({\n className,\n size = 'medium',\n variant = 'outlined',\n afterSignUpUrl,\n onError,\n children,\n}: SignUpProps) => {\n const {signUp, applicationId: contextApplicationId, scopes} = useThunderID();\n\n /**\n * Initialize the sign-up flow.\n */\n const handleInitialize = async (payload?: any): Promise<any> => {\n if (!signUp) {\n throw new ThunderIDRuntimeError(\n '`signUp` function is not available.',\n 'SignUp-handleInitialize-RuntimeError-001',\n 'nextjs',\n );\n }\n\n return (await signUp({\n flowType: EmbeddedFlowType.Registration,\n ...(contextApplicationId && {applicationId: contextApplicationId}),\n ...(scopes && {scopes}),\n ...payload,\n })) as unknown as Promise<any>;\n };\n\n /**\n * Handle sign-up steps.\n */\n const handleOnSubmit = async (payload: any): Promise<any> => {\n if (!signUp) {\n throw new ThunderIDRuntimeError(\n '`signUp` function is not available.',\n 'SignUp-handleOnSubmit-RuntimeError-001',\n 'nextjs',\n );\n }\n\n return (await signUp(payload)) as unknown as Promise<any>;\n };\n\n return (\n <BaseSignUp\n afterSignUpUrl={afterSignUpUrl}\n onInitialize={handleInitialize}\n onSubmit={handleOnSubmit}\n onError={onError}\n className={className}\n size={size}\n variant={variant}\n isInitialized={true}\n >\n {children}\n </BaseSignUp>\n );\n};\n\nexport default SignUp;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,MAAMA,UAA2B,EAC/B,WACA,OAAO,UACP,UAAU,YACV,gBACA,SACA,eACiB;CACjB,MAAM,EAAC,QAAQ,eAAe,sBAAsB,WAAUC,sBAAc;;;;CAK5E,MAAM,mBAAmB,OAAO,YAAgC;AAC9D,MAAI,CAAC,OACH,OAAM,IAAI,sBACR,uCACA,4CACA,SACD;AAGH,SAAQ,MAAM,OAAO;GACnB,UAAU,iBAAiB;GAC3B,GAAI,wBAAwB,EAAC,eAAe,sBAAqB;GACjE,GAAI,UAAU,EAAC,QAAO;GACtB,GAAG;GACJ,CAAC;;;;;CAMJ,MAAM,iBAAiB,OAAO,YAA+B;AAC3D,MAAI,CAAC,OACH,OAAM,IAAI,sBACR,uCACA,0CACA,SACD;AAGH,SAAQ,MAAM,OAAO,QAAQ;;AAG/B,QACE,oBAAC;EACiB;EAChB,cAAc;EACd,UAAU;EACD;EACE;EACL;EACG;EACT,eAAe;EAEd;GACU;;AAIjB,qBAAe"}
|
|
@@ -9,17 +9,6 @@ import { RefreshResult } from '../../../server/actions/refreshToken';
|
|
|
9
9
|
export type ThunderIDClientProviderProps = Partial<Omit<ThunderIDProviderProps, 'baseUrl' | 'clientId'>> & Pick<ThunderIDProviderProps, 'baseUrl' | 'clientId'> & {
|
|
10
10
|
applicationId: ThunderIDContextProps['applicationId'];
|
|
11
11
|
clearSession: () => Promise<void>;
|
|
12
|
-
/**
|
|
13
|
-
* Server Action that fetches `GET /flow/meta` server-side, passed down to `FlowMetaProvider` so
|
|
14
|
-
* it never calls `baseUrl` directly from the browser — avoiding the CORS requirement that a
|
|
15
|
-
* direct client-side fetch to the ThunderID server would otherwise impose. Used for both the
|
|
16
|
-
* initial fetch (when `initialMeta` isn't seeded, e.g. it failed server-side) and subsequent
|
|
17
|
-
* `switchLanguage()` calls.
|
|
18
|
-
*/
|
|
19
|
-
fetchMeta?: (params: {
|
|
20
|
-
applicationId?: string;
|
|
21
|
-
language?: string;
|
|
22
|
-
}) => Promise<FlowMetadataResponse>;
|
|
23
12
|
handleOAuthCallback: (code: string, state: string) => Promise<{
|
|
24
13
|
error?: string;
|
|
25
14
|
redirectUrl?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThunderIDProvider.d.ts","sourceRoot":"","sources":["../../../../src/client/contexts/ThunderID/ThunderIDProvider.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,eAAe,EAEf,oBAAoB,EAEpB,qBAAqB,EACrB,IAAI,EACJ,WAAW,EAGZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAQL,sBAAsB,EACvB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAC,EAAE,EAAE,iBAAiB,EAAkD,MAAM,OAAO,CAAC;AAC7F,OAAyB,EAAC,qBAAqB,EAAC,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAC,aAAa,EAAC,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"ThunderIDProvider.d.ts","sourceRoot":"","sources":["../../../../src/client/contexts/ThunderID/ThunderIDProvider.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,eAAe,EAEf,oBAAoB,EAEpB,qBAAqB,EACrB,IAAI,EACJ,WAAW,EAGZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAQL,sBAAsB,EACvB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAC,EAAE,EAAE,iBAAiB,EAAkD,MAAM,OAAO,CAAC;AAC7F,OAAyB,EAAC,qBAAqB,EAAC,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAC,aAAa,EAAC,MAAM,sCAAsC,CAAC;AAInE;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,OAAO,CAAC,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,UAAU,CAAC,CAAC,GACtG,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,UAAU,CAAC,GAAG;IACrD,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAC;IACtD,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,mBAAmB,EAAE,CACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,KACV,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;IACvE;;;OAGG;IACH,WAAW,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,qBAAqB,CAAC,oBAAoB,CAAC,CAAC;IAChE,YAAY,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3C,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IACxC,OAAO,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IACxC,aAAa,EAAE,CACb,aAAa,EAAE,qBAAqB,EACpC,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC;QAAC,IAAI,EAAE;YAAC,IAAI,EAAE,IAAI,CAAA;SAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;IACpE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,IAAI,CAAC;CACrD,CAAC;AAEJ,QAAA,MAAM,uBAAuB,EAAE,EAAE,CAAC,iBAAiB,CAAC,4BAA4B,CAAC,CA0VhF,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import logger_default from "../../../utils/logger.js";
|
|
5
5
|
import ThunderIDContext_default from "./ThunderIDContext.js";
|
|
6
|
+
import fetchFlowMetaFromServer_default from "../../fetchFlowMetaFromServer.js";
|
|
6
7
|
import { ThunderIDRuntimeError, generateFlattenedUserProfile, getVendorPrefix } from "@thunderid/node";
|
|
7
8
|
import { FlowMetaProvider, FlowProvider, I18nProvider, ThemeProvider, ThunderIDContext, UserProvider } from "@thunderid/react";
|
|
8
9
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
@@ -10,7 +11,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
10
11
|
import { jsx } from "react/jsx-runtime";
|
|
11
12
|
|
|
12
13
|
//#region src/client/contexts/ThunderID/ThunderIDProvider.tsx
|
|
13
|
-
const ThunderIDClientProvider = ({ baseUrl, children, signIn, clearSession, refreshToken, signOut, signUp, handleOAuthCallback, preferences, isSignedIn, signInUrl, signUpUrl, user: _user, userProfile: _userProfile, userSchema: _userSchema = null, updateProfile, applicationId, organizationHandle, scopes, vendor, initialMeta = null
|
|
14
|
+
const ThunderIDClientProvider = ({ baseUrl, children, signIn, clearSession, refreshToken, signOut, signUp, handleOAuthCallback, preferences, isSignedIn, signInUrl, signUpUrl, user: _user, userProfile: _userProfile, userSchema: _userSchema = null, updateProfile, applicationId, organizationHandle, scopes, vendor, initialMeta = null }) => {
|
|
14
15
|
const reRenderCheckRef = useRef(false);
|
|
15
16
|
const router = useRouter();
|
|
16
17
|
const searchParams = useSearchParams();
|
|
@@ -200,7 +201,7 @@ const ThunderIDClientProvider = ({ baseUrl, children, signIn, clearSession, refr
|
|
|
200
201
|
children: /* @__PURE__ */ jsx(FlowMetaProvider, {
|
|
201
202
|
enabled: preferences?.resolveFromMeta !== false,
|
|
202
203
|
initialMeta,
|
|
203
|
-
fetchMeta,
|
|
204
|
+
fetchMeta: fetchFlowMetaFromServer_default,
|
|
204
205
|
children: /* @__PURE__ */ jsx(ThemeProvider, {
|
|
205
206
|
theme: preferences?.theme?.overrides,
|
|
206
207
|
mode: preferences?.theme?.mode,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThunderIDProvider.js","names":["ThunderIDClientProvider: FC<PropsWithChildren<ThunderIDClientProviderProps>>","reRenderCheckRef: RefObject<boolean>","router: AppRouterInstance","searchParams: ReadonlyURLSearchParams","code: string | null","state: string | null","result: {error?: string; redirectUrl?: string; success: boolean}","result: any","contextValue: ThunderIDContextProps","reactContextValue: ReactThunderIDContextProps","ThunderIDContext","ReactThunderIDContext"],"sources":["../../../../src/client/contexts/ThunderID/ThunderIDProvider.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use client';\n\nimport {\n AttributeSchema,\n EmbeddedFlowExecuteRequestConfig,\n FlowMetadataResponse,\n generateFlattenedUserProfile,\n UpdateMeProfileConfig,\n User,\n UserProfile,\n ThunderIDRuntimeError,\n getVendorPrefix,\n} from '@thunderid/node';\nimport {\n I18nProvider,\n FlowMetaProvider,\n FlowProvider,\n UserProvider,\n ThemeProvider,\n ThunderIDContext as ReactThunderIDContext,\n ThunderIDContextProps as ReactThunderIDContextProps,\n ThunderIDProviderProps,\n} from '@thunderid/react';\nimport {ReadonlyURLSearchParams} from 'next/dist/client/components/navigation.react-server';\nimport {AppRouterInstance} from 'next/dist/shared/lib/app-router-context.shared-runtime';\nimport {useRouter, useSearchParams} from 'next/navigation';\nimport {FC, PropsWithChildren, RefObject, useEffect, useMemo, useRef, useState} from 'react';\nimport ThunderIDContext, {ThunderIDContextProps} from './ThunderIDContext';\nimport {RefreshResult} from '../../../server/actions/refreshToken';\nimport logger from '../../../utils/logger';\n\n/**\n * Props interface of {@link ThunderIDClientProvider}\n */\nexport type ThunderIDClientProviderProps = Partial<Omit<ThunderIDProviderProps, 'baseUrl' | 'clientId'>> &\n Pick<ThunderIDProviderProps, 'baseUrl' | 'clientId'> & {\n applicationId: ThunderIDContextProps['applicationId'];\n clearSession: () => Promise<void>;\n /**\n * Server Action that fetches `GET /flow/meta` server-side, passed down to `FlowMetaProvider` so\n * it never calls `baseUrl` directly from the browser — avoiding the CORS requirement that a\n * direct client-side fetch to the ThunderID server would otherwise impose. Used for both the\n * initial fetch (when `initialMeta` isn't seeded, e.g. it failed server-side) and subsequent\n * `switchLanguage()` calls.\n */\n fetchMeta?: (params: {applicationId?: string; language?: string}) => Promise<FlowMetadataResponse>;\n handleOAuthCallback: (\n code: string,\n state: string,\n ) => Promise<{error?: string; redirectUrl?: string; success: boolean}>;\n /**\n * Flow metadata fetched server-side ahead of time, seeding `FlowMetaProvider` so it can skip\n * its own initial client-side fetch (avoiding a flash of untranslated i18n keys).\n */\n initialMeta?: FlowMetadataResponse | null;\n isSignedIn: boolean;\n organizationHandle: ThunderIDContextProps['organizationHandle'];\n refreshToken: () => Promise<RefreshResult>;\n signIn: ThunderIDContextProps['signIn'];\n signOut: ThunderIDContextProps['signOut'];\n signUp: ThunderIDContextProps['signUp'];\n updateProfile: (\n requestConfig: UpdateMeProfileConfig,\n sessionId?: string,\n ) => Promise<{data: {user: User}; error: string; success: boolean}>;\n user: User | null;\n userProfile: UserProfile;\n userSchema?: Record<string, AttributeSchema> | null;\n };\n\nconst ThunderIDClientProvider: FC<PropsWithChildren<ThunderIDClientProviderProps>> = ({\n baseUrl,\n children,\n signIn,\n clearSession,\n refreshToken,\n signOut,\n signUp,\n handleOAuthCallback,\n preferences,\n isSignedIn,\n signInUrl,\n signUpUrl,\n user: _user,\n userProfile: _userProfile,\n userSchema: _userSchema = null,\n updateProfile,\n applicationId,\n organizationHandle,\n scopes,\n vendor,\n initialMeta = null,\n fetchMeta,\n}: PropsWithChildren<ThunderIDClientProviderProps>) => {\n const reRenderCheckRef: RefObject<boolean> = useRef(false);\n const router: AppRouterInstance = useRouter();\n const searchParams: ReadonlyURLSearchParams = useSearchParams();\n const [isLoading, setIsLoading] = useState<boolean>(true);\n const [user, setUser] = useState<User | null>(_user);\n const [userProfile, setUserProfile] = useState<UserProfile>(_userProfile);\n const [userSchema, setUserSchema] = useState<Record<string, AttributeSchema> | null>(_userSchema);\n\n useEffect(() => {\n setUserProfile(_userProfile);\n }, [_userProfile]);\n\n useEffect(() => {\n setUserSchema(_userSchema);\n }, [_userSchema]);\n\n useEffect(() => {\n setUser(_user);\n }, [_user]);\n\n // Handle OAuth callback automatically\n useEffect(() => {\n // React 18.x Strict.Mode has a new check for `Ensuring reusable state` to facilitate an upcoming react feature.\n // https://reactjs.org/docs/strict-mode.html#ensuring-reusable-state\n // This will remount all the useEffects to ensure that there are no unexpected side effects.\n // When react remounts the signIn hook of the AuthProvider, it will cause a race condition. Hence, we have to\n // prevent the re-render of this hook as suggested in the following discussion.\n // https://github.com/reactwg/react-18/discussions/18#discussioncomment-795623\n if (reRenderCheckRef.current) {\n return;\n }\n\n reRenderCheckRef.current = true;\n\n // Don't handle callback if already signed in\n if (isSignedIn) return;\n\n (async (): Promise<void> => {\n try {\n const code: string | null = searchParams.get('code');\n const state: string | null = searchParams.get('state');\n const error: string | null = searchParams.get('error');\n\n // Check for OAuth errors first\n if (error) {\n logger.error('[ThunderIDClientProvider] An error was received for the initiated sign-in request.');\n\n return;\n }\n\n // Handle OAuth callback if code and state are present\n if (code && state) {\n setIsLoading(true);\n\n const result: {error?: string; redirectUrl?: string; success: boolean} = await handleOAuthCallback(\n code,\n state,\n );\n\n if (result.success) {\n // Redirect to the success URL\n if (result.redirectUrl) {\n router.push(result.redirectUrl);\n } else {\n // Refresh the page to update authentication state\n window.location.reload();\n }\n } else {\n logger.error(\n `[ThunderIDClientProvider] An error occurred while signing in: ${result.error || 'Authentication failed'}`,\n );\n }\n }\n } catch (error) {\n logger.error('[ThunderIDClientProvider] Failed to handle OAuth callback:', error);\n }\n })();\n }, []);\n\n useEffect(() => {\n // Set loading to false when server has resolved authentication state\n setIsLoading(false);\n }, [isSignedIn, user]);\n\n const handleSignIn = async (payload: any, request: EmbeddedFlowExecuteRequestConfig): Promise<any> => {\n if (!signIn) {\n throw new ThunderIDRuntimeError(\n '`signIn` function is not available.',\n 'ThunderIDClientProvider-handleSignIn-RuntimeError-001',\n 'nextjs',\n );\n }\n\n const result: any = await signIn(payload, request);\n\n // Redirect based flow URL is sent as `signInUrl` in the response.\n // Use window.location.href instead of router.push() — the OAuth authorization\n // endpoint is on an external server, and router.push() would send RSC fetch\n // headers that the identity provider doesn't understand, causing a CORS error.\n if (result?.data?.signInUrl) {\n window.location.href = result.data.signInUrl;\n\n return undefined;\n }\n\n // After the Embedded flow is successful, the URL to navigate next is sent as `afterSignInUrl` in the response.\n if (result?.data?.afterSignInUrl) {\n router.push(result.data.afterSignInUrl);\n\n return undefined;\n }\n\n if (result?.error) {\n throw new Error(result.error);\n }\n\n return result?.data ?? result;\n };\n\n const handleSignUp = async (payload: any, request: EmbeddedFlowExecuteRequestConfig): Promise<any> => {\n if (!signUp) {\n throw new ThunderIDRuntimeError(\n '`signUp` function is not available.',\n 'ThunderIDClientProvider-handleSignUp-RuntimeError-001',\n 'nextjs',\n );\n }\n\n const result: any = await signUp(payload, request);\n\n // Redirect based flow URL is sent as `signUpUrl` in the response.\n if (result?.data?.signUpUrl) {\n router.push(result.data.signUpUrl);\n\n return undefined;\n }\n\n // After the Embedded flow is successful, the URL to navigate next is sent as `afterSignUpUrl` in the response.\n if (result?.data?.afterSignUpUrl) {\n router.push(result.data.afterSignUpUrl);\n\n return undefined;\n }\n\n if (result?.error) {\n throw new Error(result.error);\n }\n\n return result?.data ?? result;\n };\n\n const handleSignOut = async (): Promise<any> => {\n logger.debug('[ThunderIDClientProvider][handleSignOut] `handleSignOut` called.');\n\n try {\n const result: any = await signOut();\n\n logger.debug('[ThunderIDClientProvider][handleSignOut] Sign out result:', result);\n\n if (result?.data?.afterSignOutUrl) {\n router.push(result.data.afterSignOutUrl);\n\n return {location: result.data.afterSignOutUrl, redirected: true};\n }\n\n if (result?.error) {\n logger.error(\n '[ThunderIDClientProvider][handleSignOut] Error result was returned during signing the user out with a button click:',\n result.error,\n );\n }\n\n return result?.data ?? result;\n } catch (error) {\n logger.error(\n '[ThunderIDClientProvider][handleSignOut] Error occurred during signing the user out with a button click:',\n error,\n );\n\n return undefined;\n }\n };\n\n const contextValue: ThunderIDContextProps = useMemo(\n () => ({\n applicationId,\n baseUrl,\n clearSession,\n isLoading,\n isSignedIn,\n organizationHandle,\n preferences,\n refreshToken,\n scopes,\n signIn: handleSignIn,\n signInUrl,\n signOut: handleSignOut,\n signUp: handleSignUp,\n signUpUrl,\n user,\n }),\n [\n baseUrl,\n user,\n isSignedIn,\n isLoading,\n signInUrl,\n signUpUrl,\n applicationId,\n organizationHandle,\n scopes,\n preferences,\n ],\n );\n\n const handleProfileUpdate = (payload: User): void => {\n setUser(payload);\n setUserProfile((prev: UserProfile) => ({\n ...prev,\n flattenedProfile: generateFlattenedUserProfile(payload),\n profile: payload,\n }));\n };\n\n // Bridge into @thunderid/react's own ThunderIDContext. Internal react components rendered by\n // BaseSignIn/BaseSignUp — most notably FlowMetaProvider, which fetches `/flow/meta` and injects\n // its i18n bundle — call react's `useThunderID()` directly. Without this bridge they'd only see\n // react's context default (applicationId/baseUrl undefined, isInitialized false), so the meta\n // fetch would never fire and flow labels would render as untranslated i18n keys. Fields with no\n // nextjs equivalent (token/http helpers meant for direct browser API calls) are stubbed out,\n // since nextjs routes those operations through server actions instead.\n const unsupported = (name: string): (() => Promise<never>) => {\n return () =>\n Promise.reject(\n new ThunderIDRuntimeError(\n `\\`${name}\\` is not supported in @thunderid/nextjs.`,\n `ThunderIDClientProvider-${name}-NotSupportedError-001`,\n 'nextjs',\n ),\n );\n };\n\n const reactContextValue: ReactThunderIDContextProps = useMemo(\n () => ({\n afterSignInUrl: undefined,\n applicationId,\n baseUrl,\n clearSession,\n clientId: undefined,\n discovery: {wellKnown: null},\n exchangeToken: unsupported('exchangeToken'),\n getAccessToken: unsupported('getAccessToken'),\n getDecodedIdToken: unsupported('getDecodedIdToken'),\n getIdToken: unsupported('getIdToken'),\n getStorageManager: () => Promise.resolve(null),\n http: {\n request: unsupported('http.request'),\n requestAll: unsupported('http.requestAll'),\n },\n instanceId: 0,\n isInitialized: !isLoading,\n isLoading,\n isMetaLoading: false,\n isSignedIn,\n meta: initialMeta ?? null,\n organizationHandle,\n reInitialize: unsupported('reInitialize'),\n recover: unsupported('recover'),\n resolveFlowTemplateLiterals: (text: string | undefined): string => text ?? '',\n scopes,\n signIn: handleSignIn,\n signInSilently: unsupported('signInSilently'),\n signInUrl,\n signOut: handleSignOut,\n signUp: handleSignUp,\n signUpUrl,\n user,\n userSchema,\n vendor: getVendorPrefix(vendor),\n }),\n [\n applicationId,\n baseUrl,\n clearSession,\n isLoading,\n isSignedIn,\n organizationHandle,\n scopes,\n signInUrl,\n signUpUrl,\n user,\n userSchema,\n initialMeta,\n vendor,\n ],\n );\n\n return (\n <ThunderIDContext.Provider value={contextValue}>\n <ReactThunderIDContext.Provider value={reactContextValue}>\n <I18nProvider preferences={preferences?.i18n}>\n <FlowMetaProvider\n enabled={preferences?.resolveFromMeta !== false}\n initialMeta={initialMeta}\n fetchMeta={fetchMeta}\n >\n <ThemeProvider theme={preferences?.theme?.overrides} mode={preferences?.theme?.mode}>\n <FlowProvider>\n <UserProvider\n profile={userProfile}\n userSchema={userSchema}\n onUpdateProfile={handleProfileUpdate}\n updateProfile={updateProfile}\n >\n {children}\n </UserProvider>\n </FlowProvider>\n </ThemeProvider>\n </FlowMetaProvider>\n </I18nProvider>\n </ReactThunderIDContext.Provider>\n </ThunderIDContext.Provider>\n );\n};\n\nexport default ThunderIDClientProvider;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ThunderIDProvider.js","names":["ThunderIDClientProvider: FC<PropsWithChildren<ThunderIDClientProviderProps>>","reRenderCheckRef: RefObject<boolean>","router: AppRouterInstance","searchParams: ReadonlyURLSearchParams","code: string | null","state: string | null","result: {error?: string; redirectUrl?: string; success: boolean}","result: any","contextValue: ThunderIDContextProps","reactContextValue: ReactThunderIDContextProps","ThunderIDContext","ReactThunderIDContext","fetchFlowMetaFromServer"],"sources":["../../../../src/client/contexts/ThunderID/ThunderIDProvider.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use client';\n\nimport {\n AttributeSchema,\n EmbeddedFlowExecuteRequestConfig,\n FlowMetadataResponse,\n generateFlattenedUserProfile,\n UpdateMeProfileConfig,\n User,\n UserProfile,\n ThunderIDRuntimeError,\n getVendorPrefix,\n} from '@thunderid/node';\nimport {\n I18nProvider,\n FlowMetaProvider,\n FlowProvider,\n UserProvider,\n ThemeProvider,\n ThunderIDContext as ReactThunderIDContext,\n ThunderIDContextProps as ReactThunderIDContextProps,\n ThunderIDProviderProps,\n} from '@thunderid/react';\nimport {ReadonlyURLSearchParams} from 'next/dist/client/components/navigation.react-server';\nimport {AppRouterInstance} from 'next/dist/shared/lib/app-router-context.shared-runtime';\nimport {useRouter, useSearchParams} from 'next/navigation';\nimport {FC, PropsWithChildren, RefObject, useEffect, useMemo, useRef, useState} from 'react';\nimport ThunderIDContext, {ThunderIDContextProps} from './ThunderIDContext';\nimport {RefreshResult} from '../../../server/actions/refreshToken';\nimport logger from '../../../utils/logger';\nimport fetchFlowMetaFromServer from '../../fetchFlowMetaFromServer';\n\n/**\n * Props interface of {@link ThunderIDClientProvider}\n */\nexport type ThunderIDClientProviderProps = Partial<Omit<ThunderIDProviderProps, 'baseUrl' | 'clientId'>> &\n Pick<ThunderIDProviderProps, 'baseUrl' | 'clientId'> & {\n applicationId: ThunderIDContextProps['applicationId'];\n clearSession: () => Promise<void>;\n handleOAuthCallback: (\n code: string,\n state: string,\n ) => Promise<{error?: string; redirectUrl?: string; success: boolean}>;\n /**\n * Flow metadata fetched server-side ahead of time, seeding `FlowMetaProvider` so it can skip\n * its own initial client-side fetch (avoiding a flash of untranslated i18n keys).\n */\n initialMeta?: FlowMetadataResponse | null;\n isSignedIn: boolean;\n organizationHandle: ThunderIDContextProps['organizationHandle'];\n refreshToken: () => Promise<RefreshResult>;\n signIn: ThunderIDContextProps['signIn'];\n signOut: ThunderIDContextProps['signOut'];\n signUp: ThunderIDContextProps['signUp'];\n updateProfile: (\n requestConfig: UpdateMeProfileConfig,\n sessionId?: string,\n ) => Promise<{data: {user: User}; error: string; success: boolean}>;\n user: User | null;\n userProfile: UserProfile;\n userSchema?: Record<string, AttributeSchema> | null;\n };\n\nconst ThunderIDClientProvider: FC<PropsWithChildren<ThunderIDClientProviderProps>> = ({\n baseUrl,\n children,\n signIn,\n clearSession,\n refreshToken,\n signOut,\n signUp,\n handleOAuthCallback,\n preferences,\n isSignedIn,\n signInUrl,\n signUpUrl,\n user: _user,\n userProfile: _userProfile,\n userSchema: _userSchema = null,\n updateProfile,\n applicationId,\n organizationHandle,\n scopes,\n vendor,\n initialMeta = null,\n}: PropsWithChildren<ThunderIDClientProviderProps>) => {\n const reRenderCheckRef: RefObject<boolean> = useRef(false);\n const router: AppRouterInstance = useRouter();\n const searchParams: ReadonlyURLSearchParams = useSearchParams();\n const [isLoading, setIsLoading] = useState<boolean>(true);\n const [user, setUser] = useState<User | null>(_user);\n const [userProfile, setUserProfile] = useState<UserProfile>(_userProfile);\n const [userSchema, setUserSchema] = useState<Record<string, AttributeSchema> | null>(_userSchema);\n\n useEffect(() => {\n setUserProfile(_userProfile);\n }, [_userProfile]);\n\n useEffect(() => {\n setUserSchema(_userSchema);\n }, [_userSchema]);\n\n useEffect(() => {\n setUser(_user);\n }, [_user]);\n\n // Handle OAuth callback automatically\n useEffect(() => {\n // React 18.x Strict.Mode has a new check for `Ensuring reusable state` to facilitate an upcoming react feature.\n // https://reactjs.org/docs/strict-mode.html#ensuring-reusable-state\n // This will remount all the useEffects to ensure that there are no unexpected side effects.\n // When react remounts the signIn hook of the AuthProvider, it will cause a race condition. Hence, we have to\n // prevent the re-render of this hook as suggested in the following discussion.\n // https://github.com/reactwg/react-18/discussions/18#discussioncomment-795623\n if (reRenderCheckRef.current) {\n return;\n }\n\n reRenderCheckRef.current = true;\n\n // Don't handle callback if already signed in\n if (isSignedIn) return;\n\n (async (): Promise<void> => {\n try {\n const code: string | null = searchParams.get('code');\n const state: string | null = searchParams.get('state');\n const error: string | null = searchParams.get('error');\n\n // Check for OAuth errors first\n if (error) {\n logger.error('[ThunderIDClientProvider] An error was received for the initiated sign-in request.');\n\n return;\n }\n\n // Handle OAuth callback if code and state are present\n if (code && state) {\n setIsLoading(true);\n\n const result: {error?: string; redirectUrl?: string; success: boolean} = await handleOAuthCallback(\n code,\n state,\n );\n\n if (result.success) {\n // Redirect to the success URL\n if (result.redirectUrl) {\n router.push(result.redirectUrl);\n } else {\n // Refresh the page to update authentication state\n window.location.reload();\n }\n } else {\n logger.error(\n `[ThunderIDClientProvider] An error occurred while signing in: ${result.error || 'Authentication failed'}`,\n );\n }\n }\n } catch (error) {\n logger.error('[ThunderIDClientProvider] Failed to handle OAuth callback:', error);\n }\n })();\n }, []);\n\n useEffect(() => {\n // Set loading to false when server has resolved authentication state\n setIsLoading(false);\n }, [isSignedIn, user]);\n\n const handleSignIn = async (payload: any, request: EmbeddedFlowExecuteRequestConfig): Promise<any> => {\n if (!signIn) {\n throw new ThunderIDRuntimeError(\n '`signIn` function is not available.',\n 'ThunderIDClientProvider-handleSignIn-RuntimeError-001',\n 'nextjs',\n );\n }\n\n const result: any = await signIn(payload, request);\n\n // Redirect based flow URL is sent as `signInUrl` in the response.\n // Use window.location.href instead of router.push() — the OAuth authorization\n // endpoint is on an external server, and router.push() would send RSC fetch\n // headers that the identity provider doesn't understand, causing a CORS error.\n if (result?.data?.signInUrl) {\n window.location.href = result.data.signInUrl;\n\n return undefined;\n }\n\n // After the Embedded flow is successful, the URL to navigate next is sent as `afterSignInUrl` in the response.\n if (result?.data?.afterSignInUrl) {\n router.push(result.data.afterSignInUrl);\n\n return undefined;\n }\n\n if (result?.error) {\n throw new Error(result.error);\n }\n\n return result?.data ?? result;\n };\n\n const handleSignUp = async (payload: any, request: EmbeddedFlowExecuteRequestConfig): Promise<any> => {\n if (!signUp) {\n throw new ThunderIDRuntimeError(\n '`signUp` function is not available.',\n 'ThunderIDClientProvider-handleSignUp-RuntimeError-001',\n 'nextjs',\n );\n }\n\n const result: any = await signUp(payload, request);\n\n // Redirect based flow URL is sent as `signUpUrl` in the response.\n if (result?.data?.signUpUrl) {\n router.push(result.data.signUpUrl);\n\n return undefined;\n }\n\n // After the Embedded flow is successful, the URL to navigate next is sent as `afterSignUpUrl` in the response.\n if (result?.data?.afterSignUpUrl) {\n router.push(result.data.afterSignUpUrl);\n\n return undefined;\n }\n\n if (result?.error) {\n throw new Error(result.error);\n }\n\n return result?.data ?? result;\n };\n\n const handleSignOut = async (): Promise<any> => {\n logger.debug('[ThunderIDClientProvider][handleSignOut] `handleSignOut` called.');\n\n try {\n const result: any = await signOut();\n\n logger.debug('[ThunderIDClientProvider][handleSignOut] Sign out result:', result);\n\n if (result?.data?.afterSignOutUrl) {\n router.push(result.data.afterSignOutUrl);\n\n return {location: result.data.afterSignOutUrl, redirected: true};\n }\n\n if (result?.error) {\n logger.error(\n '[ThunderIDClientProvider][handleSignOut] Error result was returned during signing the user out with a button click:',\n result.error,\n );\n }\n\n return result?.data ?? result;\n } catch (error) {\n logger.error(\n '[ThunderIDClientProvider][handleSignOut] Error occurred during signing the user out with a button click:',\n error,\n );\n\n return undefined;\n }\n };\n\n const contextValue: ThunderIDContextProps = useMemo(\n () => ({\n applicationId,\n baseUrl,\n clearSession,\n isLoading,\n isSignedIn,\n organizationHandle,\n preferences,\n refreshToken,\n scopes,\n signIn: handleSignIn,\n signInUrl,\n signOut: handleSignOut,\n signUp: handleSignUp,\n signUpUrl,\n user,\n }),\n [\n baseUrl,\n user,\n isSignedIn,\n isLoading,\n signInUrl,\n signUpUrl,\n applicationId,\n organizationHandle,\n scopes,\n preferences,\n ],\n );\n\n const handleProfileUpdate = (payload: User): void => {\n setUser(payload);\n setUserProfile((prev: UserProfile) => ({\n ...prev,\n flattenedProfile: generateFlattenedUserProfile(payload),\n profile: payload,\n }));\n };\n\n // Bridge into @thunderid/react's own ThunderIDContext. Internal react components rendered by\n // BaseSignIn/BaseSignUp — most notably FlowMetaProvider, which fetches `/flow/meta` and injects\n // its i18n bundle — call react's `useThunderID()` directly. Without this bridge they'd only see\n // react's context default (applicationId/baseUrl undefined, isInitialized false), so the meta\n // fetch would never fire and flow labels would render as untranslated i18n keys. Fields with no\n // nextjs equivalent (token/http helpers meant for direct browser API calls) are stubbed out,\n // since nextjs routes those operations through server actions instead.\n const unsupported = (name: string): (() => Promise<never>) => {\n return () =>\n Promise.reject(\n new ThunderIDRuntimeError(\n `\\`${name}\\` is not supported in @thunderid/nextjs.`,\n `ThunderIDClientProvider-${name}-NotSupportedError-001`,\n 'nextjs',\n ),\n );\n };\n\n const reactContextValue: ReactThunderIDContextProps = useMemo(\n () => ({\n afterSignInUrl: undefined,\n applicationId,\n baseUrl,\n clearSession,\n clientId: undefined,\n discovery: {wellKnown: null},\n exchangeToken: unsupported('exchangeToken'),\n getAccessToken: unsupported('getAccessToken'),\n getDecodedIdToken: unsupported('getDecodedIdToken'),\n getIdToken: unsupported('getIdToken'),\n getStorageManager: () => Promise.resolve(null),\n http: {\n request: unsupported('http.request'),\n requestAll: unsupported('http.requestAll'),\n },\n instanceId: 0,\n isInitialized: !isLoading,\n isLoading,\n isMetaLoading: false,\n isSignedIn,\n meta: initialMeta ?? null,\n organizationHandle,\n reInitialize: unsupported('reInitialize'),\n recover: unsupported('recover'),\n resolveFlowTemplateLiterals: (text: string | undefined): string => text ?? '',\n scopes,\n signIn: handleSignIn,\n signInSilently: unsupported('signInSilently'),\n signInUrl,\n signOut: handleSignOut,\n signUp: handleSignUp,\n signUpUrl,\n user,\n userSchema,\n vendor: getVendorPrefix(vendor),\n }),\n [\n applicationId,\n baseUrl,\n clearSession,\n isLoading,\n isSignedIn,\n organizationHandle,\n scopes,\n signInUrl,\n signUpUrl,\n user,\n userSchema,\n initialMeta,\n vendor,\n ],\n );\n\n return (\n <ThunderIDContext.Provider value={contextValue}>\n <ReactThunderIDContext.Provider value={reactContextValue}>\n <I18nProvider preferences={preferences?.i18n}>\n <FlowMetaProvider\n enabled={preferences?.resolveFromMeta !== false}\n initialMeta={initialMeta}\n fetchMeta={fetchFlowMetaFromServer}\n >\n <ThemeProvider theme={preferences?.theme?.overrides} mode={preferences?.theme?.mode}>\n <FlowProvider>\n <UserProvider\n profile={userProfile}\n userSchema={userSchema}\n onUpdateProfile={handleProfileUpdate}\n updateProfile={updateProfile}\n >\n {children}\n </UserProvider>\n </FlowProvider>\n </ThemeProvider>\n </FlowMetaProvider>\n </I18nProvider>\n </ReactThunderIDContext.Provider>\n </ThunderIDContext.Provider>\n );\n};\n\nexport default ThunderIDClientProvider;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FlowMetadataResponse } from '@thunderid/node';
|
|
2
|
+
/**
|
|
3
|
+
* The `fetchMeta` behind `FlowMetaProvider`: loads flow metadata through {@link getFlowMetaAction} so the browser
|
|
4
|
+
* never calls `baseUrl` directly, and throws the failure the action returned as an SDK error, which
|
|
5
|
+
* `FlowMetaProvider` reports through its `error` state.
|
|
6
|
+
*/
|
|
7
|
+
declare const fetchFlowMetaFromServer: (params: {
|
|
8
|
+
applicationId?: string;
|
|
9
|
+
language?: string;
|
|
10
|
+
}) => Promise<FlowMetadataResponse>;
|
|
11
|
+
export default fetchFlowMetaFromServer;
|
|
12
|
+
//# sourceMappingURL=fetchFlowMetaFromServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetchFlowMetaFromServer.d.ts","sourceRoot":"","sources":["../../src/client/fetchFlowMetaFromServer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,oBAAoB,EAA2C,MAAM,iBAAiB,CAAC;AAG/F;;;;GAIG;AACH,QAAA,MAAM,uBAAuB,GAAU,QAAQ;IAC7C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,KAAG,OAAO,CAAC,oBAAoB,CAY/B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import getFlowMetaAction_default from "../server/actions/getFlowMetaAction.js";
|
|
2
|
+
import { ThunderIDAPIError, ThunderIDRuntimeError } from "@thunderid/node";
|
|
3
|
+
|
|
4
|
+
//#region src/client/fetchFlowMetaFromServer.ts
|
|
5
|
+
/**
|
|
6
|
+
* The `fetchMeta` behind `FlowMetaProvider`: loads flow metadata through {@link getFlowMetaAction} so the browser
|
|
7
|
+
* never calls `baseUrl` directly, and throws the failure the action returned as an SDK error, which
|
|
8
|
+
* `FlowMetaProvider` reports through its `error` state.
|
|
9
|
+
*/
|
|
10
|
+
const fetchFlowMetaFromServer = async (params) => {
|
|
11
|
+
const result = await getFlowMetaAction_default(params);
|
|
12
|
+
if (result.success) return result.meta;
|
|
13
|
+
if (result.statusCode !== void 0) throw new ThunderIDAPIError(result.message, "getFlowMetaAction-Error-001", "nextjs", result.statusCode);
|
|
14
|
+
throw new ThunderIDRuntimeError(result.message, "getFlowMetaAction-Error-001", "nextjs");
|
|
15
|
+
};
|
|
16
|
+
var fetchFlowMetaFromServer_default = fetchFlowMetaFromServer;
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { fetchFlowMetaFromServer_default as default };
|
|
20
|
+
//# sourceMappingURL=fetchFlowMetaFromServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetchFlowMetaFromServer.js","names":["result: GetFlowMetaActionResult","getFlowMetaAction"],"sources":["../../src/client/fetchFlowMetaFromServer.ts"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {FlowMetadataResponse, ThunderIDAPIError, ThunderIDRuntimeError} from '@thunderid/node';\nimport getFlowMetaAction, {GetFlowMetaActionResult} from '../server/actions/getFlowMetaAction';\n\n/**\n * The `fetchMeta` behind `FlowMetaProvider`: loads flow metadata through {@link getFlowMetaAction} so the browser\n * never calls `baseUrl` directly, and throws the failure the action returned as an SDK error, which\n * `FlowMetaProvider` reports through its `error` state.\n */\nconst fetchFlowMetaFromServer = async (params: {\n applicationId?: string;\n language?: string;\n}): Promise<FlowMetadataResponse> => {\n const result: GetFlowMetaActionResult = await getFlowMetaAction(params);\n\n if (result.success) {\n return result.meta;\n }\n\n if (result.statusCode !== undefined) {\n throw new ThunderIDAPIError(result.message, 'getFlowMetaAction-Error-001', 'nextjs', result.statusCode);\n }\n\n throw new ThunderIDRuntimeError(result.message, 'getFlowMetaAction-Error-001', 'nextjs');\n};\n\nexport default fetchFlowMetaFromServer;\n"],"mappings":";;;;;;;;;AAWA,MAAM,0BAA0B,OAAO,WAGF;CACnC,MAAMA,SAAkC,MAAMC,0BAAkB,OAAO;AAEvE,KAAI,OAAO,QACT,QAAO,OAAO;AAGhB,KAAI,OAAO,eAAe,OACxB,OAAM,IAAI,kBAAkB,OAAO,SAAS,+BAA+B,UAAU,OAAO,WAAW;AAGzG,OAAM,IAAI,sBAAsB,OAAO,SAAS,+BAA+B,SAAS;;AAG1F,sCAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThunderIDProvider.d.ts","sourceRoot":"","sources":["../../src/server/ThunderIDProvider.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAAC,sBAAsB,EAAC,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAC,EAAE,EAAE,iBAAiB,EAAe,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ThunderIDProvider.d.ts","sourceRoot":"","sources":["../../src/server/ThunderIDProvider.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAAC,sBAAsB,EAAC,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAC,EAAE,EAAE,iBAAiB,EAAe,MAAM,OAAO,CAAC;AAoB1D;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,OAAO,CAAC,sBAAsB,CAAC,GAAG;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;OAYG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,uBAAuB,EAAE,EAAE,CAAC,iBAAiB,CAAC,4BAA4B,CAAC,CAsJhF,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -16,7 +16,6 @@ import signInAction_default from "./actions/signInAction.js";
|
|
|
16
16
|
import signOutAction_default from "./actions/signOutAction.js";
|
|
17
17
|
import signUpAction_default from "./actions/signUpAction.js";
|
|
18
18
|
import updateUserProfileAction_default from "./actions/updateUserProfileAction.js";
|
|
19
|
-
import getFlowMetaAction_default from "./actions/getFlowMetaAction.js";
|
|
20
19
|
import ThunderIDProvider_default from "../client/contexts/ThunderID/ThunderIDProvider.js";
|
|
21
20
|
import { FlowMetaType, ThunderIDRuntimeError, extractUserClaimsFromIdToken, getFlowMeta, resolveResourceEndpoint } from "@thunderid/node";
|
|
22
21
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
@@ -123,7 +122,6 @@ const ThunderIDServerProvider = async ({ children, afterSignInUrl, afterSignOutU
|
|
|
123
122
|
applicationId: config?.applicationId,
|
|
124
123
|
baseUrl: config?.baseUrl,
|
|
125
124
|
initialMeta: flowMeta,
|
|
126
|
-
fetchMeta: getFlowMetaAction_default,
|
|
127
125
|
signIn: signInAction_default,
|
|
128
126
|
clearSession: clearSession_default,
|
|
129
127
|
refreshToken: refreshToken_default,
|