@thunderid/nextjs 1.0.8 → 1.2.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.
Files changed (81) hide show
  1. package/dist/ThunderIDNextClient.d.ts +16 -0
  2. package/dist/ThunderIDNextClient.d.ts.map +1 -1
  3. package/dist/ThunderIDNextClient.js +28 -2
  4. package/dist/ThunderIDNextClient.js.map +1 -1
  5. package/dist/cjs/ThunderIDNextClient.cjs +27 -1
  6. package/dist/cjs/ThunderIDNextClient.cjs.map +1 -1
  7. package/dist/cjs/client/components/presentation/ChangeCredential/ChangeCredential.cjs +104 -0
  8. package/dist/cjs/client/components/presentation/ChangeCredential/ChangeCredential.cjs.map +1 -0
  9. package/dist/cjs/client/components/presentation/UserDropdown/UserDropdown.cjs +8 -4
  10. package/dist/cjs/client/components/presentation/UserDropdown/UserDropdown.cjs.map +1 -1
  11. package/dist/cjs/client/contexts/ThunderID/ThunderIDProvider.cjs +2 -3
  12. package/dist/cjs/client/contexts/ThunderID/ThunderIDProvider.cjs.map +1 -1
  13. package/dist/cjs/client/index.cjs +1 -0
  14. package/dist/cjs/index.cjs +4 -0
  15. package/dist/cjs/server/actions/handleOAuthCallbackAction.cjs +2 -4
  16. package/dist/cjs/server/actions/handleOAuthCallbackAction.cjs.map +1 -1
  17. package/dist/cjs/server/actions/signInAction.cjs +1 -1
  18. package/dist/cjs/server/actions/signInAction.cjs.map +1 -1
  19. package/dist/cjs/server/actions/updateUserCredentialsAction.cjs +41 -0
  20. package/dist/cjs/server/actions/updateUserCredentialsAction.cjs.map +1 -0
  21. package/dist/cjs/server/backchannelLogout.cjs +92 -0
  22. package/dist/cjs/server/backchannelLogout.cjs.map +1 -0
  23. package/dist/cjs/server/index.cjs +2 -0
  24. package/dist/cjs/server/proxy/thunderIDProxy.cjs +4 -1
  25. package/dist/cjs/server/proxy/thunderIDProxy.cjs.map +1 -1
  26. package/dist/cjs/utils/SessionManager.cjs +17 -1
  27. package/dist/cjs/utils/SessionManager.cjs.map +1 -1
  28. package/dist/cjs/utils/handleRefreshToken.cjs +5 -2
  29. package/dist/cjs/utils/handleRefreshToken.cjs.map +1 -1
  30. package/dist/cjs/utils/loggedOutSessions.cjs +32 -0
  31. package/dist/cjs/utils/loggedOutSessions.cjs.map +1 -0
  32. package/dist/client/components/presentation/ChangeCredential/ChangeCredential.d.ts +45 -0
  33. package/dist/client/components/presentation/ChangeCredential/ChangeCredential.d.ts.map +1 -0
  34. package/dist/client/components/presentation/ChangeCredential/ChangeCredential.js +99 -0
  35. package/dist/client/components/presentation/ChangeCredential/ChangeCredential.js.map +1 -0
  36. package/dist/client/components/presentation/UserDropdown/UserDropdown.d.ts +12 -0
  37. package/dist/client/components/presentation/UserDropdown/UserDropdown.d.ts.map +1 -1
  38. package/dist/client/components/presentation/UserDropdown/UserDropdown.js +8 -4
  39. package/dist/client/components/presentation/UserDropdown/UserDropdown.js.map +1 -1
  40. package/dist/client/contexts/ThunderID/ThunderIDProvider.d.ts +1 -1
  41. package/dist/client/contexts/ThunderID/ThunderIDProvider.d.ts.map +1 -1
  42. package/dist/client/contexts/ThunderID/ThunderIDProvider.js +3 -4
  43. package/dist/client/contexts/ThunderID/ThunderIDProvider.js.map +1 -1
  44. package/dist/client/index.d.ts +2 -0
  45. package/dist/client/index.d.ts.map +1 -1
  46. package/dist/client/index.js +1 -0
  47. package/dist/index.js +3 -1
  48. package/dist/server/actions/handleOAuthCallbackAction.d.ts +1 -2
  49. package/dist/server/actions/handleOAuthCallbackAction.d.ts.map +1 -1
  50. package/dist/server/actions/handleOAuthCallbackAction.js +2 -4
  51. package/dist/server/actions/handleOAuthCallbackAction.js.map +1 -1
  52. package/dist/server/actions/signInAction.d.ts.map +1 -1
  53. package/dist/server/actions/signInAction.js +1 -1
  54. package/dist/server/actions/signInAction.js.map +1 -1
  55. package/dist/server/actions/updateUserCredentialsAction.d.ts +16 -0
  56. package/dist/server/actions/updateUserCredentialsAction.d.ts.map +1 -0
  57. package/dist/server/actions/updateUserCredentialsAction.js +39 -0
  58. package/dist/server/actions/updateUserCredentialsAction.js.map +1 -0
  59. package/dist/server/backchannelLogout.d.ts +40 -0
  60. package/dist/server/backchannelLogout.d.ts.map +1 -0
  61. package/dist/server/backchannelLogout.js +90 -0
  62. package/dist/server/backchannelLogout.js.map +1 -0
  63. package/dist/server/index.d.ts +2 -0
  64. package/dist/server/index.d.ts.map +1 -1
  65. package/dist/server/index.js +2 -1
  66. package/dist/server/proxy/thunderIDProxy.d.ts +12 -1
  67. package/dist/server/proxy/thunderIDProxy.d.ts.map +1 -1
  68. package/dist/server/proxy/thunderIDProxy.js +4 -1
  69. package/dist/server/proxy/thunderIDProxy.js.map +1 -1
  70. package/dist/utils/SessionManager.d.ts +13 -1
  71. package/dist/utils/SessionManager.d.ts.map +1 -1
  72. package/dist/utils/SessionManager.js +17 -1
  73. package/dist/utils/SessionManager.js.map +1 -1
  74. package/dist/utils/handleRefreshToken.d.ts.map +1 -1
  75. package/dist/utils/handleRefreshToken.js +5 -2
  76. package/dist/utils/handleRefreshToken.js.map +1 -1
  77. package/dist/utils/loggedOutSessions.d.ts +12 -0
  78. package/dist/utils/loggedOutSessions.d.ts.map +1 -0
  79. package/dist/utils/loggedOutSessions.js +29 -0
  80. package/dist/utils/loggedOutSessions.js.map +1 -0
  81. package/package.json +4 -4
