@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.
- package/dist/ThunderIDNextClient.d.ts +16 -0
- package/dist/ThunderIDNextClient.d.ts.map +1 -1
- package/dist/ThunderIDNextClient.js +28 -2
- package/dist/ThunderIDNextClient.js.map +1 -1
- package/dist/cjs/ThunderIDNextClient.cjs +27 -1
- package/dist/cjs/ThunderIDNextClient.cjs.map +1 -1
- package/dist/cjs/client/components/presentation/ChangeCredential/ChangeCredential.cjs +104 -0
- package/dist/cjs/client/components/presentation/ChangeCredential/ChangeCredential.cjs.map +1 -0
- package/dist/cjs/client/components/presentation/UserDropdown/UserDropdown.cjs +8 -4
- package/dist/cjs/client/components/presentation/UserDropdown/UserDropdown.cjs.map +1 -1
- package/dist/cjs/client/contexts/ThunderID/ThunderIDProvider.cjs +2 -3
- package/dist/cjs/client/contexts/ThunderID/ThunderIDProvider.cjs.map +1 -1
- package/dist/cjs/client/index.cjs +1 -0
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/server/actions/handleOAuthCallbackAction.cjs +2 -4
- package/dist/cjs/server/actions/handleOAuthCallbackAction.cjs.map +1 -1
- package/dist/cjs/server/actions/signInAction.cjs +1 -1
- package/dist/cjs/server/actions/signInAction.cjs.map +1 -1
- package/dist/cjs/server/actions/updateUserCredentialsAction.cjs +41 -0
- package/dist/cjs/server/actions/updateUserCredentialsAction.cjs.map +1 -0
- package/dist/cjs/server/backchannelLogout.cjs +92 -0
- package/dist/cjs/server/backchannelLogout.cjs.map +1 -0
- package/dist/cjs/server/index.cjs +2 -0
- package/dist/cjs/server/proxy/thunderIDProxy.cjs +4 -1
- package/dist/cjs/server/proxy/thunderIDProxy.cjs.map +1 -1
- package/dist/cjs/utils/SessionManager.cjs +17 -1
- package/dist/cjs/utils/SessionManager.cjs.map +1 -1
- package/dist/cjs/utils/handleRefreshToken.cjs +5 -2
- package/dist/cjs/utils/handleRefreshToken.cjs.map +1 -1
- package/dist/cjs/utils/loggedOutSessions.cjs +32 -0
- package/dist/cjs/utils/loggedOutSessions.cjs.map +1 -0
- package/dist/client/components/presentation/ChangeCredential/ChangeCredential.d.ts +45 -0
- package/dist/client/components/presentation/ChangeCredential/ChangeCredential.d.ts.map +1 -0
- package/dist/client/components/presentation/ChangeCredential/ChangeCredential.js +99 -0
- package/dist/client/components/presentation/ChangeCredential/ChangeCredential.js.map +1 -0
- package/dist/client/components/presentation/UserDropdown/UserDropdown.d.ts +12 -0
- package/dist/client/components/presentation/UserDropdown/UserDropdown.d.ts.map +1 -1
- package/dist/client/components/presentation/UserDropdown/UserDropdown.js +8 -4
- package/dist/client/components/presentation/UserDropdown/UserDropdown.js.map +1 -1
- package/dist/client/contexts/ThunderID/ThunderIDProvider.d.ts +1 -1
- package/dist/client/contexts/ThunderID/ThunderIDProvider.d.ts.map +1 -1
- package/dist/client/contexts/ThunderID/ThunderIDProvider.js +3 -4
- package/dist/client/contexts/ThunderID/ThunderIDProvider.js.map +1 -1
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/index.js +3 -1
- package/dist/server/actions/handleOAuthCallbackAction.d.ts +1 -2
- package/dist/server/actions/handleOAuthCallbackAction.d.ts.map +1 -1
- package/dist/server/actions/handleOAuthCallbackAction.js +2 -4
- package/dist/server/actions/handleOAuthCallbackAction.js.map +1 -1
- package/dist/server/actions/signInAction.d.ts.map +1 -1
- package/dist/server/actions/signInAction.js +1 -1
- package/dist/server/actions/signInAction.js.map +1 -1
- package/dist/server/actions/updateUserCredentialsAction.d.ts +16 -0
- package/dist/server/actions/updateUserCredentialsAction.d.ts.map +1 -0
- package/dist/server/actions/updateUserCredentialsAction.js +39 -0
- package/dist/server/actions/updateUserCredentialsAction.js.map +1 -0
- package/dist/server/backchannelLogout.d.ts +40 -0
- package/dist/server/backchannelLogout.d.ts.map +1 -0
- package/dist/server/backchannelLogout.js +90 -0
- package/dist/server/backchannelLogout.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -1
- package/dist/server/proxy/thunderIDProxy.d.ts +12 -1
- package/dist/server/proxy/thunderIDProxy.d.ts.map +1 -1
- package/dist/server/proxy/thunderIDProxy.js +4 -1
- package/dist/server/proxy/thunderIDProxy.js.map +1 -1
- package/dist/utils/SessionManager.d.ts +13 -1
- package/dist/utils/SessionManager.d.ts.map +1 -1
- package/dist/utils/SessionManager.js +17 -1
- package/dist/utils/SessionManager.js.map +1 -1
- package/dist/utils/handleRefreshToken.d.ts.map +1 -1
- package/dist/utils/handleRefreshToken.js +5 -2
- package/dist/utils/handleRefreshToken.js.map +1 -1
- package/dist/utils/loggedOutSessions.d.ts +12 -0
- package/dist/utils/loggedOutSessions.d.ts.map +1 -0
- package/dist/utils/loggedOutSessions.js +29 -0
- package/dist/utils/loggedOutSessions.js.map +1 -0
- package/package.json +4 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_loggedOutSessions = require('./loggedOutSessions.cjs');
|
|
2
3
|
const require_sessionConstants = require('../constants/sessionConstants.cjs');
|
|
3
4
|
let __thunderid_node = require("@thunderid/node");
|
|
4
5
|
__thunderid_node = require_rolldown_runtime.__toESM(__thunderid_node);
|
|
@@ -50,7 +51,7 @@ var SessionManager = class {
|
|
|
50
51
|
}
|
|
51
52
|
return require_sessionConstants.DEFAULT_SESSION_COOKIE_EXPIRY_TIME;
|
|
52
53
|
}
|
|
53
|
-
static async createSessionToken(accessToken, userId, sessionId, scopes, accessTokenTtlSeconds, refreshToken, organizationId) {
|
|
54
|
+
static async createSessionToken(accessToken, userId, sessionId, scopes, accessTokenTtlSeconds, refreshToken, organizationId, serverSession) {
|
|
54
55
|
const secret = this.getSecret();
|
|
55
56
|
return await new jose.SignJWT({
|
|
56
57
|
accessToken,
|
|
@@ -58,6 +59,8 @@ var SessionManager = class {
|
|
|
58
59
|
refreshToken,
|
|
59
60
|
scopes,
|
|
60
61
|
sessionId,
|
|
62
|
+
sid: serverSession?.sid,
|
|
63
|
+
signedInAt: serverSession?.signedInAt ?? Math.floor(Date.now() / 1e3),
|
|
61
64
|
type: "session"
|
|
62
65
|
}).setProtectedHeader({ alg: "HS256" }).setSubject(userId).setIssuedAt().setExpirationTime(Math.floor(Date.now() / 1e3) + accessTokenTtlSeconds).sign(secret);
|
|
63
66
|
}
|
|
@@ -68,6 +71,7 @@ var SessionManager = class {
|
|
|
68
71
|
try {
|
|
69
72
|
const { payload } = await (0, jose.jwtVerify)(token, this.getSecret());
|
|
70
73
|
if (payload["type"] !== "session") throw new Error("Invalid token type");
|
|
74
|
+
await this.refuseLoggedOutSession(payload);
|
|
71
75
|
return payload;
|
|
72
76
|
} catch (error) {
|
|
73
77
|
throw new __thunderid_node.ThunderIDRuntimeError(`Invalid session token: ${error instanceof Error ? error.message : "Unknown error"}`, "invalid-session-token", "nextjs", "Session token verification failed");
|
|
@@ -89,6 +93,7 @@ var SessionManager = class {
|
|
|
89
93
|
const { payload: rawPayload } = await (0, jose.compactVerify)(token, this.getSecret());
|
|
90
94
|
const payload = JSON.parse(new TextDecoder().decode(rawPayload));
|
|
91
95
|
if (payload.type !== "session") throw new Error("Invalid token type");
|
|
96
|
+
await this.refuseLoggedOutSession(payload);
|
|
92
97
|
return payload;
|
|
93
98
|
} catch (error) {
|
|
94
99
|
throw new __thunderid_node.ThunderIDRuntimeError(`Invalid session token: ${error instanceof Error ? error.message : "Unknown error"}`, "invalid-session-token-for-refresh", "nextjs", "Session token signature or type check failed during refresh");
|
|
@@ -109,6 +114,17 @@ var SessionManager = class {
|
|
|
109
114
|
/**
|
|
110
115
|
* Get session cookie options
|
|
111
116
|
*/
|
|
117
|
+
/**
|
|
118
|
+
* Throws when a back-channel logout has ended the session this cookie carries. The cookie is
|
|
119
|
+
* still correctly signed, so this is the only thing that stops it from being used.
|
|
120
|
+
*/
|
|
121
|
+
static async refuseLoggedOutSession(payload) {
|
|
122
|
+
if (await require_loggedOutSessions.getLoggedOutSessions().isLoggedOut({
|
|
123
|
+
sid: payload.sid,
|
|
124
|
+
startedAt: payload.signedInAt ?? payload.iat,
|
|
125
|
+
sub: payload.sub
|
|
126
|
+
})) throw new Error("The session was ended by a back-channel logout");
|
|
127
|
+
}
|
|
112
128
|
static getSessionCookieOptions(maxAge) {
|
|
113
129
|
return {
|
|
114
130
|
httpOnly: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionManager.cjs","names":["secret: string | undefined","ThunderIDRuntimeError","secret: Uint8Array","SignJWT","envValue: string | undefined","parsed: number","DEFAULT_SESSION_COOKIE_EXPIRY_TIME","payload: SessionTokenPayload","CookieConfig"],"sources":["../../../src/utils/SessionManager.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {ThunderIDRuntimeError, CookieConfig} from '@thunderid/node';\nimport {SignJWT, jwtVerify, compactVerify, JWTPayload} from 'jose';\nimport {DEFAULT_SESSION_COOKIE_EXPIRY_TIME} from '../constants/sessionConstants';\n\n/**\n * Session token payload interface\n */\nexport interface SessionTokenPayload extends JWTPayload {\n /** Expiration timestamp — doubles as the access token expiry (JWT exp == access token exp) */\n exp: number;\n /** Issued at timestamp */\n iat: number;\n /** Organization ID if applicable */\n organizationId?: string;\n /** The refresh token; empty string if not provided by the auth server */\n refreshToken: string;\n /** OAuth scopes */\n scopes: string[];\n /** Session ID */\n sessionId: string;\n /** User ID */\n sub: string;\n /** Token type discriminant — must be 'session' for access-session JWTs */\n type: 'session';\n}\n\n/**\n * Session management utility class for JWT-based session cookies\n */\nclass SessionManager {\n /**\n * Get the signing secret from environment variable\n * Throws error in production if not set\n */\n private static getSecret(): Uint8Array {\n const secret: string | undefined = process.env['THUNDERID_SECRET'];\n\n if (!secret) {\n if (process.env['NODE_ENV'] === 'production') {\n throw new ThunderIDRuntimeError(\n 'THUNDERID_SECRET environment variable is required in production',\n 'session-secret-required',\n 'nextjs',\n 'Set the THUNDERID_SECRET environment variable with a secure random string',\n );\n }\n // Use a default secret for development (not secure)\n // eslint-disable-next-line no-console\n console.warn('Using default secret for development. Set THUNDERID_SECRET for production!');\n return new TextEncoder().encode('development-secret-not-for-production');\n }\n\n return new TextEncoder().encode(secret);\n }\n\n /**\n * Create a temporary session cookie for login initiation\n */\n static async createTempSession(sessionId: string): Promise<string> {\n const secret: Uint8Array = this.getSecret();\n\n const jwt: string = await new SignJWT({\n sessionId,\n type: 'temp',\n })\n .setProtectedHeader({alg: 'HS256'})\n .setIssuedAt()\n .setExpirationTime('15m')\n .sign(secret);\n\n return jwt;\n }\n\n /**\n * Resolve the session cookie expiry time in seconds.\n *\n * Resolution order (first defined value wins):\n * 1. `configuredExpiry` — value from `ThunderIDNodeConfig.sessionCookie?.expiryTime`\n * 2. `THUNDERID_SESSION_COOKIE_EXPIRY_TIME` environment variable\n * 3. `DEFAULT_SESSION_COOKIE_EXPIRY_TIME` (24 hours)\n */\n static resolveSessionCookieExpiry(configuredExpiry?: number): number {\n if (configuredExpiry != null && configuredExpiry > 0) {\n return configuredExpiry;\n }\n\n const envValue: string | undefined = process.env['THUNDERID_SESSION_COOKIE_EXPIRY_TIME'];\n\n if (envValue) {\n const parsed: number = parseInt(envValue, 10);\n\n if (!Number.isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n\n return DEFAULT_SESSION_COOKIE_EXPIRY_TIME;\n }\n\n static async createSessionToken(\n accessToken: string,\n userId: string,\n sessionId: string,\n scopes: string,\n accessTokenTtlSeconds: number,\n refreshToken: string,\n organizationId?: string,\n ): Promise<string> {\n const secret: Uint8Array = this.getSecret();\n\n const jwt: string = await new SignJWT({\n accessToken,\n organizationId,\n refreshToken,\n scopes,\n sessionId,\n type: 'session',\n } as Omit<SessionTokenPayload, 'sub' | 'iat' | 'exp'>)\n .setProtectedHeader({alg: 'HS256'})\n .setSubject(userId)\n .setIssuedAt()\n .setExpirationTime(Math.floor(Date.now() / 1000) + accessTokenTtlSeconds)\n .sign(secret);\n\n return jwt;\n }\n\n /**\n * Verify and decode a session token\n */\n static async verifySessionToken(token: string): Promise<SessionTokenPayload> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload} = await jwtVerify(token, secret);\n\n if (payload['type'] !== 'session') {\n throw new Error('Invalid token type');\n }\n\n return payload as SessionTokenPayload;\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-session-token',\n 'nextjs',\n 'Session token verification failed',\n );\n }\n }\n\n /**\n * Verify a session token for refresh. Validates the HMAC signature and the\n * `type === 'session'` discriminant but intentionally skips the `exp` check\n * so an expired access token can still be exchanged for a new one.\n *\n * Session lifetime is still bounded — the cookie's `maxAge` is set from\n * `sessionCookieExpiryTime`, so the browser drops an over-age session regardless\n * of the access-token exp embedded in the JWT.\n *\n * Never use the returned payload for authorization.\n */\n static async verifySessionTokenForRefresh(token: string): Promise<SessionTokenPayload> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload: rawPayload} = await compactVerify(token, secret);\n const payload: SessionTokenPayload = JSON.parse(new TextDecoder().decode(rawPayload)) as SessionTokenPayload;\n\n if (payload.type !== 'session') {\n throw new Error('Invalid token type');\n }\n\n return payload;\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-session-token-for-refresh',\n 'nextjs',\n 'Session token signature or type check failed during refresh',\n );\n }\n }\n\n /**\n * Verify and decode a temporary session token\n */\n static async verifyTempSession(token: string): Promise<{sessionId: string}> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload} = await jwtVerify(token, secret);\n\n if (payload['type'] !== 'temp') {\n throw new Error('Invalid token type');\n }\n\n return {sessionId: payload['sessionId'] as string};\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid temporary session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-temp-session-token',\n 'nextjs',\n 'Temporary session token verification failed',\n );\n }\n }\n\n /**\n * Get session cookie options\n */\n static getSessionCookieOptions(maxAge: number): {\n httpOnly: boolean;\n maxAge: number;\n path: string;\n sameSite: 'lax';\n secure: boolean;\n } {\n return {\n httpOnly: true,\n maxAge,\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env['NODE_ENV'] === 'production',\n };\n }\n\n /**\n * Get temporary session cookie options\n */\n static getTempSessionCookieOptions(): {\n httpOnly: boolean;\n maxAge: number;\n path: string;\n sameSite: 'lax';\n secure: boolean;\n } {\n return {\n httpOnly: true,\n maxAge: 15 * 60,\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env['NODE_ENV'] === 'production',\n };\n }\n\n /**\n * Get session cookie name\n */\n static getSessionCookieName(): string {\n return CookieConfig.getSessionCookieName();\n }\n\n /**\n * Get temporary session cookie name\n */\n static getTempSessionCookieName(): string {\n return CookieConfig.getTempSessionCookieName();\n }\n}\n\nexport default SessionManager;\n"],"mappings":";;;;;;;;;;;AAgCA,IAAM,iBAAN,MAAqB;;;;;CAKnB,OAAe,YAAwB;EACrC,MAAMA,SAA6B,QAAQ,IAAI;AAE/C,MAAI,CAAC,QAAQ;AACX,OAAI,QAAQ,IAAI,gBAAgB,aAC9B,OAAM,IAAIC,uCACR,mEACA,2BACA,UACA,4EACD;AAIH,WAAQ,KAAK,6EAA6E;AAC1F,UAAO,IAAI,aAAa,CAAC,OAAO,wCAAwC;;AAG1E,SAAO,IAAI,aAAa,CAAC,OAAO,OAAO;;;;;CAMzC,aAAa,kBAAkB,WAAoC;EACjE,MAAMC,SAAqB,KAAK,WAAW;AAW3C,SAToB,MAAM,IAAIC,aAAQ;GACpC;GACA,MAAM;GACP,CAAC,CACC,mBAAmB,EAAC,KAAK,SAAQ,CAAC,CAClC,aAAa,CACb,kBAAkB,MAAM,CACxB,KAAK,OAAO;;;;;;;;;;CAajB,OAAO,2BAA2B,kBAAmC;AACnE,MAAI,oBAAoB,QAAQ,mBAAmB,EACjD,QAAO;EAGT,MAAMC,WAA+B,QAAQ,IAAI;AAEjD,MAAI,UAAU;GACZ,MAAMC,SAAiB,SAAS,UAAU,GAAG;AAE7C,OAAI,CAAC,OAAO,MAAM,OAAO,IAAI,SAAS,EACpC,QAAO;;AAIX,SAAOC;;CAGT,aAAa,mBACX,aACA,QACA,WACA,QACA,uBACA,cACA,gBACiB;EACjB,MAAMJ,SAAqB,KAAK,WAAW;AAgB3C,SAdoB,MAAM,IAAIC,aAAQ;GACpC;GACA;GACA;GACA;GACA;GACA,MAAM;GACP,CAAqD,CACnD,mBAAmB,EAAC,KAAK,SAAQ,CAAC,CAClC,WAAW,OAAO,CAClB,aAAa,CACb,kBAAkB,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,GAAG,sBAAsB,CACxE,KAAK,OAAO;;;;;CAQjB,aAAa,mBAAmB,OAA6C;AAC3E,MAAI;GAEF,MAAM,EAAC,YAAW,0BAAgB,OADP,KAAK,WAAW,CACK;AAEhD,OAAI,QAAQ,YAAY,UACtB,OAAM,IAAI,MAAM,qBAAqB;AAGvC,UAAO;WACA,OAAO;AACd,SAAM,IAAIF,uCACR,0BAA0B,iBAAiB,QAAQ,MAAM,UAAU,mBACnE,yBACA,UACA,oCACD;;;;;;;;;;;;;;CAeL,aAAa,6BAA6B,OAA6C;AACrF,MAAI;GAEF,MAAM,EAAC,SAAS,eAAc,8BAAoB,OADvB,KAAK,WAAW,CACqB;GAChE,MAAMM,UAA+B,KAAK,MAAM,IAAI,aAAa,CAAC,OAAO,WAAW,CAAC;AAErF,OAAI,QAAQ,SAAS,UACnB,OAAM,IAAI,MAAM,qBAAqB;AAGvC,UAAO;WACA,OAAO;AACd,SAAM,IAAIN,uCACR,0BAA0B,iBAAiB,QAAQ,MAAM,UAAU,mBACnE,qCACA,UACA,8DACD;;;;;;CAOL,aAAa,kBAAkB,OAA6C;AAC1E,MAAI;GAEF,MAAM,EAAC,YAAW,0BAAgB,OADP,KAAK,WAAW,CACK;AAEhD,OAAI,QAAQ,YAAY,OACtB,OAAM,IAAI,MAAM,qBAAqB;AAGvC,UAAO,EAAC,WAAW,QAAQ,cAAuB;WAC3C,OAAO;AACd,SAAM,IAAIA,uCACR,oCAAoC,iBAAiB,QAAQ,MAAM,UAAU,mBAC7E,8BACA,UACA,8CACD;;;;;;CAOL,OAAO,wBAAwB,QAM7B;AACA,SAAO;GACL,UAAU;GACV;GACA,MAAM;GACN,UAAU;GACV,QAAQ,QAAQ,IAAI,gBAAgB;GACrC;;;;;CAMH,OAAO,8BAML;AACA,SAAO;GACL,UAAU;GACV,QAAQ;GACR,MAAM;GACN,UAAU;GACV,QAAQ,QAAQ,IAAI,gBAAgB;GACrC;;;;;CAMH,OAAO,uBAA+B;AACpC,SAAOO,8BAAa,sBAAsB;;;;;CAM5C,OAAO,2BAAmC;AACxC,SAAOA,8BAAa,0BAA0B;;;AAIlD,6BAAe"}
|
|
1
|
+
{"version":3,"file":"SessionManager.cjs","names":["secret: string | undefined","ThunderIDRuntimeError","secret: Uint8Array","SignJWT","envValue: string | undefined","parsed: number","DEFAULT_SESSION_COOKIE_EXPIRY_TIME","payload: SessionTokenPayload","getLoggedOutSessions","CookieConfig"],"sources":["../../../src/utils/SessionManager.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {ThunderIDRuntimeError, CookieConfig} from '@thunderid/node';\nimport {SignJWT, jwtVerify, compactVerify, JWTPayload} from 'jose';\nimport {getLoggedOutSessions} from './loggedOutSessions';\nimport {DEFAULT_SESSION_COOKIE_EXPIRY_TIME} from '../constants/sessionConstants';\n\n/**\n * Session token payload interface\n */\nexport interface SessionTokenPayload extends JWTPayload {\n /** Expiration timestamp — doubles as the access token expiry (JWT exp == access token exp) */\n exp: number;\n /** Issued at timestamp */\n iat: number;\n /** Organization ID if applicable */\n organizationId?: string;\n /** The refresh token; empty string if not provided by the auth server */\n refreshToken: string;\n /** OAuth scopes */\n scopes: string[];\n /** Session ID */\n sessionId: string;\n /** The server session this session joined, from the `sid` of the ID token. */\n sid?: string;\n /** When the user signed in, in seconds. Kept across token refresh, unlike `iat`. */\n signedInAt?: number;\n /** User ID */\n sub: string;\n /** Token type discriminant — must be 'session' for access-session JWTs */\n type: 'session';\n}\n\n/**\n * Session management utility class for JWT-based session cookies\n */\nclass SessionManager {\n /**\n * Get the signing secret from environment variable\n * Throws error in production if not set\n */\n private static getSecret(): Uint8Array {\n const secret: string | undefined = process.env['THUNDERID_SECRET'];\n\n if (!secret) {\n if (process.env['NODE_ENV'] === 'production') {\n throw new ThunderIDRuntimeError(\n 'THUNDERID_SECRET environment variable is required in production',\n 'session-secret-required',\n 'nextjs',\n 'Set the THUNDERID_SECRET environment variable with a secure random string',\n );\n }\n // Use a default secret for development (not secure)\n // eslint-disable-next-line no-console\n console.warn('Using default secret for development. Set THUNDERID_SECRET for production!');\n return new TextEncoder().encode('development-secret-not-for-production');\n }\n\n return new TextEncoder().encode(secret);\n }\n\n /**\n * Create a temporary session cookie for login initiation\n */\n static async createTempSession(sessionId: string): Promise<string> {\n const secret: Uint8Array = this.getSecret();\n\n const jwt: string = await new SignJWT({\n sessionId,\n type: 'temp',\n })\n .setProtectedHeader({alg: 'HS256'})\n .setIssuedAt()\n .setExpirationTime('15m')\n .sign(secret);\n\n return jwt;\n }\n\n /**\n * Resolve the session cookie expiry time in seconds.\n *\n * Resolution order (first defined value wins):\n * 1. `configuredExpiry` — value from `ThunderIDNodeConfig.sessionCookie?.expiryTime`\n * 2. `THUNDERID_SESSION_COOKIE_EXPIRY_TIME` environment variable\n * 3. `DEFAULT_SESSION_COOKIE_EXPIRY_TIME` (24 hours)\n */\n static resolveSessionCookieExpiry(configuredExpiry?: number): number {\n if (configuredExpiry != null && configuredExpiry > 0) {\n return configuredExpiry;\n }\n\n const envValue: string | undefined = process.env['THUNDERID_SESSION_COOKIE_EXPIRY_TIME'];\n\n if (envValue) {\n const parsed: number = parseInt(envValue, 10);\n\n if (!Number.isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n\n return DEFAULT_SESSION_COOKIE_EXPIRY_TIME;\n }\n\n static async createSessionToken(\n accessToken: string,\n userId: string,\n sessionId: string,\n scopes: string,\n accessTokenTtlSeconds: number,\n refreshToken: string,\n organizationId?: string,\n serverSession?: {sid?: string; signedInAt?: number},\n ): Promise<string> {\n const secret: Uint8Array = this.getSecret();\n\n const jwt: string = await new SignJWT({\n accessToken,\n organizationId,\n refreshToken,\n scopes,\n sessionId,\n // What a back-channel logout matches this session by.\n sid: serverSession?.sid,\n signedInAt: serverSession?.signedInAt ?? Math.floor(Date.now() / 1000),\n type: 'session',\n } as Omit<SessionTokenPayload, 'sub' | 'iat' | 'exp'>)\n .setProtectedHeader({alg: 'HS256'})\n .setSubject(userId)\n .setIssuedAt()\n .setExpirationTime(Math.floor(Date.now() / 1000) + accessTokenTtlSeconds)\n .sign(secret);\n\n return jwt;\n }\n\n /**\n * Verify and decode a session token\n */\n static async verifySessionToken(token: string): Promise<SessionTokenPayload> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload} = await jwtVerify(token, secret);\n\n if (payload['type'] !== 'session') {\n throw new Error('Invalid token type');\n }\n\n await this.refuseLoggedOutSession(payload as SessionTokenPayload);\n\n return payload as SessionTokenPayload;\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-session-token',\n 'nextjs',\n 'Session token verification failed',\n );\n }\n }\n\n /**\n * Verify a session token for refresh. Validates the HMAC signature and the\n * `type === 'session'` discriminant but intentionally skips the `exp` check\n * so an expired access token can still be exchanged for a new one.\n *\n * Session lifetime is still bounded — the cookie's `maxAge` is set from\n * `sessionCookieExpiryTime`, so the browser drops an over-age session regardless\n * of the access-token exp embedded in the JWT.\n *\n * Never use the returned payload for authorization.\n */\n static async verifySessionTokenForRefresh(token: string): Promise<SessionTokenPayload> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload: rawPayload} = await compactVerify(token, secret);\n const payload: SessionTokenPayload = JSON.parse(new TextDecoder().decode(rawPayload)) as SessionTokenPayload;\n\n if (payload.type !== 'session') {\n throw new Error('Invalid token type');\n }\n\n await this.refuseLoggedOutSession(payload);\n\n return payload;\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-session-token-for-refresh',\n 'nextjs',\n 'Session token signature or type check failed during refresh',\n );\n }\n }\n\n /**\n * Verify and decode a temporary session token\n */\n static async verifyTempSession(token: string): Promise<{sessionId: string}> {\n try {\n const secret: Uint8Array = this.getSecret();\n const {payload} = await jwtVerify(token, secret);\n\n if (payload['type'] !== 'temp') {\n throw new Error('Invalid token type');\n }\n\n return {sessionId: payload['sessionId'] as string};\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Invalid temporary session token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'invalid-temp-session-token',\n 'nextjs',\n 'Temporary session token verification failed',\n );\n }\n }\n\n /**\n * Get session cookie options\n */\n /**\n * Throws when a back-channel logout has ended the session this cookie carries. The cookie is\n * still correctly signed, so this is the only thing that stops it from being used.\n */\n private static async refuseLoggedOutSession(payload: SessionTokenPayload): Promise<void> {\n const loggedOut: boolean = await getLoggedOutSessions().isLoggedOut({\n sid: payload.sid,\n startedAt: payload.signedInAt ?? payload.iat,\n sub: payload.sub,\n });\n\n if (loggedOut) {\n throw new Error('The session was ended by a back-channel logout');\n }\n }\n\n static getSessionCookieOptions(maxAge: number): {\n httpOnly: boolean;\n maxAge: number;\n path: string;\n sameSite: 'lax';\n secure: boolean;\n } {\n return {\n httpOnly: true,\n maxAge,\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env['NODE_ENV'] === 'production',\n };\n }\n\n /**\n * Get temporary session cookie options\n */\n static getTempSessionCookieOptions(): {\n httpOnly: boolean;\n maxAge: number;\n path: string;\n sameSite: 'lax';\n secure: boolean;\n } {\n return {\n httpOnly: true,\n maxAge: 15 * 60,\n path: '/',\n sameSite: 'lax' as const,\n secure: process.env['NODE_ENV'] === 'production',\n };\n }\n\n /**\n * Get session cookie name\n */\n static getSessionCookieName(): string {\n return CookieConfig.getSessionCookieName();\n }\n\n /**\n * Get temporary session cookie name\n */\n static getTempSessionCookieName(): string {\n return CookieConfig.getTempSessionCookieName();\n }\n}\n\nexport default SessionManager;\n"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ const require_SessionManager = require('./SessionManager.cjs');
|
|
|
10
10
|
*/
|
|
11
11
|
const handleRefreshToken = async (sessionPayload, config) => {
|
|
12
12
|
const { baseUrl, clientId, clientSecret, sessionCookie } = config;
|
|
13
|
-
const { refreshToken: storedRefreshToken, sessionId, sub, scopes, organizationId } = sessionPayload;
|
|
13
|
+
const { refreshToken: storedRefreshToken, sessionId, sub, scopes, organizationId, sid, signedInAt, iat } = sessionPayload;
|
|
14
14
|
if (!storedRefreshToken) throw new Error("No refresh token found in session payload.");
|
|
15
15
|
const tokenEndpoint = `${baseUrl}/oauth2/token`;
|
|
16
16
|
const body = new URLSearchParams({
|
|
@@ -42,7 +42,10 @@ const handleRefreshToken = async (sessionPayload, config) => {
|
|
|
42
42
|
const newScopes = tokenData["scope"] ?? (Array.isArray(scopes) ? scopes.join(" ") : scopes ?? "");
|
|
43
43
|
const resolvedSessionCookieExpiry = require_SessionManager.default.resolveSessionCookieExpiry(sessionCookie?.expiryTime);
|
|
44
44
|
return {
|
|
45
|
-
newSessionToken: await require_SessionManager.default.createSessionToken(newAccessToken, sub, sessionId, newScopes, expiresIn, newRefreshToken, organizationId
|
|
45
|
+
newSessionToken: await require_SessionManager.default.createSessionToken(newAccessToken, sub, sessionId, newScopes, expiresIn, newRefreshToken, organizationId, {
|
|
46
|
+
sid,
|
|
47
|
+
signedInAt: signedInAt ?? iat
|
|
48
|
+
}),
|
|
46
49
|
sessionCookieExpiryTime: resolvedSessionCookieExpiry,
|
|
47
50
|
tokenResponse: {
|
|
48
51
|
accessToken: newAccessToken,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleRefreshToken.cjs","names":["body: URLSearchParams","response: Response","tokenData: Record<string, unknown>","newAccessToken: string","expiresIn: number","newRefreshToken: string","newScopes: string","resolvedSessionCookieExpiry: number","SessionManager"],"sources":["../../../src/utils/handleRefreshToken.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type {TokenResponse, SessionCookieConfig} from '@thunderid/node';\nimport SessionManager, {SessionTokenPayload} from './SessionManager';\n\n/**\n * Config required to call the token endpoint.\n */\nexport interface HandleRefreshTokenConfig {\n baseUrl: string;\n clientId: string;\n clientSecret: string;\n sessionCookie?: SessionCookieConfig;\n}\n\n/**\n * Result returned by handleRefreshToken.\n * Callers are responsible for persisting newSessionToken in the appropriate cookie context.\n */\nexport interface HandleRefreshTokenResult {\n newSessionToken: string;\n sessionCookieExpiryTime: number;\n tokenResponse: TokenResponse;\n}\n\n/**\n * Handles the OAuth refresh_token grant and builds a new session JWT string.\n *\n * Intentionally decoupled from cookie APIs so it can be called from both the Edge\n * Runtime (Next.js middleware) and the Node.js Runtime (server actions).\n * Cookie persistence is the caller's responsibility.\n */\nconst handleRefreshToken = async (\n sessionPayload: SessionTokenPayload,\n config: HandleRefreshTokenConfig,\n): Promise<HandleRefreshTokenResult> => {\n const {baseUrl, clientId, clientSecret, sessionCookie} = config;\n const {refreshToken: storedRefreshToken
|
|
1
|
+
{"version":3,"file":"handleRefreshToken.cjs","names":["body: URLSearchParams","response: Response","tokenData: Record<string, unknown>","newAccessToken: string","expiresIn: number","newRefreshToken: string","newScopes: string","resolvedSessionCookieExpiry: number","SessionManager"],"sources":["../../../src/utils/handleRefreshToken.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type {TokenResponse, SessionCookieConfig} from '@thunderid/node';\nimport SessionManager, {SessionTokenPayload} from './SessionManager';\n\n/**\n * Config required to call the token endpoint.\n */\nexport interface HandleRefreshTokenConfig {\n baseUrl: string;\n clientId: string;\n clientSecret: string;\n sessionCookie?: SessionCookieConfig;\n}\n\n/**\n * Result returned by handleRefreshToken.\n * Callers are responsible for persisting newSessionToken in the appropriate cookie context.\n */\nexport interface HandleRefreshTokenResult {\n newSessionToken: string;\n sessionCookieExpiryTime: number;\n tokenResponse: TokenResponse;\n}\n\n/**\n * Handles the OAuth refresh_token grant and builds a new session JWT string.\n *\n * Intentionally decoupled from cookie APIs so it can be called from both the Edge\n * Runtime (Next.js middleware) and the Node.js Runtime (server actions).\n * Cookie persistence is the caller's responsibility.\n */\nconst handleRefreshToken = async (\n sessionPayload: SessionTokenPayload,\n config: HandleRefreshTokenConfig,\n): Promise<HandleRefreshTokenResult> => {\n const {baseUrl, clientId, clientSecret, sessionCookie} = config;\n const {\n refreshToken: storedRefreshToken,\n sessionId,\n sub,\n scopes,\n organizationId,\n sid,\n signedInAt,\n iat,\n } = sessionPayload;\n\n if (!storedRefreshToken) {\n throw new Error('No refresh token found in session payload.');\n }\n\n const tokenEndpoint = `${baseUrl}/oauth2/token`;\n const body: URLSearchParams = new URLSearchParams({\n client_id: clientId ?? '',\n client_secret: clientSecret ?? '',\n grant_type: 'refresh_token',\n refresh_token: storedRefreshToken,\n });\n\n let response: Response;\n\n try {\n response = await fetch(tokenEndpoint, {\n body: body.toString(),\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\n method: 'POST',\n });\n } catch (fetchError) {\n throw new Error(\n `Token refresh network error: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`,\n );\n }\n\n if (!response.ok) {\n throw new Error(`Token endpoint rejected refresh (HTTP ${response.status}).`);\n }\n\n let tokenData: Record<string, unknown>;\n\n try {\n tokenData = (await response.json()) as Record<string, unknown>;\n } catch {\n throw new Error('Failed to parse token endpoint response as JSON.');\n }\n\n const newAccessToken: string = tokenData['access_token'] as string;\n const expiresIn: number = tokenData['expires_in'] as number;\n // Use the rotated refresh token if the server provided one; otherwise keep the existing one.\n const newRefreshToken: string = (tokenData['refresh_token'] as string | undefined) ?? storedRefreshToken;\n const newScopes: string =\n (tokenData['scope'] as string | undefined) ??\n (Array.isArray(scopes) ? scopes.join(' ') : ((scopes as string) ?? ''));\n\n const resolvedSessionCookieExpiry: number = SessionManager.resolveSessionCookieExpiry(sessionCookie?.expiryTime);\n\n const newSessionToken: string = await SessionManager.createSessionToken(\n newAccessToken,\n sub,\n sessionId,\n newScopes,\n expiresIn,\n newRefreshToken,\n organizationId,\n // A refreshed session is still the same sign-in, so it keeps what a logout matches it by.\n {sid, signedInAt: signedInAt ?? iat},\n );\n\n return {\n newSessionToken,\n sessionCookieExpiryTime: resolvedSessionCookieExpiry,\n tokenResponse: {\n accessToken: newAccessToken,\n createdAt: Math.floor(Date.now() / 1000),\n expiresIn: String(expiresIn),\n idToken: (tokenData['id_token'] as string | undefined) ?? '',\n refreshToken: newRefreshToken,\n scope: newScopes,\n tokenType: (tokenData['token_type'] as string | undefined) ?? 'Bearer',\n },\n };\n};\n\nexport default handleRefreshToken;\n"],"mappings":";;;;;;;;;;AAiCA,MAAM,qBAAqB,OACzB,gBACA,WACsC;CACtC,MAAM,EAAC,SAAS,UAAU,cAAc,kBAAiB;CACzD,MAAM,EACJ,cAAc,oBACd,WACA,KACA,QACA,gBACA,KACA,YACA,QACE;AAEJ,KAAI,CAAC,mBACH,OAAM,IAAI,MAAM,6CAA6C;CAG/D,MAAM,gBAAgB,GAAG,QAAQ;CACjC,MAAMA,OAAwB,IAAI,gBAAgB;EAChD,WAAW,YAAY;EACvB,eAAe,gBAAgB;EAC/B,YAAY;EACZ,eAAe;EAChB,CAAC;CAEF,IAAIC;AAEJ,KAAI;AACF,aAAW,MAAM,MAAM,eAAe;GACpC,MAAM,KAAK,UAAU;GACrB,SAAS,EAAC,gBAAgB,qCAAoC;GAC9D,QAAQ;GACT,CAAC;UACK,YAAY;AACnB,QAAM,IAAI,MACR,gCAAgC,sBAAsB,QAAQ,WAAW,UAAU,OAAO,WAAW,GACtG;;AAGH,KAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,yCAAyC,SAAS,OAAO,IAAI;CAG/E,IAAIC;AAEJ,KAAI;AACF,cAAa,MAAM,SAAS,MAAM;SAC5B;AACN,QAAM,IAAI,MAAM,mDAAmD;;CAGrE,MAAMC,iBAAyB,UAAU;CACzC,MAAMC,YAAoB,UAAU;CAEpC,MAAMC,kBAA2B,UAAU,oBAA2C;CACtF,MAAMC,YACH,UAAU,aACV,MAAM,QAAQ,OAAO,GAAG,OAAO,KAAK,IAAI,GAAK,UAAqB;CAErE,MAAMC,8BAAsCC,+BAAe,2BAA2B,eAAe,WAAW;AAchH,QAAO;EACL,iBAb8B,MAAMA,+BAAe,mBACnD,gBACA,KACA,WACA,WACA,WACA,iBACA,gBAEA;GAAC;GAAK,YAAY,cAAc;GAAI,CACrC;EAIC,yBAAyB;EACzB,eAAe;GACb,aAAa;GACb,WAAW,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;GACxC,WAAW,OAAO,UAAU;GAC5B,SAAU,UAAU,eAAsC;GAC1D,cAAc;GACd,OAAO;GACP,WAAY,UAAU,iBAAwC;GAC/D;EACF;;AAGH,iCAAe"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let __thunderid_node = require("@thunderid/node");
|
|
3
|
+
__thunderid_node = require_rolldown_runtime.__toESM(__thunderid_node);
|
|
4
|
+
|
|
5
|
+
//#region src/utils/loggedOutSessions.ts
|
|
6
|
+
const SHARED_KEY = Symbol.for("thunderid.nextjs.loggedOutSessions");
|
|
7
|
+
const shared = () => {
|
|
8
|
+
const scope = globalThis;
|
|
9
|
+
scope[SHARED_KEY] ??= { sessions: new __thunderid_node.LoggedOutSessions() };
|
|
10
|
+
return scope[SHARED_KEY];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Returns the list of sessions that back-channel logout has ended, shared by everything that runs
|
|
14
|
+
* in this process.
|
|
15
|
+
*/
|
|
16
|
+
const getLoggedOutSessions = () => shared().sessions;
|
|
17
|
+
/**
|
|
18
|
+
* Makes the list use the given store, so several instances of an application can share it.
|
|
19
|
+
* Without one, records stay in the memory of this process.
|
|
20
|
+
*/
|
|
21
|
+
const setLogoutRecordStore = (store) => {
|
|
22
|
+
const current = shared();
|
|
23
|
+
if (store && current.store !== store) {
|
|
24
|
+
current.store = store;
|
|
25
|
+
current.sessions = new __thunderid_node.LoggedOutSessions(store);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
exports.getLoggedOutSessions = getLoggedOutSessions;
|
|
31
|
+
exports.setLogoutRecordStore = setLogoutRecordStore;
|
|
32
|
+
//# sourceMappingURL=loggedOutSessions.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loggedOutSessions.cjs","names":["SHARED_KEY: unique symbol","scope: GlobalWithShared","LoggedOutSessions","current: Shared"],"sources":["../../../src/utils/loggedOutSessions.ts"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {LoggedOutSessions, LogoutRecordStore} from '@thunderid/node';\n\n// Next.js bundles the proxy, route handlers and server components separately, so a module-level\n// variable would give each its own list. The global object is the one thing they share in a process.\nconst SHARED_KEY: unique symbol = Symbol.for('thunderid.nextjs.loggedOutSessions');\n\ninterface Shared {\n sessions: LoggedOutSessions;\n store?: LogoutRecordStore;\n}\n\ntype GlobalWithShared = typeof globalThis & {[SHARED_KEY]?: Shared};\n\nconst shared = (): Shared => {\n const scope: GlobalWithShared = globalThis as GlobalWithShared;\n\n scope[SHARED_KEY] ??= {sessions: new LoggedOutSessions()};\n\n return scope[SHARED_KEY];\n};\n\n/**\n * Returns the list of sessions that back-channel logout has ended, shared by everything that runs\n * in this process.\n */\nexport const getLoggedOutSessions = (): LoggedOutSessions => shared().sessions;\n\n/**\n * Makes the list use the given store, so several instances of an application can share it.\n * Without one, records stay in the memory of this process.\n */\nexport const setLogoutRecordStore = (store?: LogoutRecordStore): void => {\n const current: Shared = shared();\n\n if (store && current.store !== store) {\n current.store = store;\n current.sessions = new LoggedOutSessions(store);\n }\n};\n"],"mappings":";;;;;AAOA,MAAMA,aAA4B,OAAO,IAAI,qCAAqC;AASlF,MAAM,eAAuB;CAC3B,MAAMC,QAA0B;AAEhC,OAAM,gBAAgB,EAAC,UAAU,IAAIC,oCAAmB,EAAC;AAEzD,QAAO,MAAM;;;;;;AAOf,MAAa,6BAAgD,QAAQ,CAAC;;;;;AAMtE,MAAa,wBAAwB,UAAoC;CACvE,MAAMC,UAAkB,QAAQ;AAEhC,KAAI,SAAS,QAAQ,UAAU,OAAO;AACpC,UAAQ,QAAQ;AAChB,UAAQ,WAAW,IAAID,mCAAkB,MAAM"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BaseChangeCredentialProps } from '@thunderid/react';
|
|
2
|
+
import { FC } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Props for the ChangeCredential component.
|
|
5
|
+
* Mirrors `@thunderid/react`'s `ChangeCredentialProps` exactly, so a consumer moving between
|
|
6
|
+
* the React and Next.js SDKs doesn't need to change how they call it.
|
|
7
|
+
*/
|
|
8
|
+
export type ChangeCredentialProps = Omit<BaseChangeCredentialProps, 'credentialDisplayName' | 'error' | 'fieldErrors' | 'loading' | 'onSubmit' | 'success'> & {
|
|
9
|
+
/**
|
|
10
|
+
* The credential attribute this instance manages, any attribute the user's entity type
|
|
11
|
+
* schema declares `credential: true` (for example `password` or `pin`). Defaults to
|
|
12
|
+
* `password`. Render the component once per credential to let a user manage more than one,
|
|
13
|
+
* for example `<ChangeCredential />` for the password and
|
|
14
|
+
* `<ChangeCredential attribute="pin" />` for a PIN.
|
|
15
|
+
*/
|
|
16
|
+
attribute?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Called after the credential has been changed successfully.
|
|
19
|
+
*/
|
|
20
|
+
onSuccess?: () => void;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* ChangeCredential lets the signed-in user set a new value for one of their own credentials.
|
|
24
|
+
*
|
|
25
|
+
* This is the Next.js-specific implementation: it uses `BaseChangeCredential` from
|
|
26
|
+
* `@thunderid/react` for rendering, but routes the write through
|
|
27
|
+
* `updateUserCredentialsAction` (a server action) instead of calling the ThunderID server
|
|
28
|
+
* directly from the browser, matching how `UserProfile` reaches `updateUserProfileAction`.
|
|
29
|
+
*
|
|
30
|
+
* Defaults to managing the `password` credential. To manage a different one (for example a
|
|
31
|
+
* PIN declared on the user type schema), set `attribute`; render the component once per
|
|
32
|
+
* credential to let a user manage several.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // Basic usage, manages the password
|
|
37
|
+
* <ChangeCredential onSuccess={() => toast('Password updated')} />
|
|
38
|
+
*
|
|
39
|
+
* // Managing a different credential declared on the schema
|
|
40
|
+
* <ChangeCredential attribute="pin" />
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare const ChangeCredential: FC<ChangeCredentialProps>;
|
|
44
|
+
export default ChangeCredential;
|
|
45
|
+
//# sourceMappingURL=ChangeCredential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChangeCredential.d.ts","sourceRoot":"","sources":["../../../../../src/client/components/presentation/ChangeCredential/ChangeCredential.tsx"],"names":[],"mappings":"AAMA,OAAO,EAEL,yBAAyB,EAI1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAC,EAAE,EAAkC,MAAM,OAAO,CAAC;AAa1D;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,IAAI,CACtC,yBAAyB,EACzB,uBAAuB,GAAG,OAAO,GAAG,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CACvF,GAAG;IACF;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,QAAA,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CA4E/C,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import getSessionId_default from "../../../../server/actions/getSessionId.js";
|
|
5
|
+
import useThunderID_default from "../../../contexts/ThunderID/useThunderID.js";
|
|
6
|
+
import updateUserCredentialsAction_default from "../../../../server/actions/updateUserCredentialsAction.js";
|
|
7
|
+
import { CredentialConstants, resolveChangeCredentialPolicy, supportsCredential } from "@thunderid/node";
|
|
8
|
+
import { BaseChangeCredential, useTranslation, useUser } from "@thunderid/react";
|
|
9
|
+
import { useMemo, useState } from "react";
|
|
10
|
+
import { jsx } from "react/jsx-runtime";
|
|
11
|
+
|
|
12
|
+
//#region src/client/components/presentation/ChangeCredential/ChangeCredential.tsx
|
|
13
|
+
/**
|
|
14
|
+
* Title-cases a credential attribute name for use as a display-name fallback when the schema
|
|
15
|
+
* declares no `displayName` for it, e.g. `pin` -> `Pin`.
|
|
16
|
+
*/
|
|
17
|
+
const defaultDisplayName = (attribute) => attribute.charAt(0).toUpperCase() + attribute.slice(1);
|
|
18
|
+
/**
|
|
19
|
+
* ChangeCredential lets the signed-in user set a new value for one of their own credentials.
|
|
20
|
+
*
|
|
21
|
+
* This is the Next.js-specific implementation: it uses `BaseChangeCredential` from
|
|
22
|
+
* `@thunderid/react` for rendering, but routes the write through
|
|
23
|
+
* `updateUserCredentialsAction` (a server action) instead of calling the ThunderID server
|
|
24
|
+
* directly from the browser, matching how `UserProfile` reaches `updateUserProfileAction`.
|
|
25
|
+
*
|
|
26
|
+
* Defaults to managing the `password` credential. To manage a different one (for example a
|
|
27
|
+
* PIN declared on the user type schema), set `attribute`; render the component once per
|
|
28
|
+
* credential to let a user manage several.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* // Basic usage, manages the password
|
|
33
|
+
* <ChangeCredential onSuccess={() => toast('Password updated')} />
|
|
34
|
+
*
|
|
35
|
+
* // Managing a different credential declared on the schema
|
|
36
|
+
* <ChangeCredential attribute="pin" />
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
const ChangeCredential = ({ attribute = CredentialConstants.PASSWORD, onSuccess, policy, preferences,...rest }) => {
|
|
40
|
+
const { preferences: contextPreferences } = useThunderID_default();
|
|
41
|
+
const { userSchema } = useUser();
|
|
42
|
+
const resolvedDisplayName = userSchema?.[attribute]?.displayName ?? defaultDisplayName(attribute);
|
|
43
|
+
const resolvedPreferences = useMemo(() => ({
|
|
44
|
+
...contextPreferences,
|
|
45
|
+
...preferences,
|
|
46
|
+
user: {
|
|
47
|
+
...contextPreferences?.user,
|
|
48
|
+
...preferences?.user
|
|
49
|
+
}
|
|
50
|
+
}), [contextPreferences, preferences]);
|
|
51
|
+
const { t } = useTranslation(resolvedPreferences?.i18n);
|
|
52
|
+
const [error, setError] = useState(null);
|
|
53
|
+
const [fieldErrors, setFieldErrors] = useState({});
|
|
54
|
+
const [loading, setLoading] = useState(false);
|
|
55
|
+
const [success, setSuccess] = useState(false);
|
|
56
|
+
const resolvedPolicy = useMemo(() => resolveChangeCredentialPolicy(userSchema, attribute, policy), [
|
|
57
|
+
userSchema,
|
|
58
|
+
attribute,
|
|
59
|
+
policy
|
|
60
|
+
]);
|
|
61
|
+
const handleSubmit = async ({ newPassword }) => {
|
|
62
|
+
setError(null);
|
|
63
|
+
setFieldErrors({});
|
|
64
|
+
setSuccess(false);
|
|
65
|
+
setLoading(true);
|
|
66
|
+
const result = await updateUserCredentialsAction_default({ [attribute]: newPassword }, await getSessionId_default());
|
|
67
|
+
if (result.success) {
|
|
68
|
+
setSuccess(true);
|
|
69
|
+
onSuccess?.();
|
|
70
|
+
} else {
|
|
71
|
+
const text = result.message ?? t(result.messageKey, {
|
|
72
|
+
credential: resolvedDisplayName,
|
|
73
|
+
credentialLower: resolvedDisplayName.toLowerCase()
|
|
74
|
+
});
|
|
75
|
+
if (result.field) setFieldErrors({ [result.field]: text });
|
|
76
|
+
else setError(text);
|
|
77
|
+
}
|
|
78
|
+
setLoading(false);
|
|
79
|
+
};
|
|
80
|
+
return /* @__PURE__ */ jsx(BaseChangeCredential, {
|
|
81
|
+
...rest,
|
|
82
|
+
credentialDisplayName: resolvedDisplayName,
|
|
83
|
+
error,
|
|
84
|
+
fieldErrors,
|
|
85
|
+
loading,
|
|
86
|
+
policy: resolvedPolicy,
|
|
87
|
+
preferences: resolvedPreferences,
|
|
88
|
+
success,
|
|
89
|
+
unavailable: !supportsCredential(userSchema, attribute),
|
|
90
|
+
onSubmit: (values) => {
|
|
91
|
+
handleSubmit(values);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
var ChangeCredential_default = ChangeCredential;
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { ChangeCredential_default as default };
|
|
99
|
+
//# sourceMappingURL=ChangeCredential.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChangeCredential.js","names":["ChangeCredential: FC<ChangeCredentialProps>","useThunderID","resolvedDisplayName: string","resolvedPolicy: PasswordPolicy","result: UpdateUserCredentialsActionResult","updateUserCredentialsAction","getSessionId","text: string"],"sources":["../../../../../src/client/components/presentation/ChangeCredential/ChangeCredential.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use client';\n\nimport {CredentialConstants, PasswordPolicy, resolveChangeCredentialPolicy, supportsCredential} from '@thunderid/node';\nimport {\n BaseChangeCredential,\n BaseChangeCredentialProps,\n ChangePasswordValues,\n useTranslation,\n useUser,\n} from '@thunderid/react';\nimport {FC, ReactElement, useMemo, useState} from 'react';\nimport getSessionId from '../../../../server/actions/getSessionId';\nimport updateUserCredentialsAction, {\n UpdateUserCredentialsActionResult,\n} from '../../../../server/actions/updateUserCredentialsAction';\nimport useThunderID from '../../../contexts/ThunderID/useThunderID';\n\n/**\n * Title-cases a credential attribute name for use as a display-name fallback when the schema\n * declares no `displayName` for it, e.g. `pin` -> `Pin`.\n */\nconst defaultDisplayName = (attribute: string): string => attribute.charAt(0).toUpperCase() + attribute.slice(1);\n\n/**\n * Props for the ChangeCredential component.\n * Mirrors `@thunderid/react`'s `ChangeCredentialProps` exactly, so a consumer moving between\n * the React and Next.js SDKs doesn't need to change how they call it.\n */\nexport type ChangeCredentialProps = Omit<\n BaseChangeCredentialProps,\n 'credentialDisplayName' | 'error' | 'fieldErrors' | 'loading' | 'onSubmit' | 'success'\n> & {\n /**\n * The credential attribute this instance manages, any attribute the user's entity type\n * schema declares `credential: true` (for example `password` or `pin`). Defaults to\n * `password`. Render the component once per credential to let a user manage more than one,\n * for example `<ChangeCredential />` for the password and\n * `<ChangeCredential attribute=\"pin\" />` for a PIN.\n */\n attribute?: string;\n /**\n * Called after the credential has been changed successfully.\n */\n onSuccess?: () => void;\n};\n\n/**\n * ChangeCredential lets the signed-in user set a new value for one of their own credentials.\n *\n * This is the Next.js-specific implementation: it uses `BaseChangeCredential` from\n * `@thunderid/react` for rendering, but routes the write through\n * `updateUserCredentialsAction` (a server action) instead of calling the ThunderID server\n * directly from the browser, matching how `UserProfile` reaches `updateUserProfileAction`.\n *\n * Defaults to managing the `password` credential. To manage a different one (for example a\n * PIN declared on the user type schema), set `attribute`; render the component once per\n * credential to let a user manage several.\n *\n * @example\n * ```tsx\n * // Basic usage, manages the password\n * <ChangeCredential onSuccess={() => toast('Password updated')} />\n *\n * // Managing a different credential declared on the schema\n * <ChangeCredential attribute=\"pin\" />\n * ```\n */\nconst ChangeCredential: FC<ChangeCredentialProps> = ({\n attribute = CredentialConstants.PASSWORD,\n onSuccess,\n policy,\n preferences,\n ...rest\n}: ChangeCredentialProps): ReactElement => {\n const {preferences: contextPreferences} = useThunderID();\n const {userSchema} = useUser();\n const resolvedDisplayName: string = userSchema?.[attribute]?.displayName ?? defaultDisplayName(attribute);\n\n const resolvedPreferences = useMemo(\n () => ({\n ...contextPreferences,\n ...preferences,\n user: {...contextPreferences?.user, ...preferences?.user},\n }),\n [contextPreferences, preferences],\n );\n const {t} = useTranslation(resolvedPreferences?.i18n);\n\n const [error, setError] = useState<string | null>(null);\n const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});\n const [loading, setLoading] = useState<boolean>(false);\n const [success, setSuccess] = useState<boolean>(false);\n\n const resolvedPolicy: PasswordPolicy = useMemo(\n () => resolveChangeCredentialPolicy(userSchema, attribute, policy),\n [userSchema, attribute, policy],\n );\n\n const handleSubmit = async ({newPassword}: ChangePasswordValues): Promise<void> => {\n setError(null);\n setFieldErrors({});\n setSuccess(false);\n setLoading(true);\n\n const result: UpdateUserCredentialsActionResult = await updateUserCredentialsAction(\n {[attribute]: newPassword},\n await getSessionId(),\n );\n\n if (result.success) {\n setSuccess(true);\n onSuccess?.();\n } else {\n const text: string =\n result.message ??\n t(result.messageKey, {credential: resolvedDisplayName, credentialLower: resolvedDisplayName.toLowerCase()});\n\n if (result.field) {\n setFieldErrors({[result.field]: text});\n } else {\n setError(text);\n }\n }\n\n setLoading(false);\n };\n\n return (\n <BaseChangeCredential\n {...rest}\n credentialDisplayName={resolvedDisplayName}\n error={error}\n fieldErrors={fieldErrors}\n loading={loading}\n policy={resolvedPolicy}\n preferences={resolvedPreferences}\n success={success}\n unavailable={!supportsCredential(userSchema, attribute)}\n onSubmit={(values: ChangePasswordValues): void => {\n void handleSubmit(values);\n }}\n />\n );\n};\n\nexport default ChangeCredential;\n"],"mappings":";;;;;;;;;;;;;;;;AAwBA,MAAM,sBAAsB,cAA8B,UAAU,OAAO,EAAE,CAAC,aAAa,GAAG,UAAU,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;AA8ChH,MAAMA,oBAA+C,EACnD,YAAY,oBAAoB,UAChC,WACA,QACA,YACA,GAAG,WACsC;CACzC,MAAM,EAAC,aAAa,uBAAsBC,sBAAc;CACxD,MAAM,EAAC,eAAc,SAAS;CAC9B,MAAMC,sBAA8B,aAAa,YAAY,eAAe,mBAAmB,UAAU;CAEzG,MAAM,sBAAsB,eACnB;EACL,GAAG;EACH,GAAG;EACH,MAAM;GAAC,GAAG,oBAAoB;GAAM,GAAG,aAAa;GAAK;EAC1D,GACD,CAAC,oBAAoB,YAAY,CAClC;CACD,MAAM,EAAC,MAAK,eAAe,qBAAqB,KAAK;CAErD,MAAM,CAAC,OAAO,YAAY,SAAwB,KAAK;CACvD,MAAM,CAAC,aAAa,kBAAkB,SAAiC,EAAE,CAAC;CAC1E,MAAM,CAAC,SAAS,cAAc,SAAkB,MAAM;CACtD,MAAM,CAAC,SAAS,cAAc,SAAkB,MAAM;CAEtD,MAAMC,iBAAiC,cAC/B,8BAA8B,YAAY,WAAW,OAAO,EAClE;EAAC;EAAY;EAAW;EAAO,CAChC;CAED,MAAM,eAAe,OAAO,EAAC,kBAAsD;AACjF,WAAS,KAAK;AACd,iBAAe,EAAE,CAAC;AAClB,aAAW,MAAM;AACjB,aAAW,KAAK;EAEhB,MAAMC,SAA4C,MAAMC,oCACtD,GAAE,YAAY,aAAY,EAC1B,MAAMC,sBAAc,CACrB;AAED,MAAI,OAAO,SAAS;AAClB,cAAW,KAAK;AAChB,gBAAa;SACR;GACL,MAAMC,OACJ,OAAO,WACP,EAAE,OAAO,YAAY;IAAC,YAAY;IAAqB,iBAAiB,oBAAoB,aAAa;IAAC,CAAC;AAE7G,OAAI,OAAO,MACT,gBAAe,GAAE,OAAO,QAAQ,MAAK,CAAC;OAEtC,UAAS,KAAK;;AAIlB,aAAW,MAAM;;AAGnB,QACE,oBAAC;EACC,GAAI;EACJ,uBAAuB;EAChB;EACM;EACJ;EACT,QAAQ;EACR,aAAa;EACJ;EACT,aAAa,CAAC,mBAAmB,YAAY,UAAU;EACvD,WAAW,WAAuC;AAChD,GAAK,aAAa,OAAO;;GAE3B;;AAIN,+BAAe"}
|
|
@@ -27,6 +27,18 @@ export type UserDropdownProps = Omit<BaseUserDropdownProps, 'user' | 'onManagePr
|
|
|
27
27
|
* When provided, this completely replaces the default dropdown rendering.
|
|
28
28
|
*/
|
|
29
29
|
children?: (props: UserDropdownRenderProps) => ReactNode;
|
|
30
|
+
/**
|
|
31
|
+
* Called instead of opening the built-in "Manage Profile" popup when the "Manage Profile"
|
|
32
|
+
* menu item (see `manageProfileLabel`) is clicked. Use this when the app has its own
|
|
33
|
+
* profile/account page it wants to navigate to instead, for example with the router's
|
|
34
|
+
* `push()`.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* <UserDropdown manageProfileLabel="Manage Account" onManageProfile={() => router.push('/account')} />
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
onManageProfile?: () => void;
|
|
30
42
|
/**
|
|
31
43
|
* Custom render function for the dropdown content.
|
|
32
44
|
* When provided, this replaces just the dropdown content while keeping the trigger.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserDropdown.d.ts","sourceRoot":"","sources":["../../../../../src/client/components/presentation/UserDropdown/UserDropdown.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAmB,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAC,EAAE,EAAgB,SAAS,EAAW,MAAM,OAAO,CAAC;AAI5D;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,2CAA2C;IAC3C,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,aAAa,EAAE,OAAO,CAAC;IACvB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,oCAAoC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,oCAAoC;IACpC,IAAI,EAAE,GAAG,CAAC;CACX;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,iBAAiB,CAAC,GAAG;IACxF;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAC;IACzD;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAC;IAC/D;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAC;CAC/D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,QAAA,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"UserDropdown.d.ts","sourceRoot":"","sources":["../../../../../src/client/components/presentation/UserDropdown/UserDropdown.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAmB,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAC,EAAE,EAAgB,SAAS,EAAW,MAAM,OAAO,CAAC;AAI5D;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,2CAA2C;IAC3C,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,aAAa,EAAE,OAAO,CAAC;IACvB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,oCAAoC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,oCAAoC;IACpC,IAAI,EAAE,GAAG,CAAC;CACX;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,qBAAqB,EAAE,MAAM,GAAG,iBAAiB,CAAC,GAAG;IACxF;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAC;IACzD;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAC;IAC/D;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAC;CAC/D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,QAAA,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAwFvC,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -54,10 +54,14 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
54
54
|
* />
|
|
55
55
|
* ```
|
|
56
56
|
*/
|
|
57
|
-
const UserDropdown = ({ children, renderTrigger, renderDropdown, onSignOut,...rest }) => {
|
|
57
|
+
const UserDropdown = ({ children, renderTrigger, renderDropdown, onManageProfile: onManageProfileOverride, onSignOut,...rest }) => {
|
|
58
58
|
const { user, isLoading, signOut } = useThunderID_default();
|
|
59
59
|
const [isProfileOpen, setIsProfileOpen] = useState(false);
|
|
60
60
|
const handleManageProfile = () => {
|
|
61
|
+
if (onManageProfileOverride) {
|
|
62
|
+
onManageProfileOverride();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
61
65
|
setIsProfileOpen(true);
|
|
62
66
|
};
|
|
63
67
|
const handleSignOut = () => {
|
|
@@ -75,7 +79,7 @@ const UserDropdown = ({ children, renderTrigger, renderDropdown, onSignOut,...re
|
|
|
75
79
|
signOut: handleSignOut,
|
|
76
80
|
user
|
|
77
81
|
};
|
|
78
|
-
if (children) return /* @__PURE__ */ jsxs(Fragment, { children: [children(renderProps), /* @__PURE__ */ jsx(UserProfile_default, {
|
|
82
|
+
if (children) return /* @__PURE__ */ jsxs(Fragment, { children: [children(renderProps), !onManageProfileOverride && /* @__PURE__ */ jsx(UserProfile_default, {
|
|
79
83
|
mode: "popup",
|
|
80
84
|
open: isProfileOpen,
|
|
81
85
|
onOpenChange: setIsProfileOpen
|
|
@@ -86,7 +90,7 @@ const UserDropdown = ({ children, renderTrigger, renderDropdown, onSignOut,...re
|
|
|
86
90
|
onManageProfile: handleManageProfile,
|
|
87
91
|
onSignOut: handleSignOut,
|
|
88
92
|
...rest
|
|
89
|
-
}), /* @__PURE__ */ jsx(UserProfile_default, {
|
|
93
|
+
}), !onManageProfileOverride && /* @__PURE__ */ jsx(UserProfile_default, {
|
|
90
94
|
mode: "popup",
|
|
91
95
|
open: isProfileOpen,
|
|
92
96
|
onOpenChange: setIsProfileOpen
|
|
@@ -97,7 +101,7 @@ const UserDropdown = ({ children, renderTrigger, renderDropdown, onSignOut,...re
|
|
|
97
101
|
onManageProfile: handleManageProfile,
|
|
98
102
|
onSignOut: handleSignOut,
|
|
99
103
|
...rest
|
|
100
|
-
}), isProfileOpen && /* @__PURE__ */ jsx(UserProfile_default, {
|
|
104
|
+
}), !onManageProfileOverride && isProfileOpen && /* @__PURE__ */ jsx(UserProfile_default, {
|
|
101
105
|
mode: "popup",
|
|
102
106
|
open: isProfileOpen,
|
|
103
107
|
onOpenChange: setIsProfileOpen
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserDropdown.js","names":["UserDropdown: FC<UserDropdownProps>","useThunderID","renderProps: UserDropdownRenderProps","UserProfile"],"sources":["../../../../../src/client/components/presentation/UserDropdown/UserDropdown.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use client';\n\nimport {BaseUserDropdown, BaseUserDropdownProps} from '@thunderid/react';\nimport {FC, ReactElement, ReactNode, useState} from 'react';\nimport useThunderID from '../../../contexts/ThunderID/useThunderID';\nimport UserProfile from '../UserProfile/UserProfile.js';\n\n/**\n * Render props data passed to the children function\n */\nexport interface UserDropdownRenderProps {\n /** Function to close the profile dialog */\n closeProfile: () => void;\n /** Whether user data is currently loading */\n isLoading: boolean;\n /** Whether the profile dialog is currently open */\n isProfileOpen: boolean;\n /** Function to open the user profile dialog */\n openProfile: () => void;\n /** Function to sign out the user */\n signOut: () => void;\n /** The authenticated user object */\n user: any;\n}\n\n/**\n * Props for the UserDropdown component.\n * Extends BaseUserDropdownProps but excludes user, onManageProfile, and onSignOut since they're handled internally\n */\nexport type UserDropdownProps = Omit<BaseUserDropdownProps, 'user' | 'onManageProfile'> & {\n /**\n * Render prop function that receives user state and actions.\n * When provided, this completely replaces the default dropdown rendering.\n */\n children?: (props: UserDropdownRenderProps) => ReactNode;\n /**\n * Custom render function for the dropdown content.\n * When provided, this replaces just the dropdown content while keeping the trigger.\n */\n renderDropdown?: (props: UserDropdownRenderProps) => ReactNode;\n /**\n * Custom render function for the trigger button.\n * When provided, this replaces just the trigger button while keeping the dropdown.\n */\n renderTrigger?: (props: UserDropdownRenderProps) => ReactNode;\n};\n\n/**\n * UserDropdown component displays a user avatar with a dropdown menu.\n * When clicked, it shows a popover with customizable menu items.\n * This component is the React-specific implementation that uses the BaseUserDropdown\n * and automatically retrieves the user data from ThunderID context.\n *\n * Supports render props for complete customization of the dropdown appearance and behavior.\n *\n * @example\n * ```tsx\n * // Basic usage - will use user from ThunderID context\n * <UserDropdown menuItems={[\n * { label: 'Profile', onClick: () => {} },\n * { label: 'Settings', href: '/settings' },\n * { label: 'Sign Out', onClick: () => {} }\n * ]} />\n *\n * // With custom configuration\n * <UserDropdown\n * showTriggerLabel={true}\n * avatarSize={40}\n * fallback={<div>Please sign in</div>}\n * />\n *\n * // Using render props for complete customization\n * <UserDropdown>\n * {({ user, isLoading, openProfile, signOut }) => (\n * <div>\n * <button onClick={openProfile}>\n * {user?.name || 'Loading...'}\n * </button>\n * <button onClick={signOut}>Logout</button>\n * </div>\n * )}\n * </UserDropdown>\n *\n * // Using partial render props\n * <UserDropdown\n * renderTrigger={({ user, openProfile }) => (\n * <button onClick={openProfile} className=\"custom-trigger\">\n * Welcome, {user?.name}!\n * </button>\n * )}\n * />\n * ```\n */\nconst UserDropdown: FC<UserDropdownProps> = ({\n children,\n renderTrigger,\n renderDropdown,\n onSignOut,\n ...rest\n}: UserDropdownProps): ReactElement => {\n const {user, isLoading, signOut} = useThunderID();\n const [isProfileOpen, setIsProfileOpen] = useState(false);\n\n const handleManageProfile = (): void => {\n setIsProfileOpen(true);\n };\n\n const handleSignOut = (): void => {\n signOut();\n if (onSignOut) {\n onSignOut();\n }\n };\n\n const closeProfile = (): void => {\n setIsProfileOpen(false);\n };\n\n // Prepare render props data\n const renderProps: UserDropdownRenderProps = {\n closeProfile,\n isLoading: isLoading!,\n isProfileOpen,\n openProfile: handleManageProfile,\n signOut: handleSignOut,\n user,\n };\n\n // If children render prop is provided, use it for complete customization\n if (children) {\n return (\n <>\n {children(renderProps)}\n <UserProfile mode=\"popup\" open={isProfileOpen} onOpenChange={setIsProfileOpen}
|
|
1
|
+
{"version":3,"file":"UserDropdown.js","names":["UserDropdown: FC<UserDropdownProps>","useThunderID","renderProps: UserDropdownRenderProps","UserProfile"],"sources":["../../../../../src/client/components/presentation/UserDropdown/UserDropdown.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\n'use client';\n\nimport {BaseUserDropdown, BaseUserDropdownProps} from '@thunderid/react';\nimport {FC, ReactElement, ReactNode, useState} from 'react';\nimport useThunderID from '../../../contexts/ThunderID/useThunderID';\nimport UserProfile from '../UserProfile/UserProfile.js';\n\n/**\n * Render props data passed to the children function\n */\nexport interface UserDropdownRenderProps {\n /** Function to close the profile dialog */\n closeProfile: () => void;\n /** Whether user data is currently loading */\n isLoading: boolean;\n /** Whether the profile dialog is currently open */\n isProfileOpen: boolean;\n /** Function to open the user profile dialog */\n openProfile: () => void;\n /** Function to sign out the user */\n signOut: () => void;\n /** The authenticated user object */\n user: any;\n}\n\n/**\n * Props for the UserDropdown component.\n * Extends BaseUserDropdownProps but excludes user, onManageProfile, and onSignOut since they're handled internally\n */\nexport type UserDropdownProps = Omit<BaseUserDropdownProps, 'user' | 'onManageProfile'> & {\n /**\n * Render prop function that receives user state and actions.\n * When provided, this completely replaces the default dropdown rendering.\n */\n children?: (props: UserDropdownRenderProps) => ReactNode;\n /**\n * Called instead of opening the built-in \"Manage Profile\" popup when the \"Manage Profile\"\n * menu item (see `manageProfileLabel`) is clicked. Use this when the app has its own\n * profile/account page it wants to navigate to instead, for example with the router's\n * `push()`.\n *\n * @example\n * ```tsx\n * <UserDropdown manageProfileLabel=\"Manage Account\" onManageProfile={() => router.push('/account')} />\n * ```\n */\n onManageProfile?: () => void;\n /**\n * Custom render function for the dropdown content.\n * When provided, this replaces just the dropdown content while keeping the trigger.\n */\n renderDropdown?: (props: UserDropdownRenderProps) => ReactNode;\n /**\n * Custom render function for the trigger button.\n * When provided, this replaces just the trigger button while keeping the dropdown.\n */\n renderTrigger?: (props: UserDropdownRenderProps) => ReactNode;\n};\n\n/**\n * UserDropdown component displays a user avatar with a dropdown menu.\n * When clicked, it shows a popover with customizable menu items.\n * This component is the React-specific implementation that uses the BaseUserDropdown\n * and automatically retrieves the user data from ThunderID context.\n *\n * Supports render props for complete customization of the dropdown appearance and behavior.\n *\n * @example\n * ```tsx\n * // Basic usage - will use user from ThunderID context\n * <UserDropdown menuItems={[\n * { label: 'Profile', onClick: () => {} },\n * { label: 'Settings', href: '/settings' },\n * { label: 'Sign Out', onClick: () => {} }\n * ]} />\n *\n * // With custom configuration\n * <UserDropdown\n * showTriggerLabel={true}\n * avatarSize={40}\n * fallback={<div>Please sign in</div>}\n * />\n *\n * // Using render props for complete customization\n * <UserDropdown>\n * {({ user, isLoading, openProfile, signOut }) => (\n * <div>\n * <button onClick={openProfile}>\n * {user?.name || 'Loading...'}\n * </button>\n * <button onClick={signOut}>Logout</button>\n * </div>\n * )}\n * </UserDropdown>\n *\n * // Using partial render props\n * <UserDropdown\n * renderTrigger={({ user, openProfile }) => (\n * <button onClick={openProfile} className=\"custom-trigger\">\n * Welcome, {user?.name}!\n * </button>\n * )}\n * />\n * ```\n */\nconst UserDropdown: FC<UserDropdownProps> = ({\n children,\n renderTrigger,\n renderDropdown,\n onManageProfile: onManageProfileOverride,\n onSignOut,\n ...rest\n}: UserDropdownProps): ReactElement => {\n const {user, isLoading, signOut} = useThunderID();\n const [isProfileOpen, setIsProfileOpen] = useState(false);\n\n const handleManageProfile = (): void => {\n if (onManageProfileOverride) {\n onManageProfileOverride();\n return;\n }\n setIsProfileOpen(true);\n };\n\n const handleSignOut = (): void => {\n signOut();\n if (onSignOut) {\n onSignOut();\n }\n };\n\n const closeProfile = (): void => {\n setIsProfileOpen(false);\n };\n\n // Prepare render props data\n const renderProps: UserDropdownRenderProps = {\n closeProfile,\n isLoading: isLoading!,\n isProfileOpen,\n openProfile: handleManageProfile,\n signOut: handleSignOut,\n user,\n };\n\n // If children render prop is provided, use it for complete customization\n if (children) {\n return (\n <>\n {children(renderProps)}\n {!onManageProfileOverride && <UserProfile mode=\"popup\" open={isProfileOpen} onOpenChange={setIsProfileOpen} />}\n </>\n );\n }\n\n // If partial render props are provided, customize specific parts\n if (renderTrigger || renderDropdown) {\n // This would require significant changes to BaseUserDropdown to support partial customization\n // For now, we'll provide a simple implementation that shows how it could work\n return (\n <>\n {renderTrigger ? (\n renderTrigger(renderProps)\n ) : (\n <BaseUserDropdown\n user={user}\n isLoading={isLoading}\n onManageProfile={handleManageProfile}\n onSignOut={handleSignOut}\n {...rest}\n />\n )}\n {/* Note: renderDropdown would need BaseUserDropdown modifications to implement properly */}\n {!onManageProfileOverride && <UserProfile mode=\"popup\" open={isProfileOpen} onOpenChange={setIsProfileOpen} />}\n </>\n );\n }\n\n // Default behavior - use BaseUserDropdown as before\n return (\n <>\n <BaseUserDropdown\n user={user}\n isLoading={isLoading}\n onManageProfile={handleManageProfile}\n onSignOut={handleSignOut}\n {...rest}\n />\n {!onManageProfileOverride && isProfileOpen && (\n <UserProfile mode=\"popup\" open={isProfileOpen} onOpenChange={setIsProfileOpen} />\n )}\n </>\n );\n};\n\nexport default UserDropdown;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4GA,MAAMA,gBAAuC,EAC3C,UACA,eACA,gBACA,iBAAiB,yBACjB,UACA,GAAG,WACkC;CACrC,MAAM,EAAC,MAAM,WAAW,YAAWC,sBAAc;CACjD,MAAM,CAAC,eAAe,oBAAoB,SAAS,MAAM;CAEzD,MAAM,4BAAkC;AACtC,MAAI,yBAAyB;AAC3B,4BAAyB;AACzB;;AAEF,mBAAiB,KAAK;;CAGxB,MAAM,sBAA4B;AAChC,WAAS;AACT,MAAI,UACF,YAAW;;CAIf,MAAM,qBAA2B;AAC/B,mBAAiB,MAAM;;CAIzB,MAAMC,cAAuC;EAC3C;EACW;EACX;EACA,aAAa;EACb,SAAS;EACT;EACD;AAGD,KAAI,SACF,QACE,4CACG,SAAS,YAAY,EACrB,CAAC,2BAA2B,oBAACC;EAAY,MAAK;EAAQ,MAAM;EAAe,cAAc;GAAoB,IAC7G;AAKP,KAAI,iBAAiB,eAGnB,QACE,4CACG,gBACC,cAAc,YAAY,GAE1B,oBAAC;EACO;EACK;EACX,iBAAiB;EACjB,WAAW;EACX,GAAI;GACJ,EAGH,CAAC,2BAA2B,oBAACA;EAAY,MAAK;EAAQ,MAAM;EAAe,cAAc;GAAoB,IAC7G;AAKP,QACE,4CACE,oBAAC;EACO;EACK;EACX,iBAAiB;EACjB,WAAW;EACX,GAAI;GACJ,EACD,CAAC,2BAA2B,iBAC3B,oBAACA;EAAY,MAAK;EAAQ,MAAM;EAAe,cAAc;GAAoB,IAElF;;AAIP,2BAAe"}
|
|
@@ -20,7 +20,7 @@ export type ThunderIDClientProviderProps = Partial<Omit<ThunderIDProviderProps,
|
|
|
20
20
|
applicationId?: string;
|
|
21
21
|
language?: string;
|
|
22
22
|
}) => Promise<FlowMetadataResponse>;
|
|
23
|
-
handleOAuthCallback: (code: string, state: string
|
|
23
|
+
handleOAuthCallback: (code: string, state: string) => Promise<{
|
|
24
24
|
error?: string;
|
|
25
25
|
redirectUrl?: string;
|
|
26
26
|
success: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThunderIDProvider.d.ts","sourceRoot":"","sources":["../../../../src/client/contexts/ThunderID/ThunderIDProvider.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,eAAe,EAEf,oBAAoB,EAEpB,qBAAqB,EACrB,IAAI,EACJ,WAAW,EAGZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAQL,sBAAsB,
|
|
1
|
+
{"version":3,"file":"ThunderIDProvider.d.ts","sourceRoot":"","sources":["../../../../src/client/contexts/ThunderID/ThunderIDProvider.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,eAAe,EAEf,oBAAoB,EAEpB,qBAAqB,EACrB,IAAI,EACJ,WAAW,EAGZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAQL,sBAAsB,EACvB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAC,EAAE,EAAE,iBAAiB,EAAkD,MAAM,OAAO,CAAC;AAC7F,OAAyB,EAAC,qBAAqB,EAAC,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAC,aAAa,EAAC,MAAM,sCAAsC,CAAC;AAGnE;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,OAAO,CAAC,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,UAAU,CAAC,CAAC,GACtG,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,UAAU,CAAC,GAAG;IACrD,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAC;IACtD,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACnG,mBAAmB,EAAE,CACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,KACV,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;IACvE;;;OAGG;IACH,WAAW,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,qBAAqB,CAAC,oBAAoB,CAAC,CAAC;IAChE,YAAY,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3C,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IACxC,OAAO,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IACxC,aAAa,EAAE,CACb,aAAa,EAAE,qBAAqB,EACpC,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC;QAAC,IAAI,EAAE;YAAC,IAAI,EAAE,IAAI,CAAA;SAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;IACpE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,IAAI,CAAC;CACrD,CAAC;AAEJ,QAAA,MAAM,uBAAuB,EAAE,EAAE,CAAC,iBAAiB,CAAC,4BAA4B,CAAC,CA2VhF,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import logger_default from "../../../utils/logger.js";
|
|
5
5
|
import ThunderIDContext_default from "./ThunderIDContext.js";
|
|
6
6
|
import { ThunderIDRuntimeError, generateFlattenedUserProfile, getVendorPrefix } from "@thunderid/node";
|
|
7
|
-
import { FlowMetaProvider, FlowProvider, I18nProvider, ThemeProvider, ThunderIDContext, UserProvider
|
|
7
|
+
import { FlowMetaProvider, FlowProvider, I18nProvider, ThemeProvider, ThunderIDContext, UserProvider } from "@thunderid/react";
|
|
8
8
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
9
9
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
10
10
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -35,14 +35,13 @@ const ThunderIDClientProvider = ({ baseUrl, children, signIn, clearSession, refr
|
|
|
35
35
|
try {
|
|
36
36
|
const code = searchParams.get("code");
|
|
37
37
|
const state = searchParams.get("state");
|
|
38
|
-
const sessionState = searchParams.get("session_state");
|
|
39
38
|
if (searchParams.get("error")) {
|
|
40
39
|
logger_default.error("[ThunderIDClientProvider] An error was received for the initiated sign-in request.");
|
|
41
40
|
return;
|
|
42
41
|
}
|
|
43
42
|
if (code && state) {
|
|
44
43
|
setIsLoading(true);
|
|
45
|
-
const result = await handleOAuthCallback(code, state
|
|
44
|
+
const result = await handleOAuthCallback(code, state);
|
|
46
45
|
if (result.success) if (result.redirectUrl) router.push(result.redirectUrl);
|
|
47
46
|
else window.location.reload();
|
|
48
47
|
else logger_default.error(`[ThunderIDClientProvider] An error occurred while signing in: ${result.error || "Authentication failed"}`);
|
|
@@ -204,7 +203,7 @@ const ThunderIDClientProvider = ({ baseUrl, children, signIn, clearSession, refr
|
|
|
204
203
|
fetchMeta,
|
|
205
204
|
children: /* @__PURE__ */ jsx(ThemeProvider, {
|
|
206
205
|
theme: preferences?.theme?.overrides,
|
|
207
|
-
mode:
|
|
206
|
+
mode: preferences?.theme?.mode,
|
|
208
207
|
children: /* @__PURE__ */ jsx(FlowProvider, { children: /* @__PURE__ */ jsx(UserProvider, {
|
|
209
208
|
profile: userProfile,
|
|
210
209
|
userSchema,
|