@@ -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","sessionState: 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 getActiveTheme,\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 sessionState?: 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 sessionState: string | null = searchParams.get('session_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 sessionState || undefined,\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={getActiveTheme(preferences?.theme?.mode as any)}>\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"],"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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"}
@@ -21,4 +21,6 @@ export { default as UserAvatar } from './components/presentation/UserAvatar/User
21
21
  export type { UserAvatarProps } from './components/presentation/UserAvatar/UserAvatar';
22
22
  export { default as UserProfile } from './components/presentation/UserProfile/UserProfile';
23
23
  export type { UserProfileProps } from './components/presentation/UserProfile/UserProfile';
24
+ export { default as ChangeCredential } from './components/presentation/ChangeCredential/ChangeCredential';
25
+ export type { ChangeCredentialProps } from './components/presentation/ChangeCredential/ChangeCredential';
24
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,mCAAmC,CAAC;AAE1E,OAAO,EAAC,OAAO,IAAI,QAAQ,EAAC,MAAM,wCAAwC,CAAC;AAC3E,YAAY,EAAC,aAAa,EAAC,MAAM,wCAAwC,CAAC;AAE1E,OAAO,EAAC,OAAO,IAAI,SAAS,EAAC,MAAM,0CAA0C,CAAC;AAC9E,YAAY,EAAC,cAAc,EAAC,MAAM,0CAA0C,CAAC;AAE7E,OAAO,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,gDAAgD,CAAC;AACvF,YAAY,EAAC,iBAAiB,EAAC,MAAM,gDAAgD,CAAC;AAEtF,OAAO,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,gDAAgD,CAAC;AACvF,YAAY,EAAC,iBAAiB,EAAC,MAAM,gDAAgD,CAAC;AAEtF,OAAO,EAAC,OAAO,IAAI,MAAM,EAAC,MAAM,yCAAyC,CAAC;AAC1E,YAAY,EAAC,WAAW,EAAC,MAAM,yCAAyC,CAAC;AAEzE,OAAO,EAAC,OAAO,IAAI,aAAa,EAAC,MAAM,kDAAkD,CAAC;AAC1F,YAAY,EAAC,kBAAkB,EAAC,MAAM,kDAAkD,CAAC;AAEzF,OAAO,EAAC,OAAO,IAAI,IAAI,EAAC,MAAM,qCAAqC,CAAC;AACpE,YAAY,EAAC,SAAS,EAAC,MAAM,qCAAqC,CAAC;AAEnE,OAAO,EAAC,OAAO,IAAI,MAAM,EAAC,MAAM,yCAAyC,CAAC;AAC1E,YAAY,EAAC,WAAW,EAAC,MAAM,yCAAyC,CAAC;AAEzE,OAAO,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,qDAAqD,CAAC;AAC5F,YAAY,EAAC,iBAAiB,EAAC,MAAM,qDAAqD,CAAC;AAE3F,OAAO,EAAC,OAAO,IAAI,UAAU,EAAC,MAAM,iDAAiD,CAAC;AACtF,YAAY,EAAC,eAAe,EAAC,MAAM,iDAAiD,CAAC;AAErF,OAAO,EAAC,OAAO,IAAI,WAAW,EAAC,MAAM,mDAAmD,CAAC;AACzF,YAAY,EAAC,gBAAgB,EAAC,MAAM,mDAAmD,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,mCAAmC,CAAC;AAE1E,OAAO,EAAC,OAAO,IAAI,QAAQ,EAAC,MAAM,wCAAwC,CAAC;AAC3E,YAAY,EAAC,aAAa,EAAC,MAAM,wCAAwC,CAAC;AAE1E,OAAO,EAAC,OAAO,IAAI,SAAS,EAAC,MAAM,0CAA0C,CAAC;AAC9E,YAAY,EAAC,cAAc,EAAC,MAAM,0CAA0C,CAAC;AAE7E,OAAO,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,gDAAgD,CAAC;AACvF,YAAY,EAAC,iBAAiB,EAAC,MAAM,gDAAgD,CAAC;AAEtF,OAAO,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,gDAAgD,CAAC;AACvF,YAAY,EAAC,iBAAiB,EAAC,MAAM,gDAAgD,CAAC;AAEtF,OAAO,EAAC,OAAO,IAAI,MAAM,EAAC,MAAM,yCAAyC,CAAC;AAC1E,YAAY,EAAC,WAAW,EAAC,MAAM,yCAAyC,CAAC;AAEzE,OAAO,EAAC,OAAO,IAAI,aAAa,EAAC,MAAM,kDAAkD,CAAC;AAC1F,YAAY,EAAC,kBAAkB,EAAC,MAAM,kDAAkD,CAAC;AAEzF,OAAO,EAAC,OAAO,IAAI,IAAI,EAAC,MAAM,qCAAqC,CAAC;AACpE,YAAY,EAAC,SAAS,EAAC,MAAM,qCAAqC,CAAC;AAEnE,OAAO,EAAC,OAAO,IAAI,MAAM,EAAC,MAAM,yCAAyC,CAAC;AAC1E,YAAY,EAAC,WAAW,EAAC,MAAM,yCAAyC,CAAC;AAEzE,OAAO,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,qDAAqD,CAAC;AAC5F,YAAY,EAAC,iBAAiB,EAAC,MAAM,qDAAqD,CAAC;AAE3F,OAAO,EAAC,OAAO,IAAI,UAAU,EAAC,MAAM,iDAAiD,CAAC;AACtF,YAAY,EAAC,eAAe,EAAC,MAAM,iDAAiD,CAAC;AAErF,OAAO,EAAC,OAAO,IAAI,WAAW,EAAC,MAAM,mDAAmD,CAAC;AACzF,YAAY,EAAC,gBAAgB,EAAC,MAAM,mDAAmD,CAAC;AAExF,OAAO,EAAC,OAAO,IAAI,gBAAgB,EAAC,MAAM,6DAA6D,CAAC;AACxG,YAAY,EAAC,qBAAqB,EAAC,MAAM,6DAA6D,CAAC"}
@@ -10,5 +10,6 @@ import SignUp_default from "./components/presentation/SignUp/SignUp.js";
10
10
  import UserProfile_default from "./components/presentation/UserProfile/UserProfile.js";
11
11
  import UserDropdown_default from "./components/presentation/UserDropdown/UserDropdown.js";
12
12
  import UserAvatar_default from "./components/presentation/UserAvatar/UserAvatar.js";
13
+ import ChangeCredential_default from "./components/presentation/ChangeCredential/ChangeCredential.js";
13
14
 
14
15
  export { };
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import thunderid_default from "./server/thunderid.js";
3
3
  import ThunderIDProvider_default from "./server/ThunderIDProvider.js";
4
4
  import thunderIDProxy_default, { removeChunkedCookieFromHeader, replaceChunkedCookieInHeader } from "./server/proxy/thunderIDProxy.js";
5
5
  import createRouteMatcher_default from "./server/proxy/createRouteMatcher.js";
6
+ import backchannelLogout_default from "./server/backchannelLogout.js";
6
7
  import "./server/index.js";
7
8
  import useThunderID_default from "./client/contexts/ThunderID/useThunderID.js";
8
9
  import SignedIn_default from "./client/components/control/SignedIn/SignedIn.js";
@@ -16,6 +17,7 @@ import SignUp_default from "./client/components/presentation/SignUp/SignUp.js";
16
17
  import UserProfile_default from "./client/components/presentation/UserProfile/UserProfile.js";
17
18
  import UserDropdown_default from "./client/components/presentation/UserDropdown/UserDropdown.js";
18
19
  import UserAvatar_default from "./client/components/presentation/UserAvatar/UserAvatar.js";
20
+ import ChangeCredential_default from "./client/components/presentation/ChangeCredential/ChangeCredential.js";
19
21
  import "./client/index.js";
20
22
 
21
- export { SignIn_default as SignIn, SignInButton_default as SignInButton, SignOutButton_default as SignOutButton, SignUp_default as SignUp, SignUpButton_default as SignUpButton, SignedIn_default as SignedIn, SignedOut_default as SignedOut, ThunderIDNextClient_default as ThunderIDNext, ThunderIDProvider_default as ThunderIDProvider, User_default as User, UserAvatar_default as UserAvatar, UserDropdown_default as UserDropdown, UserProfile_default as UserProfile, createRouteMatcher_default as createRouteMatcher, removeChunkedCookieFromHeader, replaceChunkedCookieInHeader, thunderIDProxy_default as thunderIDProxy, thunderid_default as thunderid, useThunderID_default as useThunderID };
23
+ export { ChangeCredential_default as ChangeCredential, SignIn_default as SignIn, SignInButton_default as SignInButton, SignOutButton_default as SignOutButton, SignUp_default as SignUp, SignUpButton_default as SignUpButton, SignedIn_default as SignedIn, SignedOut_default as SignedOut, ThunderIDNextClient_default as ThunderIDNext, ThunderIDProvider_default as ThunderIDProvider, User_default as User, UserAvatar_default as UserAvatar, UserDropdown_default as UserDropdown, UserProfile_default as UserProfile, backchannelLogout_default as backchannelLogout, createRouteMatcher_default as createRouteMatcher, removeChunkedCookieFromHeader, replaceChunkedCookieInHeader, thunderIDProxy_default as thunderIDProxy, thunderid_default as thunderid, useThunderID_default as useThunderID };
@@ -5,10 +5,9 @@
5
5
  *
6
6
  * @param code - Authorization code from OAuth provider
7
7
  * @param state - State parameter from OAuth provider for CSRF protection
8
- * @param sessionState - Session state parameter from OAuth provider
9
8
  * @returns Promise that resolves with success status and optional error message
10
9
  */
11
- declare const handleOAuthCallbackAction: (code: string, state: string, sessionState?: string) => Promise<{
10
+ declare const handleOAuthCallbackAction: (code: string, state: string) => Promise<{
12
11
  error?: string;
13
12
  redirectUrl?: string;
14
13
  success: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"handleOAuthCallbackAction.d.ts","sourceRoot":"","sources":["../../../src/server/actions/handleOAuthCallbackAction.ts"],"names":[],"mappings":"AAeA;;;;;;;;;GASG;AACH,QAAA,MAAM,yBAAyB,GAC7B,MAAM,MAAM,EACZ,OAAO,MAAM,EACb,eAAe,MAAM,KACpB,OAAO,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB,CA0HA,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
1
+ {"version":3,"file":"handleOAuthCallbackAction.d.ts","sourceRoot":"","sources":["../../../src/server/actions/handleOAuthCallbackAction.ts"],"names":[],"mappings":"AAeA;;;;;;;;GAQG;AACH,QAAA,MAAM,yBAAyB,GAC7B,MAAM,MAAM,EACZ,OAAO,MAAM,KACZ,OAAO,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB,CA0HA,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
@@ -15,10 +15,9 @@ import { cookies } from "next/headers";
15
15
  *
16
16
  * @param code - Authorization code from OAuth provider
17
17
  * @param state - State parameter from OAuth provider for CSRF protection
18
- * @param sessionState - Session state parameter from OAuth provider
19
18
  * @returns Promise that resolves with success status and optional error message
20
19
  */
21
- const handleOAuthCallbackAction = async (code, state, sessionState) => {
20
+ const handleOAuthCallbackAction = async (code, state) => {
22
21
  try {
23
22
  if (!code || !state) return {
24
23
  error: "Missing required OAuth parameters: code and state are required",
@@ -46,7 +45,6 @@ const handleOAuthCallbackAction = async (code, state, sessionState) => {
46
45
  }
47
46
  const signInResult = await thunderIDClient.signIn({
48
47
  code,
49
- session_state: sessionState,
50
48
  state
51
49
  }, {}, sessionId);
52
50
  const config = await thunderIDClient.getConfiguration();
@@ -59,7 +57,7 @@ const handleOAuthCallbackAction = async (code, state, sessionState) => {
59
57
  const organizationId = idToken["user_org"] || idToken["organization_id"];
60
58
  const expiresIn = signInResult["expiresIn"];
61
59
  const sessionCookieExpiryTime = SessionManager_default.resolveSessionCookieExpiry(config.sessionCookie?.expiryTime);
62
- const sessionToken = await SessionManager_default.createSessionToken(accessToken, userIdFromToken, sessionId, scopes, expiresIn, refreshToken, organizationId);
60
+ const sessionToken = await SessionManager_default.createSessionToken(accessToken, userIdFromToken, sessionId, scopes, expiresIn, refreshToken, organizationId, { sid: typeof idToken["sid"] === "string" ? idToken["sid"] : void 0 });
63
61
  setChunkedCookie(cookieStore, SessionManager_default.getSessionCookieName(), sessionToken, SessionManager_default.getSessionCookieOptions(sessionCookieExpiryTime));
64
62
  cookieStore.delete(SessionManager_default.getTempSessionCookieName());
65
63
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"handleOAuthCallbackAction.js","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 * @param sessionState - Session state parameter from OAuth provider\n * @returns Promise that resolves with success status and optional error message\n */\nconst handleOAuthCallbackAction = async (\n code: string,\n state: string,\n sessionState?: 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 session_state: sessionState,\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 );\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":";;;;;;;;;;;;;;;;;;;;AAyBA,MAAM,4BAA4B,OAChC,MACA,OACA,iBAKI;AACJ,KAAI;AACF,MAAI,CAAC,QAAQ,CAAC,MACZ,QAAO;GACL,OAAO;GACP,SAAS;GACV;EAGH,MAAM,kBAAkBA,mBAAW;AAEnC,MAAI,CAAC,gBAAgB,cACnB,QAAO;GACL,OAAO;GACP,SAAS;GACV;EAGH,MAAMC,cAA8B,MAAM,SAAS;EACnD,IAAIC;EAEJ,MAAMC,mBAAuC,YAAY,IAAIC,uBAAe,0BAA0B,CAAC,EAAE;AAEzG,MAAI,iBACF,KAAI;AAEF,gBADyC,MAAMA,uBAAe,kBAAkB,iBAAiB,EACzE;UAClB;AACN,kBAAO,MACL,wGACD;;AAIL,MAAI,CAAC,WAAW;AACd,kBAAO,MAAM,yFAAyF;AAEtG,UAAO;IACL,OAAO;IACP,SAAS;IACV;;EAIH,MAAMC,eAAwC,MAAM,gBAAgB,OAClE;GACE;GACA,eAAe;GACf;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,uBAAe,2BACrD,OAAO,eAAe,WACvB;GAED,MAAMW,eAAuB,MAAMX,uBAAe,mBAChD,aACA,iBACA,WACA,QACA,WACA,cACA,eACD;AAED,oBACE,aACAA,uBAAe,sBAAsB,EACrC,cACAA,uBAAe,wBAAwB,wBAAwB,CAChE;AAED,eAAY,OAAOA,uBAAe,0BAA0B,CAAC;WACtD,OAAO;AACd,kBAAO,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"}
1
+ {"version":3,"file":"handleOAuthCallbackAction.js","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,mBAAW;AAEnC,MAAI,CAAC,gBAAgB,cACnB,QAAO;GACL,OAAO;GACP,SAAS;GACV;EAGH,MAAMC,cAA8B,MAAM,SAAS;EACnD,IAAIC;EAEJ,MAAMC,mBAAuC,YAAY,IAAIC,uBAAe,0BAA0B,CAAC,EAAE;AAEzG,MAAI,iBACF,KAAI;AAEF,gBADyC,MAAMA,uBAAe,kBAAkB,iBAAiB,EACzE;UAClB;AACN,kBAAO,MACL,wGACD;;AAIL,MAAI,CAAC,WAAW;AACd,kBAAO,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,uBAAe,2BACrD,OAAO,eAAe,WACvB;GAED,MAAMW,eAAuB,MAAMX,uBAAe,mBAChD,aACA,iBACA,WACA,QACA,WACA,cACA,gBACA,EAAC,KAAK,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS,QAAU,CACvE;AAED,oBACE,aACAA,uBAAe,sBAAsB,EACrC,cACAA,uBAAe,wBAAwB,wBAAwB,CAChE;AAED,eAAY,OAAOA,uBAAe,0BAA0B,CAAC;WACtD,OAAO;AACd,kBAAO,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"signInAction.d.ts","sourceRoot":"","sources":["../../../src/server/actions/signInAction.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,gCAAgC,EAGjC,MAAM,iBAAiB,CAAC;AAUzB;;;;;;;GAOG;AACH,QAAA,MAAM,YAAY,GAChB,UAAU,GAAG,EACb,UAAU,gCAAgC,KACzC,OAAO,CAAC;IACT,IAAI,CAAC,EACD;QACE,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,CAiHA,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"signInAction.d.ts","sourceRoot":"","sources":["../../../src/server/actions/signInAction.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,gCAAgC,EAGjC,MAAM,iBAAiB,CAAC;AAUzB;;;;;;;GAOG;AACH,QAAA,MAAM,YAAY,GAChB,UAAU,GAAG,EACb,UAAU,gCAAgC,KACzC,OAAO,CAAC;IACT,IAAI,CAAC,EACD;QACE,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,CAkHA,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -56,7 +56,7 @@ const signInAction = async (payload, request) => {
56
56
  const expiresIn = Math.max(exp - iat, 0);
57
57
  const config = await client.getConfiguration();
58
58
  const sessionCookieExpiryTime = SessionManager_default.resolveSessionCookieExpiry(config.sessionCookie?.expiryTime);
59
- const sessionToken = await SessionManager_default.createSessionToken(response.assertion, userIdFromToken, sessionId, scopes, expiresIn, "", organizationId);
59
+ const sessionToken = await SessionManager_default.createSessionToken(response.assertion, userIdFromToken, sessionId, scopes, expiresIn, "", organizationId, { sid: typeof idToken["sid"] === "string" ? idToken["sid"] : void 0 });
60
60
  setChunkedCookie(cookieStore, SessionManager_default.getSessionCookieName(), sessionToken, SessionManager_default.getSessionCookieOptions(sessionCookieExpiryTime));
61
61
  cookieStore.delete(SessionManager_default.getTempSessionCookieName());
62
62
  const afterSignInUrl = await (await client.getStorageManager()).getConfigDataParameter("afterSignInUrl");
@@ -1 +1 @@
1
- {"version":3,"file":"signInAction.js","names":["getClient","cookieStore: RequestCookies","sessionId: string | undefined","existingSessionToken: string | undefined","SessionManager","tempSessionToken: string | undefined","tempSessionToken: string","defaultSignInUrl: string","response: any","idToken: IdToken","userIdFromToken: string","organizationId: string | undefined","scopes: string","iat: number","exp: number","expiresIn: number","config: ThunderIDNextConfig","sessionCookieExpiryTime: number","sessionToken: string","afterSignInUrl: string"],"sources":["../../../src/server/actions/signInAction.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use server';\n\nimport {\n generateSessionId,\n EmbeddedSignInFlowStatus,\n EmbeddedFlowExecuteRequestConfig,\n IdToken,\n isEmpty,\n} from '@thunderid/node';\nimport {cookies} from 'next/headers';\nimport {ThunderIDNextConfig} from '../../models/config';\nimport {getChunkedCookie, setChunkedCookie} from '../../utils/chunkedCookie';\nimport logger from '../../utils/logger';\nimport SessionManager, {SessionTokenPayload} from '../../utils/SessionManager';\nimport getClient from '../getClient';\n\ntype RequestCookies = Awaited<ReturnType<typeof cookies>>;\n\n/**\n * Server action for signing in a user.\n * Handles the embedded sign-in flow and manages session cookies.\n *\n * @param payload - The embedded sign-in flow payload\n * @param request - The embedded flow execute request config\n * @returns Promise that resolves when sign-in is complete\n */\nconst signInAction = async (\n payload?: any,\n request?: EmbeddedFlowExecuteRequestConfig,\n): Promise<{\n data?:\n | {\n afterSignInUrl?: string;\n signInUrl?: string;\n }\n | Record<string, unknown>;\n error?: string;\n success: boolean;\n}> => {\n try {\n const client = getClient();\n const cookieStore: RequestCookies = await cookies();\n\n let sessionId: string | undefined;\n\n const existingSessionToken: string | undefined = getChunkedCookie(\n cookieStore,\n SessionManager.getSessionCookieName(),\n );\n\n if (existingSessionToken) {\n try {\n const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(existingSessionToken);\n sessionId = sessionPayload.sessionId;\n } catch {\n // Invalid session token, will create new temp session\n }\n }\n\n if (!sessionId) {\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 // Invalid temp session, will create new one\n }\n }\n }\n\n if (!sessionId) {\n sessionId = generateSessionId();\n\n const tempSessionToken: string = await SessionManager.createTempSession(sessionId);\n\n cookieStore.set(\n SessionManager.getTempSessionCookieName(),\n tempSessionToken,\n SessionManager.getTempSessionCookieOptions(),\n );\n }\n\n // If no payload provided, redirect to sign-in URL for redirect-based sign-in.\n if (!payload || isEmpty(payload)) {\n const defaultSignInUrl: string = await client.getAuthorizeRequestUrl({}, sessionId);\n return {data: {signInUrl: String(defaultSignInUrl)}, success: true};\n }\n\n // Handle embedded sign-in flow\n const response: any = await client.signIn(payload, request!, sessionId);\n\n if (response.flowStatus === EmbeddedSignInFlowStatus.Complete) {\n // On the V2 platform, a completed embedded flow (without a preceding `/authorize` redirect,\n // i.e. no `authId`) returns a self-contained JWT `assertion` rather than an authorization\n // code to exchange. This mirrors how @thunderid/react and @thunderid/vue treat it: the\n // assertion is used directly as the bearer session token.\n if (!response.assertion) {\n throw new Error('[signInAction] Flow completed without an assertion.');\n }\n\n const idToken: IdToken = await client.getDecodedIdToken(sessionId, response.assertion);\n const userIdFromToken: string = (idToken.sub as string | undefined) ?? sessionId;\n const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as\n | string\n | undefined;\n const scopes: string = (idToken['scope'] as string | undefined) ?? '';\n const iat: number = (idToken['iat'] as number | undefined) ?? Math.floor(Date.now() / 1000);\n const exp: number = (idToken['exp'] as number | undefined) ?? iat + 3600;\n const expiresIn: number = Math.max(exp - iat, 0);\n\n const config: ThunderIDNextConfig = await client.getConfiguration();\n const sessionCookieExpiryTime: number = SessionManager.resolveSessionCookieExpiry(\n config.sessionCookie?.expiryTime,\n );\n\n const sessionToken: string = await SessionManager.createSessionToken(\n response.assertion,\n userIdFromToken,\n sessionId,\n scopes,\n expiresIn,\n '',\n organizationId,\n );\n\n setChunkedCookie(\n cookieStore,\n SessionManager.getSessionCookieName(),\n sessionToken,\n SessionManager.getSessionCookieOptions(sessionCookieExpiryTime),\n );\n\n cookieStore.delete(SessionManager.getTempSessionCookieName());\n\n const afterSignInUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl');\n return {data: {afterSignInUrl: String(afterSignInUrl)}, success: true};\n }\n\n return {data: response as Record<string, unknown>, success: true};\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : typeof (error as any)?.message === 'string'\n ? (error as any).message\n : String(error);\n logger.error(`[signInAction] Error during sign-in: ${message}`);\n return {error: message, success: false};\n }\n};\n\nexport default signInAction;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6BA,MAAM,eAAe,OACnB,SACA,YAUI;AACJ,KAAI;EACF,MAAM,SAASA,mBAAW;EAC1B,MAAMC,cAA8B,MAAM,SAAS;EAEnD,IAAIC;EAEJ,MAAMC,uBAA2C,iBAC/C,aACAC,uBAAe,sBAAsB,CACtC;AAED,MAAI,qBACF,KAAI;AAEF,gBAD4C,MAAMA,uBAAe,mBAAmB,qBAAqB,EAC9E;UACrB;AAKV,MAAI,CAAC,WAAW;GACd,MAAMC,mBAAuC,YAAY,IAAID,uBAAe,0BAA0B,CAAC,EAAE;AAEzG,OAAI,iBACF,KAAI;AAEF,iBADyC,MAAMA,uBAAe,kBAAkB,iBAAiB,EACzE;WAClB;;AAMZ,MAAI,CAAC,WAAW;AACd,eAAY,mBAAmB;GAE/B,MAAME,mBAA2B,MAAMF,uBAAe,kBAAkB,UAAU;AAElF,eAAY,IACVA,uBAAe,0BAA0B,EACzC,kBACAA,uBAAe,6BAA6B,CAC7C;;AAIH,MAAI,CAAC,WAAW,QAAQ,QAAQ,EAAE;GAChC,MAAMG,mBAA2B,MAAM,OAAO,uBAAuB,EAAE,EAAE,UAAU;AACnF,UAAO;IAAC,MAAM,EAAC,WAAW,OAAO,iBAAiB,EAAC;IAAE,SAAS;IAAK;;EAIrE,MAAMC,WAAgB,MAAM,OAAO,OAAO,SAAS,SAAU,UAAU;AAEvE,MAAI,SAAS,eAAe,yBAAyB,UAAU;AAK7D,OAAI,CAAC,SAAS,UACZ,OAAM,IAAI,MAAM,sDAAsD;GAGxE,MAAMC,UAAmB,MAAM,OAAO,kBAAkB,WAAW,SAAS,UAAU;GACtF,MAAMC,kBAA2B,QAAQ,OAA8B;GACvE,MAAMC,iBAAsC,QAAQ,eAAe,QAAQ;GAG3E,MAAMC,SAAkB,QAAQ,YAAmC;GACnE,MAAMC,MAAe,QAAQ,UAAiC,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;GAC3F,MAAMC,MAAe,QAAQ,UAAiC,MAAM;GACpE,MAAMC,YAAoB,KAAK,IAAI,MAAM,KAAK,EAAE;GAEhD,MAAMC,SAA8B,MAAM,OAAO,kBAAkB;GACnE,MAAMC,0BAAkCb,uBAAe,2BACrD,OAAO,eAAe,WACvB;GAED,MAAMc,eAAuB,MAAMd,uBAAe,mBAChD,SAAS,WACT,iBACA,WACA,QACA,WACA,IACA,eACD;AAED,oBACE,aACAA,uBAAe,sBAAsB,EACrC,cACAA,uBAAe,wBAAwB,wBAAwB,CAChE;AAED,eAAY,OAAOA,uBAAe,0BAA0B,CAAC;GAE7D,MAAMe,iBAAyB,OAAO,MAAM,OAAO,mBAAmB,EAAE,uBAAuB,iBAAiB;AAChH,UAAO;IAAC,MAAM,EAAC,gBAAgB,OAAO,eAAe,EAAC;IAAE,SAAS;IAAK;;AAGxE,SAAO;GAAC,MAAM;GAAqC,SAAS;GAAK;UAC1D,OAAO;EACd,MAAM,UACJ,iBAAiB,QACb,MAAM,UACN,OAAQ,OAAe,YAAY,WAChC,MAAc,UACf,OAAO,MAAM;AACrB,iBAAO,MAAM,wCAAwC,UAAU;AAC/D,SAAO;GAAC,OAAO;GAAS,SAAS;GAAM;;;AAI3C,2BAAe"}
1
+ {"version":3,"file":"signInAction.js","names":["getClient","cookieStore: RequestCookies","sessionId: string | undefined","existingSessionToken: string | undefined","SessionManager","tempSessionToken: string | undefined","tempSessionToken: string","defaultSignInUrl: string","response: any","idToken: IdToken","userIdFromToken: string","organizationId: string | undefined","scopes: string","iat: number","exp: number","expiresIn: number","config: ThunderIDNextConfig","sessionCookieExpiryTime: number","sessionToken: string","afterSignInUrl: string"],"sources":["../../../src/server/actions/signInAction.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use server';\n\nimport {\n generateSessionId,\n EmbeddedSignInFlowStatus,\n EmbeddedFlowExecuteRequestConfig,\n IdToken,\n isEmpty,\n} from '@thunderid/node';\nimport {cookies} from 'next/headers';\nimport {ThunderIDNextConfig} from '../../models/config';\nimport {getChunkedCookie, setChunkedCookie} from '../../utils/chunkedCookie';\nimport logger from '../../utils/logger';\nimport SessionManager, {SessionTokenPayload} from '../../utils/SessionManager';\nimport getClient from '../getClient';\n\ntype RequestCookies = Awaited<ReturnType<typeof cookies>>;\n\n/**\n * Server action for signing in a user.\n * Handles the embedded sign-in flow and manages session cookies.\n *\n * @param payload - The embedded sign-in flow payload\n * @param request - The embedded flow execute request config\n * @returns Promise that resolves when sign-in is complete\n */\nconst signInAction = async (\n payload?: any,\n request?: EmbeddedFlowExecuteRequestConfig,\n): Promise<{\n data?:\n | {\n afterSignInUrl?: string;\n signInUrl?: string;\n }\n | Record<string, unknown>;\n error?: string;\n success: boolean;\n}> => {\n try {\n const client = getClient();\n const cookieStore: RequestCookies = await cookies();\n\n let sessionId: string | undefined;\n\n const existingSessionToken: string | undefined = getChunkedCookie(\n cookieStore,\n SessionManager.getSessionCookieName(),\n );\n\n if (existingSessionToken) {\n try {\n const sessionPayload: SessionTokenPayload = await SessionManager.verifySessionToken(existingSessionToken);\n sessionId = sessionPayload.sessionId;\n } catch {\n // Invalid session token, will create new temp session\n }\n }\n\n if (!sessionId) {\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 // Invalid temp session, will create new one\n }\n }\n }\n\n if (!sessionId) {\n sessionId = generateSessionId();\n\n const tempSessionToken: string = await SessionManager.createTempSession(sessionId);\n\n cookieStore.set(\n SessionManager.getTempSessionCookieName(),\n tempSessionToken,\n SessionManager.getTempSessionCookieOptions(),\n );\n }\n\n // If no payload provided, redirect to sign-in URL for redirect-based sign-in.\n if (!payload || isEmpty(payload)) {\n const defaultSignInUrl: string = await client.getAuthorizeRequestUrl({}, sessionId);\n return {data: {signInUrl: String(defaultSignInUrl)}, success: true};\n }\n\n // Handle embedded sign-in flow\n const response: any = await client.signIn(payload, request!, sessionId);\n\n if (response.flowStatus === EmbeddedSignInFlowStatus.Complete) {\n // On the V2 platform, a completed embedded flow (without a preceding `/authorize` redirect,\n // i.e. no `authId`) returns a self-contained JWT `assertion` rather than an authorization\n // code to exchange. This mirrors how @thunderid/react and @thunderid/vue treat it: the\n // assertion is used directly as the bearer session token.\n if (!response.assertion) {\n throw new Error('[signInAction] Flow completed without an assertion.');\n }\n\n const idToken: IdToken = await client.getDecodedIdToken(sessionId, response.assertion);\n const userIdFromToken: string = (idToken.sub as string | undefined) ?? sessionId;\n const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as\n | string\n | undefined;\n const scopes: string = (idToken['scope'] as string | undefined) ?? '';\n const iat: number = (idToken['iat'] as number | undefined) ?? Math.floor(Date.now() / 1000);\n const exp: number = (idToken['exp'] as number | undefined) ?? iat + 3600;\n const expiresIn: number = Math.max(exp - iat, 0);\n\n const config: ThunderIDNextConfig = await client.getConfiguration();\n const sessionCookieExpiryTime: number = SessionManager.resolveSessionCookieExpiry(\n config.sessionCookie?.expiryTime,\n );\n\n const sessionToken: string = await SessionManager.createSessionToken(\n response.assertion,\n userIdFromToken,\n sessionId,\n scopes,\n expiresIn,\n '',\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\n const afterSignInUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl');\n return {data: {afterSignInUrl: String(afterSignInUrl)}, success: true};\n }\n\n return {data: response as Record<string, unknown>, success: true};\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : typeof (error as any)?.message === 'string'\n ? (error as any).message\n : String(error);\n logger.error(`[signInAction] Error during sign-in: ${message}`);\n return {error: message, success: false};\n }\n};\n\nexport default signInAction;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6BA,MAAM,eAAe,OACnB,SACA,YAUI;AACJ,KAAI;EACF,MAAM,SAASA,mBAAW;EAC1B,MAAMC,cAA8B,MAAM,SAAS;EAEnD,IAAIC;EAEJ,MAAMC,uBAA2C,iBAC/C,aACAC,uBAAe,sBAAsB,CACtC;AAED,MAAI,qBACF,KAAI;AAEF,gBAD4C,MAAMA,uBAAe,mBAAmB,qBAAqB,EAC9E;UACrB;AAKV,MAAI,CAAC,WAAW;GACd,MAAMC,mBAAuC,YAAY,IAAID,uBAAe,0BAA0B,CAAC,EAAE;AAEzG,OAAI,iBACF,KAAI;AAEF,iBADyC,MAAMA,uBAAe,kBAAkB,iBAAiB,EACzE;WAClB;;AAMZ,MAAI,CAAC,WAAW;AACd,eAAY,mBAAmB;GAE/B,MAAME,mBAA2B,MAAMF,uBAAe,kBAAkB,UAAU;AAElF,eAAY,IACVA,uBAAe,0BAA0B,EACzC,kBACAA,uBAAe,6BAA6B,CAC7C;;AAIH,MAAI,CAAC,WAAW,QAAQ,QAAQ,EAAE;GAChC,MAAMG,mBAA2B,MAAM,OAAO,uBAAuB,EAAE,EAAE,UAAU;AACnF,UAAO;IAAC,MAAM,EAAC,WAAW,OAAO,iBAAiB,EAAC;IAAE,SAAS;IAAK;;EAIrE,MAAMC,WAAgB,MAAM,OAAO,OAAO,SAAS,SAAU,UAAU;AAEvE,MAAI,SAAS,eAAe,yBAAyB,UAAU;AAK7D,OAAI,CAAC,SAAS,UACZ,OAAM,IAAI,MAAM,sDAAsD;GAGxE,MAAMC,UAAmB,MAAM,OAAO,kBAAkB,WAAW,SAAS,UAAU;GACtF,MAAMC,kBAA2B,QAAQ,OAA8B;GACvE,MAAMC,iBAAsC,QAAQ,eAAe,QAAQ;GAG3E,MAAMC,SAAkB,QAAQ,YAAmC;GACnE,MAAMC,MAAe,QAAQ,UAAiC,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;GAC3F,MAAMC,MAAe,QAAQ,UAAiC,MAAM;GACpE,MAAMC,YAAoB,KAAK,IAAI,MAAM,KAAK,EAAE;GAEhD,MAAMC,SAA8B,MAAM,OAAO,kBAAkB;GACnE,MAAMC,0BAAkCb,uBAAe,2BACrD,OAAO,eAAe,WACvB;GAED,MAAMc,eAAuB,MAAMd,uBAAe,mBAChD,SAAS,WACT,iBACA,WACA,QACA,WACA,IACA,gBACA,EAAC,KAAK,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS,QAAU,CACvE;AAED,oBACE,aACAA,uBAAe,sBAAsB,EACrC,cACAA,uBAAe,wBAAwB,wBAAwB,CAChE;AAED,eAAY,OAAOA,uBAAe,0BAA0B,CAAC;GAE7D,MAAMe,iBAAyB,OAAO,MAAM,OAAO,mBAAmB,EAAE,uBAAuB,iBAAiB;AAChH,UAAO;IAAC,MAAM,EAAC,gBAAgB,OAAO,eAAe,EAAC;IAAE,SAAS;IAAK;;AAGxE,SAAO;GAAC,MAAM;GAAqC,SAAS;GAAK;UAC1D,OAAO;EACd,MAAM,UACJ,iBAAiB,QACb,MAAM,UACN,OAAQ,OAAe,YAAY,WAChC,MAAc,UACf,OAAO,MAAM;AACrB,iBAAO,MAAM,wCAAwC,UAAU;AAC/D,SAAO;GAAC,OAAO;GAAS,SAAS;GAAM;;;AAI3C,2BAAe"}
@@ -0,0 +1,16 @@
1
+ import { CredentialUpdateErrorResult } from '@thunderid/node';
2
+ export interface UpdateUserCredentialsActionResult extends CredentialUpdateErrorResult {
3
+ success: boolean;
4
+ }
5
+ /**
6
+ * Server action to update one of the signed-in user's own credentials (e.g. `password`).
7
+ *
8
+ * Never throws across the server action boundary — a thrown class instance loses its
9
+ * prototype chain crossing it, so `error instanceof ThunderIDAPIError` would no longer hold on
10
+ * the client. Instead, the real error is mapped to a field/message here, server-side, while it
11
+ * is still the genuine `ThunderIDAPIError` the core `updateMeCredentials` call threw, and only
12
+ * the plain, serializable result crosses back.
13
+ */
14
+ declare const updateUserCredentialsAction: (payload: Record<string, string>, sessionId?: string) => Promise<UpdateUserCredentialsActionResult>;
15
+ export default updateUserCredentialsAction;
16
+ //# sourceMappingURL=updateUserCredentialsAction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updateUserCredentialsAction.d.ts","sourceRoot":"","sources":["../../../src/server/actions/updateUserCredentialsAction.ts"],"names":[],"mappings":"AAKA,OAAO,EAAC,2BAA2B,EAA2B,MAAM,iBAAiB,CAAC;AAGtF,MAAM,WAAW,iCAAkC,SAAQ,2BAA2B;IACpF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,QAAA,MAAM,2BAA2B,GAC/B,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,YAAY,MAAM,KACjB,OAAO,CAAC,iCAAiC,CAS3C,CAAC;AAEF,eAAe,2BAA2B,CAAC"}
@@ -0,0 +1,39 @@
1
+ 'use server';
2
+
3
+
4
+ import getClient_default from "../getClient.js";
5
+ import { mapCredentialUpdateError } from "@thunderid/node";
6
+
7
+ //#region src/server/actions/updateUserCredentialsAction.ts
8
+ /**
9
+ * Server action to update one of the signed-in user's own credentials (e.g. `password`).
10
+ *
11
+ * Never throws across the server action boundary — a thrown class instance loses its
12
+ * prototype chain crossing it, so `error instanceof ThunderIDAPIError` would no longer hold on
13
+ * the client. Instead, the real error is mapped to a field/message here, server-side, while it
14
+ * is still the genuine `ThunderIDAPIError` the core `updateMeCredentials` call threw, and only
15
+ * the plain, serializable result crosses back.
16
+ */
17
+ const updateUserCredentialsAction = async (payload, sessionId) => {
18
+ try {
19
+ await getClient_default().updateUserCredentials(payload, sessionId);
20
+ return {
21
+ field: null,
22
+ messageKey: "",
23
+ success: true
24
+ };
25
+ } catch (error) {
26
+ const { field, message, messageKey } = mapCredentialUpdateError(error);
27
+ return {
28
+ field,
29
+ message,
30
+ messageKey,
31
+ success: false
32
+ };
33
+ }
34
+ };
35
+ var updateUserCredentialsAction_default = updateUserCredentialsAction;
36
+
37
+ //#endregion
38
+ export { updateUserCredentialsAction_default as default };
39
+ //# sourceMappingURL=updateUserCredentialsAction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updateUserCredentialsAction.js","names":["getClient"],"sources":["../../../src/server/actions/updateUserCredentialsAction.ts"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use server';\n\nimport {CredentialUpdateErrorResult, mapCredentialUpdateError} from '@thunderid/node';\nimport getClient from '../getClient';\n\nexport interface UpdateUserCredentialsActionResult extends CredentialUpdateErrorResult {\n success: boolean;\n}\n\n/**\n * Server action to update one of the signed-in user's own credentials (e.g. `password`).\n *\n * Never throws across the server action boundary — a thrown class instance loses its\n * prototype chain crossing it, so `error instanceof ThunderIDAPIError` would no longer hold on\n * the client. Instead, the real error is mapped to a field/message here, server-side, while it\n * is still the genuine `ThunderIDAPIError` the core `updateMeCredentials` call threw, and only\n * the plain, serializable result crosses back.\n */\nconst updateUserCredentialsAction = async (\n payload: Record<string, string>,\n sessionId?: string,\n): Promise<UpdateUserCredentialsActionResult> => {\n try {\n const client = getClient();\n await client.updateUserCredentials(payload, sessionId);\n return {field: null, messageKey: '', success: true};\n } catch (error) {\n const {field, message, messageKey}: CredentialUpdateErrorResult = mapCredentialUpdateError(error);\n return {field, message, messageKey, success: false};\n }\n};\n\nexport default updateUserCredentialsAction;\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,MAAM,8BAA8B,OAClC,SACA,cAC+C;AAC/C,KAAI;AAEF,QADeA,mBAAW,CACb,sBAAsB,SAAS,UAAU;AACtD,SAAO;GAAC,OAAO;GAAM,YAAY;GAAI,SAAS;GAAK;UAC5C,OAAO;EACd,MAAM,EAAC,OAAO,SAAS,eAA2C,yBAAyB,MAAM;AACjG,SAAO;GAAC;GAAO;GAAS;GAAY,SAAS;GAAM;;;AAIvD,0CAAe"}
@@ -0,0 +1,40 @@
1
+ import { BackchannelLogoutResult, LogoutRecordStore } from '@thunderid/node';
2
+ export interface BackchannelLogoutOptions {
3
+ /**
4
+ * Called after a logout was recorded, so the application can clear what it holds for the ended
5
+ * sessions. Best effort: a failure is logged and the handler still answers 200, since the logout
6
+ * is already recorded and a retry would find nothing new.
7
+ */
8
+ onLogout?: (result: BackchannelLogoutResult) => void | Promise<void>;
9
+ /**
10
+ * Where ended sessions are recorded. Give the same store to `thunderIDProxy()`. Needed when more
11
+ * than one instance of the application runs, or when the proxy runs on the Edge runtime, which
12
+ * shares no memory with this handler. Defaults to the memory of this process.
13
+ */
14
+ store?: LogoutRecordStore;
15
+ }
16
+ /**
17
+ * Creates the route handler for OpenID Connect back-channel logout. ThunderID posts a
18
+ * `logout_token` to it when a session this application took part in ends. The session lives in a
19
+ * cookie the server cannot delete, so the handler records the logout, and every later request
20
+ * that presents a matching session cookie is treated as signed out.
21
+ *
22
+ * Mount it at the path registered as the application's back-channel logout URI:
23
+ *
24
+ * ```ts
25
+ * // app/api/auth/backchannel-logout/route.ts
26
+ * import {backchannelLogout} from '@thunderid/nextjs/server';
27
+ *
28
+ * export const {POST} = backchannelLogout();
29
+ * ```
30
+ *
31
+ * It answers `200` for a valid token, including one that names no session held here, `400` for a
32
+ * request or token that is not valid, and `500` when the logout could not be recorded, which
33
+ * makes the server try again. It needs no session cookie: the token's signature is the
34
+ * authentication.
35
+ */
36
+ declare const backchannelLogout: (options?: BackchannelLogoutOptions) => {
37
+ POST: (request: Request) => Promise<Response>;
38
+ };
39
+ export default backchannelLogout;
40
+ //# sourceMappingURL=backchannelLogout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backchannelLogout.d.ts","sourceRoot":"","sources":["../../src/server/backchannelLogout.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,uBAAuB,EAEvB,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAWzB,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE;;;;OAIG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAoCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,QAAA,MAAM,iBAAiB,GAAI,UAAS,wBAA6B,KAAG;IAAC,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAyDjH,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,90 @@
1
+ import { getLoggedOutSessions, setLogoutRecordStore } from "../utils/loggedOutSessions.js";
2
+ import SessionManager_default from "../utils/SessionManager.js";
3
+ import getClient_default from "./getClient.js";
4
+ import logger_default from "../utils/logger.js";
5
+ import { BackchannelLogoutConstants, InvalidLogoutTokenError } from "@thunderid/node";
6
+
7
+ //#region src/server/backchannelLogout.ts
8
+ const BODY_LIMIT_BYTES = 16 * 1024;
9
+ const respond = (status) => new Response(null, {
10
+ headers: { "Cache-Control": "no-store" },
11
+ status
12
+ });
13
+ /**
14
+ * Reads the request body as text, giving up once it passes the limit.
15
+ */
16
+ const readBody = async (request) => {
17
+ if (Number(request.headers.get("content-length") ?? 0) > BODY_LIMIT_BYTES || !request.body) return;
18
+ const reader = request.body.getReader();
19
+ const decoder = new TextDecoder();
20
+ let body = "";
21
+ let size = 0;
22
+ for (;;) {
23
+ const { done, value } = await reader.read();
24
+ if (done) return body + decoder.decode();
25
+ size += value.byteLength;
26
+ if (size > BODY_LIMIT_BYTES) {
27
+ await reader.cancel();
28
+ return;
29
+ }
30
+ body += decoder.decode(value, { stream: true });
31
+ }
32
+ };
33
+ /**
34
+ * Creates the route handler for OpenID Connect back-channel logout. ThunderID posts a
35
+ * `logout_token` to it when a session this application took part in ends. The session lives in a
36
+ * cookie the server cannot delete, so the handler records the logout, and every later request
37
+ * that presents a matching session cookie is treated as signed out.
38
+ *
39
+ * Mount it at the path registered as the application's back-channel logout URI:
40
+ *
41
+ * ```ts
42
+ * // app/api/auth/backchannel-logout/route.ts
43
+ * import {backchannelLogout} from '@thunderid/nextjs/server';
44
+ *
45
+ * export const {POST} = backchannelLogout();
46
+ * ```
47
+ *
48
+ * It answers `200` for a valid token, including one that names no session held here, `400` for a
49
+ * request or token that is not valid, and `500` when the logout could not be recorded, which
50
+ * makes the server try again. It needs no session cookie: the token's signature is the
51
+ * authentication.
52
+ */
53
+ const backchannelLogout = (options = {}) => {
54
+ setLogoutRecordStore(options.store);
55
+ const POST = async (request) => {
56
+ if (!(request.headers.get("content-type")?.toLowerCase() ?? "").startsWith("application/x-www-form-urlencoded")) return respond(400);
57
+ const body = await readBody(request);
58
+ const logoutToken = body === void 0 ? null : new URLSearchParams(body).get(BackchannelLogoutConstants.TOKEN_PARAM);
59
+ if (!logoutToken) return respond(400);
60
+ try {
61
+ const client = getClient_default();
62
+ if (!client.isInitialized) await client.initialize({});
63
+ const result = await client.handleBackchannelLogout(logoutToken);
64
+ const config = await client.getConfiguration();
65
+ await getLoggedOutSessions().record({
66
+ ...result,
67
+ clockTolerance: config?.tokenValidation?.idToken?.clockTolerance
68
+ }, SessionManager_default.resolveSessionCookieExpiry(config?.sessionCookie?.expiryTime));
69
+ try {
70
+ await options.onLogout?.(result);
71
+ } catch {
72
+ logger_default.error("[backchannelLogout] The onLogout hook failed.");
73
+ }
74
+ return respond(200);
75
+ } catch (error) {
76
+ if (error instanceof InvalidLogoutTokenError) {
77
+ logger_default.warn(`[backchannelLogout] Rejected a logout token (${error.code}).`);
78
+ return respond(400);
79
+ }
80
+ logger_default.error("[backchannelLogout] Could not complete a back-channel logout.");
81
+ return respond(500);
82
+ }
83
+ };
84
+ return { POST };
85
+ };
86
+ var backchannelLogout_default = backchannelLogout;
87
+
88
+ //#endregion
89
+ export { backchannelLogout_default as default };
90
+ //# sourceMappingURL=backchannelLogout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backchannelLogout.js","names":["reader: ReadableStreamDefaultReader<Uint8Array>","decoder: TextDecoder","body: string | undefined","logoutToken: string | null","client: ThunderIDNextClient","getClient","result: BackchannelLogoutResult","config: ThunderIDNextConfig","SessionManager","error: unknown"],"sources":["../../src/server/backchannelLogout.ts"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n BackchannelLogoutConstants,\n BackchannelLogoutResult,\n InvalidLogoutTokenError,\n LogoutRecordStore,\n} from '@thunderid/node';\nimport getClient from './getClient';\nimport {ThunderIDNextConfig} from '../models/config';\nimport ThunderIDNextClient from '../ThunderIDNextClient';\nimport {getLoggedOutSessions, setLogoutRecordStore} from '../utils/loggedOutSessions';\nimport logger from '../utils/logger';\nimport SessionManager from '../utils/SessionManager';\n\n// A logout token is a kilobyte or two. The endpoint is unauthenticated, so nothing larger is read.\nconst BODY_LIMIT_BYTES = 16 * 1024;\n\nexport interface BackchannelLogoutOptions {\n /**\n * Called after a logout was recorded, so the application can clear what it holds for the ended\n * sessions. Best effort: a failure is logged and the handler still answers 200, since the logout\n * is already recorded and a retry would find nothing new.\n */\n onLogout?: (result: BackchannelLogoutResult) => void | Promise<void>;\n /**\n * Where ended sessions are recorded. Give the same store to `thunderIDProxy()`. Needed when more\n * than one instance of the application runs, or when the proxy runs on the Edge runtime, which\n * shares no memory with this handler. Defaults to the memory of this process.\n */\n store?: LogoutRecordStore;\n}\n\nconst respond = (status: number): Response => new Response(null, {headers: {'Cache-Control': 'no-store'}, status});\n\n/**\n * Reads the request body as text, giving up once it passes the limit.\n */\nconst readBody = async (request: Request): Promise<string | undefined> => {\n if (Number(request.headers.get('content-length') ?? 0) > BODY_LIMIT_BYTES || !request.body) {\n return undefined;\n }\n\n const reader: ReadableStreamDefaultReader<Uint8Array> = request.body.getReader();\n const decoder: TextDecoder = new TextDecoder();\n let body = '';\n let size = 0;\n\n for (;;) {\n const {done, value} = await reader.read();\n\n if (done) {\n return body + decoder.decode();\n }\n\n size += value.byteLength;\n\n if (size > BODY_LIMIT_BYTES) {\n await reader.cancel();\n\n return undefined;\n }\n\n body += decoder.decode(value, {stream: true});\n }\n};\n\n/**\n * Creates the route handler for OpenID Connect back-channel logout. ThunderID posts a\n * `logout_token` to it when a session this application took part in ends. The session lives in a\n * cookie the server cannot delete, so the handler records the logout, and every later request\n * that presents a matching session cookie is treated as signed out.\n *\n * Mount it at the path registered as the application's back-channel logout URI:\n *\n * ```ts\n * // app/api/auth/backchannel-logout/route.ts\n * import {backchannelLogout} from '@thunderid/nextjs/server';\n *\n * export const {POST} = backchannelLogout();\n * ```\n *\n * It answers `200` for a valid token, including one that names no session held here, `400` for a\n * request or token that is not valid, and `500` when the logout could not be recorded, which\n * makes the server try again. It needs no session cookie: the token's signature is the\n * authentication.\n */\nconst backchannelLogout = (options: BackchannelLogoutOptions = {}): {POST: (request: Request) => Promise<Response>} => {\n setLogoutRecordStore(options.store);\n\n const POST = async (request: Request): Promise<Response> => {\n const contentType: string = request.headers.get('content-type')?.toLowerCase() ?? '';\n\n if (!contentType.startsWith('application/x-www-form-urlencoded')) {\n return respond(400);\n }\n\n const body: string | undefined = await readBody(request);\n const logoutToken: string | null =\n body === undefined ? null : new URLSearchParams(body).get(BackchannelLogoutConstants.TOKEN_PARAM);\n\n if (!logoutToken) {\n return respond(400);\n }\n\n try {\n const client: ThunderIDNextClient = getClient();\n\n // A logout request can be the first thing a server process handles.\n if (!client.isInitialized) {\n await client.initialize({} as ThunderIDNextConfig);\n }\n\n const result: BackchannelLogoutResult = await client.handleBackchannelLogout(logoutToken);\n // Typed as synchronous, but it resolves from storage.\n const config: ThunderIDNextConfig = await (client.getConfiguration() as unknown as Promise<ThunderIDNextConfig>);\n\n // A session cookie cannot outlive this, so neither does the record that ends it.\n await getLoggedOutSessions().record(\n {...result, clockTolerance: config?.tokenValidation?.idToken?.clockTolerance},\n SessionManager.resolveSessionCookieExpiry(config?.sessionCookie?.expiryTime),\n );\n try {\n await options.onLogout?.(result);\n } catch {\n logger.error('[backchannelLogout] The onLogout hook failed.');\n }\n\n return respond(200);\n } catch (error: unknown) {\n if (error instanceof InvalidLogoutTokenError) {\n // The code says which check failed. The token itself is never logged.\n logger.warn(`[backchannelLogout] Rejected a logout token (${error.code}).`);\n\n return respond(400);\n }\n\n logger.error('[backchannelLogout] Could not complete a back-channel logout.');\n\n return respond(500);\n }\n };\n\n return {POST};\n};\n\nexport default backchannelLogout;\n"],"mappings":";;;;;;;AAiBA,MAAM,mBAAmB,KAAK;AAiB9B,MAAM,WAAW,WAA6B,IAAI,SAAS,MAAM;CAAC,SAAS,EAAC,iBAAiB,YAAW;CAAE;CAAO,CAAC;;;;AAKlH,MAAM,WAAW,OAAO,YAAkD;AACxE,KAAI,OAAO,QAAQ,QAAQ,IAAI,iBAAiB,IAAI,EAAE,GAAG,oBAAoB,CAAC,QAAQ,KACpF;CAGF,MAAMA,SAAkD,QAAQ,KAAK,WAAW;CAChF,MAAMC,UAAuB,IAAI,aAAa;CAC9C,IAAI,OAAO;CACX,IAAI,OAAO;AAEX,UAAS;EACP,MAAM,EAAC,MAAM,UAAS,MAAM,OAAO,MAAM;AAEzC,MAAI,KACF,QAAO,OAAO,QAAQ,QAAQ;AAGhC,UAAQ,MAAM;AAEd,MAAI,OAAO,kBAAkB;AAC3B,SAAM,OAAO,QAAQ;AAErB;;AAGF,UAAQ,QAAQ,OAAO,OAAO,EAAC,QAAQ,MAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAwBjD,MAAM,qBAAqB,UAAoC,EAAE,KAAsD;AACrH,sBAAqB,QAAQ,MAAM;CAEnC,MAAM,OAAO,OAAO,YAAwC;AAG1D,MAAI,EAFwB,QAAQ,QAAQ,IAAI,eAAe,EAAE,aAAa,IAAI,IAEjE,WAAW,oCAAoC,CAC9D,QAAO,QAAQ,IAAI;EAGrB,MAAMC,OAA2B,MAAM,SAAS,QAAQ;EACxD,MAAMC,cACJ,SAAS,SAAY,OAAO,IAAI,gBAAgB,KAAK,CAAC,IAAI,2BAA2B,YAAY;AAEnG,MAAI,CAAC,YACH,QAAO,QAAQ,IAAI;AAGrB,MAAI;GACF,MAAMC,SAA8BC,mBAAW;AAG/C,OAAI,CAAC,OAAO,cACV,OAAM,OAAO,WAAW,EAAE,CAAwB;GAGpD,MAAMC,SAAkC,MAAM,OAAO,wBAAwB,YAAY;GAEzF,MAAMC,SAA8B,MAAO,OAAO,kBAAkB;AAGpE,SAAM,sBAAsB,CAAC,OAC3B;IAAC,GAAG;IAAQ,gBAAgB,QAAQ,iBAAiB,SAAS;IAAe,EAC7EC,uBAAe,2BAA2B,QAAQ,eAAe,WAAW,CAC7E;AACD,OAAI;AACF,UAAM,QAAQ,WAAW,OAAO;WAC1B;AACN,mBAAO,MAAM,gDAAgD;;AAG/D,UAAO,QAAQ,IAAI;WACZC,OAAgB;AACvB,OAAI,iBAAiB,yBAAyB;AAE5C,mBAAO,KAAK,gDAAgD,MAAM,KAAK,IAAI;AAE3E,WAAO,QAAQ,IAAI;;AAGrB,kBAAO,MAAM,gEAAgE;AAE7E,UAAO,QAAQ,IAAI;;;AAIvB,QAAO,EAAC,MAAK;;AAGf,gCAAe"}
@@ -4,4 +4,6 @@ export * from './ThunderIDProvider.js';
4
4
  export { default as thunderIDProxy } from './proxy/thunderIDProxy';
5
5
  export * from './proxy/thunderIDProxy';
6
6
  export { default as createRouteMatcher } from './proxy/createRouteMatcher';
7
+ export { default as backchannelLogout } from './backchannelLogout';
8
+ export type { BackchannelLogoutOptions } from './backchannelLogout';
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAC,OAAO,IAAI,SAAS,EAAC,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAC,OAAO,IAAI,iBAAiB,EAAC,MAAM,wBAAwB,CAAC;AACpE,cAAc,wBAAwB,CAAC;AAEvC,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,wBAAwB,CAAC;AACjE,cAAc,wBAAwB,CAAC;AAEvC,OAAO,EAAC,OAAO,IAAI,kBAAkB,EAAC,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAC,OAAO,IAAI,SAAS,EAAC,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAC,OAAO,IAAI,iBAAiB,EAAC,MAAM,wBAAwB,CAAC;AACpE,cAAc,wBAAwB,CAAC;AAEvC,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,wBAAwB,CAAC;AACjE,cAAc,wBAAwB,CAAC;AAEvC,OAAO,EAAC,OAAO,IAAI,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAEzE,OAAO,EAAC,OAAO,IAAI,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACjE,YAAY,EAAC,wBAAwB,EAAC,MAAM,qBAAqB,CAAC"}
@@ -2,5 +2,6 @@ import thunderid_default from "./thunderid.js";
2
2
  import ThunderIDProvider_default from "./ThunderIDProvider.js";
3
3
  import thunderIDProxy_default, { removeChunkedCookieFromHeader, replaceChunkedCookieInHeader } from "./proxy/thunderIDProxy.js";
4
4
  import createRouteMatcher_default from "./proxy/createRouteMatcher.js";
5
+ import backchannelLogout_default from "./backchannelLogout.js";
5
6
 
6
- export { ThunderIDProvider_default as ThunderIDProvider, createRouteMatcher_default as createRouteMatcher, removeChunkedCookieFromHeader, replaceChunkedCookieInHeader, thunderIDProxy_default as thunderIDProxy, thunderid_default as thunderid };
7
+ export { ThunderIDProvider_default as ThunderIDProvider, backchannelLogout_default as backchannelLogout, createRouteMatcher_default as createRouteMatcher, removeChunkedCookieFromHeader, replaceChunkedCookieInHeader, thunderIDProxy_default as thunderIDProxy, thunderid_default as thunderid };
@@ -1,7 +1,18 @@
1
+ import { LogoutRecordStore } from '@thunderid/node';
1
2
  import { NextRequest, NextResponse } from 'next/server';
2
3
  import { ThunderIDNextConfig } from '../../models/config';
3
4
  import { SessionTokenPayload } from '../../utils/SessionManager';
4
- export type ThunderIDProxyOptions = Partial<ThunderIDNextConfig>;
5
+ export type ThunderIDProxyOptions = Partial<ThunderIDNextConfig> & {
6
+ /**
7
+ * Where sessions ended by back-channel logout are recorded. Give the same store to
8
+ * `backchannelLogout()`. Needed when more than one instance of the application runs, or when
9
+ * this proxy runs on the Edge runtime, which shares no memory with the route handler. Defaults
10
+ * to the memory of this process.
11
+ */
12
+ backchannelLogout?: {
13
+ store?: LogoutRecordStore;
14
+ };
15
+ };
5
16
  export interface ThunderIDProxyContext {
6
17
  /** Get the session payload from JWT session if available */
7
18
  getSession: () => Promise<SessionTokenPayload | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"thunderIDProxy.d.ts","sourceRoot":"","sources":["../../../src/server/proxy/thunderIDProxy.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,WAAW,EAAE,YAAY,EAAC,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAC,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAUxD,OAAuB,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAC;AAG/E,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAEjE,MAAM,WAAW,qBAAqB;IACpC,4DAA4D;IAC5D,UAAU,EAAE,MAAM,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC;IAC3D,kDAAkD;IAClD,YAAY,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACvC,iEAAiE;IACjE,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B;;;;;;;;OAQG;IACH,YAAY,EAAE,CAAC,YAAY,CAAC,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;CACpF;AAED,KAAK,qBAAqB,GAAG,CAC3B,SAAS,EAAE,qBAAqB,EAChC,GAAG,EAAE,WAAW,KACb,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,YAAY,GAAG,IAAI,CAAC;AAiBxD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GAAI,cAAc,MAAM,EAAE,MAAM,MAAM,KAAG,MAalF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GAAI,cAAc,MAAM,EAAE,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,MAShG,CAAC;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,QAAA,MAAM,cAAc,GAEhB,UAAU,qBAAqB,EAC/B,UAAU,qBAAqB,GAAG,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,qBAAqB,CAAC,KAC9E,CAAC,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAkNlD,CAAC;AAEJ,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"thunderIDProxy.d.ts","sourceRoot":"","sources":["../../../src/server/proxy/thunderIDProxy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,iBAAiB,EAAC,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAC,WAAW,EAAE,YAAY,EAAC,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAC,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAWxD,OAAuB,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAC;AAG/E,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAAG;IACjE;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,iBAAiB,CAAA;KAAC,CAAC;CACjD,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,4DAA4D;IAC5D,UAAU,EAAE,MAAM,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC;IAC3D,kDAAkD;IAClD,YAAY,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACvC,iEAAiE;IACjE,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B;;;;;;;;OAQG;IACH,YAAY,EAAE,CAAC,YAAY,CAAC,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;CACpF;AAED,KAAK,qBAAqB,GAAG,CAC3B,SAAS,EAAE,qBAAqB,EAChC,GAAG,EAAE,WAAW,KACb,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,YAAY,GAAG,IAAI,CAAC;AAiBxD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GAAI,cAAc,MAAM,EAAE,MAAM,MAAM,KAAG,MAalF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GAAI,cAAc,MAAM,EAAE,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,MAShG,CAAC;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,QAAA,MAAM,cAAc,GAEhB,UAAU,qBAAqB,EAC/B,UAAU,qBAAqB,GAAG,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,qBAAqB,CAAC,KAC9E,CAAC,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAoNlD,CAAC;AAEJ,eAAe,cAAc,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { deleteChunkedCookie, getChunkedCookie, setChunkedCookie } from "../../utils/chunkedCookie.js";
2
+ import { setLogoutRecordStore } from "../../utils/loggedOutSessions.js";
2
3
  import { REFRESH_BUFFER_SECONDS } from "../../constants/sessionConstants.js";
3
4
  import SessionManager_default from "../../utils/SessionManager.js";
4
5
  import decorateConfigWithNextEnv_default from "../../utils/decorateConfigWithNextEnv.js";
@@ -103,7 +104,9 @@ const toChunkedCookieWriter = (request, responseCookies) => ({
103
104
  * ```
104
105
  */
105
106
  const thunderIDProxy = (handler, options) => async (request) => {
106
- const resolvedConfig = decorateConfigWithNextEnv_default(typeof options === "function" ? options(request) : options || {});
107
+ const resolvedOptions = typeof options === "function" ? options(request) : options || {};
108
+ setLogoutRecordStore(resolvedOptions.backchannelLogout?.store);
109
+ const resolvedConfig = decorateConfigWithNextEnv_default(resolvedOptions);
107
110
  const url = new URL(request.url);
108
111
  const hasCallbackParams = url.searchParams.has("code") && url.searchParams.has("state");
109
112
  let isValidOAuthCallback = false;