@payez/next-mvp 4.8.0 → 4.8.2
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/POST-INSTALL.md +408 -408
- package/README.md +859 -859
- package/dist/components/account/MobileNavDrawer.js +18 -18
- package/dist/components/account/UserAvatarMenu.js +1 -1
- package/dist/components/admin/AlertSettingsTab.js +40 -40
- package/dist/components/admin/LoggingSettingsTab.js +28 -28
- package/dist/lib/session-store.js +21 -21
- package/dist/lib/startup-init.js +19 -19
- package/dist/middleware/create-middleware.js +6 -5
- package/dist/pages/login/page.js +28 -28
- package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
- package/dist/vibe/sessions.js +28 -3
- package/package.json +2 -2
- package/scripts/check-publish-eol.mjs +33 -15
- package/src/api/auth-handler.ts +550 -550
- package/src/api/index.ts +17 -17
- package/src/api-handlers/account/change-password.ts +170 -170
- package/src/api-handlers/account/masked-info.ts +46 -46
- package/src/api-handlers/account/profile.ts +99 -99
- package/src/api-handlers/account/recovery/forward.ts +117 -117
- package/src/api-handlers/account/recovery/initiate.ts +59 -59
- package/src/api-handlers/account/recovery/send-code.ts +21 -21
- package/src/api-handlers/account/recovery/verify-code.ts +20 -20
- package/src/api-handlers/account/reset-password.ts +87 -87
- package/src/api-handlers/account/send-code.ts +70 -70
- package/src/api-handlers/account/update-phone.ts +84 -84
- package/src/api-handlers/account/validate-password.ts +139 -139
- package/src/api-handlers/account/verify-email.ts +134 -134
- package/src/api-handlers/account/verify-sms.ts +134 -134
- package/src/api-handlers/admin/analytics.ts +452 -452
- package/src/api-handlers/admin/audit.ts +232 -232
- package/src/api-handlers/admin/index.ts +63 -63
- package/src/api-handlers/admin/redis-sessions.ts +259 -259
- package/src/api-handlers/admin/sessions.ts +327 -327
- package/src/api-handlers/admin/site-logs.ts +388 -388
- package/src/api-handlers/admin/stats.ts +260 -260
- package/src/api-handlers/admin/users.ts +251 -251
- package/src/api-handlers/admin/vibe-data.ts +333 -333
- package/src/api-handlers/anon/preferences.ts +123 -123
- package/src/api-handlers/auth/credential-login-email-code.ts +92 -92
- package/src/api-handlers/auth/credential-login.ts +948 -948
- package/src/api-handlers/auth/jwks.ts +31 -31
- package/src/api-handlers/auth/login.ts +252 -252
- package/src/api-handlers/auth/passkey-signin.ts +116 -116
- package/src/api-handlers/auth/refresh.ts +829 -829
- package/src/api-handlers/auth/signout.ts +201 -201
- package/src/api-handlers/auth/status.ts +23 -23
- package/src/api-handlers/auth/update-session.ts +144 -144
- package/src/api-handlers/auth/validate.ts +45 -45
- package/src/api-handlers/auth/verify-code.ts +116 -116
- package/src/api-handlers/mfa/backup-code-verify.ts +170 -170
- package/src/api-handlers/mfa/backup-codes.ts +42 -42
- package/src/api-handlers/mfa/methods.ts +47 -47
- package/src/api-handlers/mfa/totp-confirm.ts +43 -43
- package/src/api-handlers/mfa/totp-enroll.ts +43 -43
- package/src/api-handlers/mfa/totp-verify.ts +172 -172
- package/src/api-handlers/oauth/resume.ts +182 -182
- package/src/api-handlers/onboarding/send-email-code.ts +18 -18
- package/src/api-handlers/onboarding/set-password.ts +21 -21
- package/src/api-handlers/onboarding/verify-code.ts +20 -20
- package/src/api-handlers/onboarding/verify-welcome-email.ts +17 -17
- package/src/api-handlers/passkeys/list.ts +32 -32
- package/src/api-handlers/passkeys/manage.ts +55 -55
- package/src/api-handlers/passkeys/register-options.ts +43 -43
- package/src/api-handlers/passkeys/register-verify.ts +42 -42
- package/src/api-handlers/session/refresh-viability.ts +56 -56
- package/src/api-handlers/session/viability.ts +238 -238
- package/src/api-handlers/test/force-expire.ts +167 -167
- package/src/auth/auth-decision.ts +292 -292
- package/src/auth/better-auth.ts +1688 -1688
- package/src/auth/idp-oauth.ts +310 -310
- package/src/auth/password-reset-bridge.ts +98 -98
- package/src/auth/route-config.ts +236 -236
- package/src/auth/safe-callback.ts +199 -199
- package/src/auth/session-cookie-clear.ts +30 -30
- package/src/auth/session-reset-link.ts +159 -159
- package/src/auth/session-response-sanitizer.ts +151 -151
- package/src/auth/types/auth-types.ts +555 -555
- package/src/auth/types/index.ts +7 -7
- package/src/auth/unauthenticated-routes.ts +2 -2
- package/src/auth/utils/idp-client.ts +364 -364
- package/src/auth/utils/index.ts +6 -6
- package/src/auth/utils/token-utils.ts +243 -243
- package/src/client/AuthContext.tsx +192 -192
- package/src/client/better-auth-client.ts +70 -70
- package/src/client/fetch-with-auth.ts +47 -47
- package/src/client/fetchWithSession.ts +21 -21
- package/src/client/index.ts +13 -13
- package/src/client/useAnonSession.ts +131 -131
- package/src/components/SessionSync.tsx +138 -138
- package/src/components/SignalRHealthCheck.tsx +131 -131
- package/src/components/account/MobileNavDrawer.tsx +305 -305
- package/src/components/account/UserAvatarMenu.tsx +277 -277
- package/src/components/account/index.ts +13 -13
- package/src/components/admin/AlertSettingsTab.tsx +728 -728
- package/src/components/admin/AnalyticsTab.tsx +703 -703
- package/src/components/admin/DataBrowserTab.tsx +508 -508
- package/src/components/admin/LoggingSettingsTab.tsx +665 -665
- package/src/components/admin/SessionsTab.tsx +421 -421
- package/src/components/admin/StatsTab.tsx +379 -379
- package/src/components/admin/VibeAdminContext.tsx +87 -87
- package/src/components/admin/VibeAdminLayout.tsx +187 -187
- package/src/components/admin/index.ts +59 -59
- package/src/components/auth/CredentialSignInForm.tsx +240 -240
- package/src/components/auth/FederatedAuthSection.tsx +129 -129
- package/src/components/auth/ModeAwareLoginPage.tsx +146 -146
- package/src/components/auth/ModeAwareSignupPage.tsx +200 -200
- package/src/components/auth/PasswordResetBridge.tsx +30 -30
- package/src/components/auth/TraditionalAuthSection.tsx +103 -103
- package/src/components/recovery/CompleteStep.tsx +36 -36
- package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
- package/src/components/recovery/RequestedStep.tsx +30 -30
- package/src/components/recovery/SelectMethodStep.tsx +73 -73
- package/src/components/recovery/SetPasswordStep.tsx +118 -118
- package/src/components/recovery/VerifyCodeStep.tsx +90 -90
- package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -160
- package/src/components/reserved/ReservedStatusBox.tsx +118 -118
- package/src/components/ui/BetaBadge.tsx +58 -58
- package/src/components/ui/Footer.tsx +93 -93
- package/src/config/env.ts +55 -55
- package/src/config/logger.ts +62 -62
- package/src/config/logging-config.ts +82 -82
- package/src/config/unauthenticated-routes.ts +19 -19
- package/src/config/vibe-log-transport.ts +264 -264
- package/src/edge/internal-api-url.ts +65 -65
- package/src/edge/middleware.ts +42 -42
- package/src/hooks/useAuth.ts +117 -117
- package/src/hooks/useAuthSettings.ts +109 -109
- package/src/hooks/useAvailableProviders.ts +143 -143
- package/src/hooks/usePasswordValidation.ts +127 -127
- package/src/hooks/useProfile.ts +82 -82
- package/src/hooks/usePublicAuthSettings.ts +171 -171
- package/src/hooks/useSessionExpiration.ts +101 -101
- package/src/hooks/useViabilitySession.ts +337 -337
- package/src/index.ts +64 -64
- package/src/lib/anon-session.ts +213 -213
- package/src/lib/api-handler.ts +643 -643
- package/src/lib/app-slug.ts +228 -228
- package/src/lib/authorize-resume.ts +172 -172
- package/src/lib/bearer-fetch.ts +31 -31
- package/src/lib/client-key.ts +88 -88
- package/src/lib/code-step-copy.ts +83 -83
- package/src/lib/credential-sign-in-client.ts +101 -101
- package/src/lib/demo-mode.ts +18 -18
- package/src/lib/device-id.ts +67 -67
- package/src/lib/ensure-fresh-access-token.ts +509 -509
- package/src/lib/geolocation.ts +265 -265
- package/src/lib/idp-client-config.ts +1005 -1005
- package/src/lib/idp-fetch.ts +169 -169
- package/src/lib/idp-url-guard.ts +37 -37
- package/src/lib/internal-api.ts +171 -171
- package/src/lib/jwt-decode-client.ts +45 -45
- package/src/lib/jwt-decode.ts +82 -82
- package/src/lib/lock-window.ts +49 -49
- package/src/lib/login-error-message.ts +41 -41
- package/src/lib/never-in-response.ts +64 -64
- package/src/lib/nextauth-secret.ts +168 -168
- package/src/lib/onboarding-proxy.ts +57 -57
- package/src/lib/password-rules.ts +50 -50
- package/src/lib/rate-limit-service.ts +9 -9
- package/src/lib/redis.ts +105 -105
- package/src/lib/refresh-token-validator.ts +64 -64
- package/src/lib/roles.ts +177 -177
- package/src/lib/safe-error-response.ts +149 -149
- package/src/lib/secret-validation.ts +8 -8
- package/src/lib/session-cookie-shape.ts +185 -185
- package/src/lib/session-store.ts +1092 -1092
- package/src/lib/session.ts +34 -34
- package/src/lib/single-flight.ts +64 -64
- package/src/lib/site-logger.ts +245 -245
- package/src/lib/standardized-client-api.ts +901 -901
- package/src/lib/startup-init.ts +276 -276
- package/src/lib/test-aware-get-token.ts +41 -41
- package/src/lib/token-expiry.ts +40 -40
- package/src/lib/token-lifecycle.ts +546 -546
- package/src/lib/token-log-id.ts +69 -69
- package/src/lib/twofa-rejection.ts +91 -91
- package/src/lib/types/api-responses.ts +336 -336
- package/src/lib/user-agent-parser.ts +252 -252
- package/src/logging/api/admin-analytics.ts +51 -51
- package/src/logging/api/audit-log.ts +53 -53
- package/src/logging/components/AdminAnalyticsLayout.tsx +49 -49
- package/src/logging/components/AuditLogViewer.tsx +125 -125
- package/src/logging/components/ErrorMetricsCard.tsx +98 -98
- package/src/logging/components/HealthMetricsCard.tsx +70 -70
- package/src/logging/hooks/useAdminAnalytics.ts +22 -22
- package/src/logging/hooks/useAuditLog.ts +24 -24
- package/src/logging/hooks/useErrorMetrics.ts +40 -40
- package/src/logging/hooks/useHealthMetrics.ts +44 -44
- package/src/logging/index.ts +18 -18
- package/src/logging/types/analytics.ts +81 -81
- package/src/logging/types/audit.ts +31 -31
- package/src/logging/types/index.ts +3 -3
- package/src/middleware/create-middleware.ts +1139 -1138
- package/src/middleware/rbac-check.ts +279 -279
- package/src/middleware/twofa-presets.ts +224 -224
- package/src/models/DecodedAccessToken.ts +16 -16
- package/src/models/SessionModel.ts +331 -331
- package/src/pages/admin-login/page.tsx +111 -111
- package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -527
- package/src/pages/admin-page-permissions/index.ts +7 -7
- package/src/pages/admin-roles/RolesAdminPage.tsx +533 -533
- package/src/pages/admin-roles/index.ts +9 -9
- package/src/pages/admin-roles/modals.tsx +469 -469
- package/src/pages/client-admin/ClientSiteAdminPage.tsx +382 -382
- package/src/pages/client-admin/index.ts +33 -33
- package/src/pages/coming-soon/page.tsx +83 -83
- package/src/pages/login/page.tsx +338 -338
- package/src/pages/onboarding/index.ts +11 -11
- package/src/pages/onboarding/page.tsx +212 -212
- package/src/pages/profile/EnhancedProfilePage.tsx +514 -514
- package/src/pages/profile/index.ts +9 -9
- package/src/pages/profile/page.tsx +166 -166
- package/src/pages/recovery/page.tsx +267 -267
- package/src/pages/roles/MyRolesPage.tsx +211 -211
- package/src/pages/roles/components.tsx +294 -294
- package/src/pages/roles/index.ts +17 -17
- package/src/pages/security/EnhancedSecurityPage.tsx +660 -660
- package/src/pages/security/index.ts +12 -12
- package/src/pages/security/page.tsx +507 -507
- package/src/pages/settings/EnhancedSettingsPage.tsx +642 -642
- package/src/pages/settings/index.ts +9 -9
- package/src/pages/settings/page.tsx +47 -47
- package/src/pages/showcase/ShowcasePage.tsx +532 -532
- package/src/pages/showcase/index.ts +13 -13
- package/src/pages/test-env/EmergencyLogoutPage.tsx +192 -192
- package/src/pages/test-env/JwtInspectPage.tsx +449 -449
- package/src/pages/test-env/RefreshTokenPage.tsx +178 -178
- package/src/pages/test-env/TestEnvPage.tsx +130 -130
- package/src/pages/test-env/guarded.tsx +81 -81
- package/src/pages/test-env/index.ts +77 -77
- package/src/pages/test-env/redact-credentials.ts +96 -96
- package/src/pages/verify-code/page.tsx +746 -746
- package/src/routes/account/index.ts +31 -31
- package/src/routes/account/masked-info.ts +36 -36
- package/src/routes/account/send-code.ts +39 -39
- package/src/routes/account/update-phone.ts +13 -13
- package/src/routes/account/verify-email.ts +40 -40
- package/src/routes/account/verify-sms.ts +40 -40
- package/src/routes/auth/credential-login-email-code.ts +6 -6
- package/src/routes/auth/credential-login.ts +6 -6
- package/src/routes/auth/index.ts +22 -22
- package/src/routes/auth/logout.ts +111 -111
- package/src/routes/auth/nextauth.ts +45 -45
- package/src/routes/auth/passkey-signin.ts +13 -13
- package/src/routes/auth/refresh.ts +77 -77
- package/src/routes/auth/session-reset.ts +37 -37
- package/src/routes/auth/session.ts +261 -261
- package/src/routes/auth/settings.ts +181 -181
- package/src/routes/auth/viability.ts +519 -519
- package/src/routes/index.ts +17 -17
- package/src/routes/mfa/backup-code-verify.ts +1 -1
- package/src/routes/mfa/backup-codes.ts +10 -10
- package/src/routes/mfa/methods.ts +22 -22
- package/src/routes/mfa/totp-confirm.ts +10 -10
- package/src/routes/mfa/totp-enroll.ts +10 -10
- package/src/routes/mfa/totp-verify.ts +14 -14
- package/src/routes/onboarding/index.ts +15 -15
- package/src/routes/onboarding/send-email-code.ts +2 -2
- package/src/routes/onboarding/set-password.ts +2 -2
- package/src/routes/onboarding/verify-code.ts +2 -2
- package/src/routes/onboarding/verify-welcome-email.ts +2 -2
- package/src/routes/passkeys/list.ts +10 -10
- package/src/routes/passkeys/manage.ts +23 -23
- package/src/routes/passkeys/register-options.ts +10 -10
- package/src/routes/passkeys/register-verify.ts +10 -10
- package/src/routes/session/index.ts +7 -7
- package/src/routes/session/refresh-viability.ts +17 -17
- package/src/server/auth-guard.ts +203 -203
- package/src/server/auth.ts +486 -486
- package/src/server/decode-session.ts +309 -309
- package/src/server/slim-middleware.ts +117 -117
- package/src/server/verified-session-token.ts +103 -103
- package/src/server/with-auth.ts +93 -93
- package/src/services/signalrActivityService.ts +258 -258
- package/src/stores/authStore.ts +1900 -1900
- package/src/templates/instrumentation.ts +60 -60
- package/src/theme/ThemeProvider.tsx +39 -39
- package/src/theme/default.ts +33 -33
- package/src/theme/index.ts +31 -31
- package/src/theme/types.ts +69 -69
- package/src/theme/useTheme.ts +57 -57
- package/src/theme/utils.ts +40 -40
- package/src/types/api.ts +12 -12
- package/src/types/auth.d.ts +23 -23
- package/src/types/auth.ts +48 -48
- package/src/types/logging.ts +11 -11
- package/src/types/recovery.ts +54 -54
- package/src/utils/api.ts +401 -401
- package/src/utils/circuitBreaker.ts +40 -40
- package/src/utils/error-message.ts +108 -108
- package/src/utils/layout/reservedSpace.ts +124 -124
- package/src/utils/logout.ts +30 -30
- package/src/vibe/client.ts +877 -877
- package/src/vibe/enterprise-auth.ts +208 -208
- package/src/vibe/errors.ts +209 -209
- package/src/vibe/generic.ts +339 -339
- package/src/vibe/hooks/index.ts +367 -367
- package/src/vibe/index.ts +133 -133
- package/src/vibe/sessions.ts +925 -899
- package/src/vibe/types.ts +577 -577
|
@@ -1,262 +1,262 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ready-to-Use Session Management Route
|
|
3
|
-
*
|
|
4
|
-
* Provides pre-configured session handlers for checking and updating session state.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* // app/api/auth/session/route.ts
|
|
9
|
-
* export { GET, POST } from '@payez/next-mvp/routes/auth/session';
|
|
10
|
-
* ```
|
|
11
|
-
*
|
|
12
|
-
* @version 2.0.0
|
|
13
|
-
* @since auth-ready-v2
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
17
|
-
import { getSession as getBetterAuthSession } from '../../server/auth';
|
|
18
|
-
import { getSession as getRedisSession, updateSession } from '../../lib/session-store';
|
|
19
|
-
import { logExceptionWithCorrelation } from '../../lib/safe-error-response';
|
|
20
|
-
import { tokenLogId } from '../../lib/token-log-id';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* GET /api/auth/session - Check current session status
|
|
24
|
-
*
|
|
25
|
-
* Returns the current session information including:
|
|
26
|
-
* - User details
|
|
27
|
-
* - Token expiry status
|
|
28
|
-
* - Session validity
|
|
29
|
-
*/
|
|
30
|
-
export async function GET(req: NextRequest) {
|
|
31
|
-
try {
|
|
32
|
-
const authSession = await getBetterAuthSession(req);
|
|
33
|
-
|
|
34
|
-
if (!authSession) {
|
|
35
|
-
console.warn('[SESSION_ROUTE] Better Auth session not found');
|
|
36
|
-
// MUST return empty {} — useSession() treats any non-empty
|
|
37
|
-
// response object as "authenticated", causing redirect loops on login page.
|
|
38
|
-
return NextResponse.json({});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const redisSessionId = authSession.session?.token;
|
|
42
|
-
|
|
43
|
-
console.log('[SESSION_ROUTE] Session found:', {
|
|
44
|
-
userId: authSession.user?.id,
|
|
45
|
-
email: authSession.user?.email,
|
|
46
|
-
name: authSession.user?.name,
|
|
47
|
-
redisSessionId: redisSessionId ? tokenLogId(redisSessionId) : 'MISSING',
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Fetch full session data from Redis
|
|
51
|
-
const session = redisSessionId ? await getRedisSession(redisSessionId) : null;
|
|
52
|
-
|
|
53
|
-
console.log('[SESSION_ROUTE] Redis session:', {
|
|
54
|
-
found: !!session,
|
|
55
|
-
userId: session?.userId,
|
|
56
|
-
roles: session?.roles,
|
|
57
|
-
hasAccessToken: !!session?.idpAccessToken,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// A Better Auth cookie is NOT authorization. The Redis session is the
|
|
61
|
-
// authoritative record of what this user is allowed to do; without it we
|
|
62
|
-
// have no roles and no IDP access token, so there is nothing to authorize
|
|
63
|
-
// against. Returning a populated user here produces the worst failure mode
|
|
64
|
-
// available: a client that renders as signed-in and is not.
|
|
65
|
-
//
|
|
66
|
-
// MUST return empty {} for the same reason as line 34 — useSession() treats
|
|
67
|
-
// ANY non-empty response object as "authenticated".
|
|
68
|
-
//
|
|
69
|
-
// NOTE: an MFA-pending session IS present in Redis (it just has
|
|
70
|
-
// mfaVerified=false), so this gate does not break /verify-code. Gating on a
|
|
71
|
-
// missing accessToken instead WOULD break it.
|
|
72
|
-
if (!session) {
|
|
73
|
-
console.error('[SESSION_ROUTE] Redis session MISS — refusing to report authenticated', {
|
|
74
|
-
userId: authSession.user?.id,
|
|
75
|
-
redisSessionId: redisSessionId ? tokenLogId(redisSessionId) : 'MISSING',
|
|
76
|
-
reason: redisSessionId ? 'session expired or evicted' : 'no session token on auth session',
|
|
77
|
-
});
|
|
78
|
-
return NextResponse.json({});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Return session format with Redis data
|
|
82
|
-
// useSession() expects: { user: {...}, expires: "..." }
|
|
83
|
-
// We enrich with all session data from Redis
|
|
84
|
-
return NextResponse.json({
|
|
85
|
-
user: {
|
|
86
|
-
id: session?.userId || authSession.user?.id,
|
|
87
|
-
email: session?.email || authSession.user?.email,
|
|
88
|
-
name: session?.name || authSession.user?.name,
|
|
89
|
-
image: authSession.user?.image || session?.image || null,
|
|
90
|
-
// Redis session data
|
|
91
|
-
roles: session?.roles || [],
|
|
92
|
-
twoFactorSessionVerified: session?.mfaVerified || false,
|
|
93
|
-
requiresTwoFactor: !session?.mfaVerified,
|
|
94
|
-
authenticationMethods: session?.authenticationMethods,
|
|
95
|
-
authenticationLevel: session?.authenticationLevel,
|
|
96
|
-
mfaCompletedAt: session?.mfaCompletedAt,
|
|
97
|
-
mfaExpiresAt: session?.mfaExpiresAt,
|
|
98
|
-
mfaValidityHours: session?.mfaValidityHours,
|
|
99
|
-
oauthProvider: session?.oauthProvider,
|
|
100
|
-
idpClientId: session?.idpClientId,
|
|
101
|
-
merchantId: session?.merchantId,
|
|
102
|
-
},
|
|
103
|
-
// PAY-1481 item 1: NO token material. This body is read by browser JS, so returning the session token and the
|
|
104
|
-
// IdP access/refresh tokens handed them to any script on the page (the refresh token outlives the session).
|
|
105
|
-
// Server routes read tokens from the session store; the UI only needs presence and expiry.
|
|
106
|
-
hasAccessToken: !!session?.idpAccessToken,
|
|
107
|
-
hasRefreshToken: !!session?.idpRefreshToken,
|
|
108
|
-
accessTokenExpires: session?.idpAccessTokenExpires,
|
|
109
|
-
expires: authSession.session?.expiresAt
|
|
110
|
-
? new Date(authSession.session.expiresAt).toISOString()
|
|
111
|
-
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
112
|
-
});
|
|
113
|
-
} catch (error) {
|
|
114
|
-
// PAY-1438: pre-auth route - the exception text stays in the log, keyed by correlationId.
|
|
115
|
-
const correlationId = logExceptionWithCorrelation('[SESSION_ROUTE] Error checking session:', error);
|
|
116
|
-
return NextResponse.json({
|
|
117
|
-
error: 'Failed to check session',
|
|
118
|
-
code: 'INTERNAL_ERROR',
|
|
119
|
-
correlationId,
|
|
120
|
-
}, { status: 500 });
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* The fields this route USED to accept from the request body and must never accept
|
|
126
|
-
* again. Cards 308833 (critical) / 312092 (remediation spec) / 318272 / 311425.
|
|
127
|
-
*
|
|
128
|
-
* `twoFactorComplete` was written straight through to `mfaVerified`, so a caller
|
|
129
|
-
* holding any session supplied its OWN second-factor verdict - no factor presented,
|
|
130
|
-
* no comparison against `amr`, no server-side derivation. The caller can be PRE-2FA,
|
|
131
|
-
* which makes this a 2FA bypass rather than a user rewriting a flag he had earned.
|
|
132
|
-
*
|
|
133
|
-
* The package already states the invariant in four places, in its own voice:
|
|
134
|
-
* api-handlers/mfa/totp-verify.ts - "Enrolment must never set mfaVerified."
|
|
135
|
-
* api-handlers/auth/refresh.ts - "mfaVerified is derived from amrClaims"
|
|
136
|
-
* auth/better-auth.ts - "computed fresh ... re-derived fresh"
|
|
137
|
-
* (consumer) passkey-signin-plugin - "mfaVerified is DERIVED from amr, never asserted."
|
|
138
|
-
* Every other writer in the estate derives it. This one route took it from an HTTP body.
|
|
139
|
-
*
|
|
140
|
-
* PRESENCE, NOT TRUTHINESS, is what is refused - `twoFactorComplete: false` is rejected
|
|
141
|
-
* exactly like `true`. A caller must not be able to drive this field in EITHER direction,
|
|
142
|
-
* and per R486/R908 an absent signal is "cannot determine", never a negative assertion.
|
|
143
|
-
*
|
|
144
|
-
* REFUSED LOUDLY RATHER THAN IGNORED SILENTLY. Dropping the field would leave a caller
|
|
145
|
-
* believing it had recorded a 2FA completion that never happened - the same failure shape
|
|
146
|
-
* as the 4.1.14 -> 4.1.31 history below, where a write nobody read looked like it worked.
|
|
147
|
-
*
|
|
148
|
-
* *** DO NOT "FIX" A REGRESSION HERE BY REVERTING THE KEY NORMALIZATION (312092 s2). ***
|
|
149
|
-
* 4.1.14 had this same unvalidated write but stored bare `accessToken`/`twoFactorComplete`,
|
|
150
|
-
* keys no reader consulted, so the forgery landed where nothing looked. The 4.1.31
|
|
151
|
-
* normalization did not introduce the bug - IT ARMED IT. Reverting re-hides the hole and
|
|
152
|
-
* restores two real client-facing bugs. The write is authorized here instead.
|
|
153
|
-
*/
|
|
154
|
-
const CALLER_ASSERTED_FIELDS: ReadonlyArray<{ field: string; why: string }> = [
|
|
155
|
-
{ field: 'twoFactorComplete', why: 'the second-factor verdict is derived from the IDP token (amr), never asserted by the caller' },
|
|
156
|
-
{ field: 'mfaVerified', why: 'the second-factor verdict is derived from the IDP token (amr), never asserted by the caller' },
|
|
157
|
-
{ field: 'twoFactorMethod', why: 'the method is recorded by the server-side verification path that observed the factor' },
|
|
158
|
-
{ field: 'access_token', why: 'tokens are issued by the IDP; use POST /api/auth/refresh' },
|
|
159
|
-
{ field: 'refresh_token', why: 'tokens are issued by the IDP; use POST /api/auth/refresh' },
|
|
160
|
-
{ field: 'idpAccessToken', why: 'tokens are issued by the IDP; use POST /api/auth/refresh' },
|
|
161
|
-
{ field: 'idpRefreshToken', why: 'tokens are issued by the IDP; use POST /api/auth/refresh' },
|
|
162
|
-
];
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* POST /api/auth/session - Update session METADATA
|
|
166
|
-
*
|
|
167
|
-
* Updates session metadata only. This route holds no authority over authentication
|
|
168
|
-
* state: it cannot set the 2FA verdict and it cannot set tokens.
|
|
169
|
-
*
|
|
170
|
-
* Token refresh uses POST /api/auth/refresh. The 2FA verdict is written only by the
|
|
171
|
-
* server-side verification path that actually observed the factor.
|
|
172
|
-
*
|
|
173
|
-
* Body:
|
|
174
|
-
* - metadata: object - Custom metadata to store in session
|
|
175
|
-
*
|
|
176
|
-
* Any of CALLER_ASSERTED_FIELDS present in the body is a 400. The docstring above used
|
|
177
|
-
* to promise "metadata (not tokens)" while the handler wrote idpAccessToken,
|
|
178
|
-
* idpRefreshToken AND mfaVerified - that false parenthetical is the ONLY caution a
|
|
179
|
-
* mounter reads, and it is how this route came to be mounted (318272). The promise is
|
|
180
|
-
* now enforced by the code rather than asserted by the comment.
|
|
181
|
-
*/
|
|
182
|
-
export async function POST(req: NextRequest) {
|
|
183
|
-
try {
|
|
184
|
-
const authSession = await getBetterAuthSession(req);
|
|
185
|
-
|
|
186
|
-
if (!authSession) {
|
|
187
|
-
return NextResponse.json({
|
|
188
|
-
error: 'No session found',
|
|
189
|
-
code: 'UNAUTHORIZED'
|
|
190
|
-
}, { status: 401 });
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const sessionToken = authSession.session?.token;
|
|
194
|
-
if (!sessionToken) {
|
|
195
|
-
return NextResponse.json({
|
|
196
|
-
error: 'Invalid session',
|
|
197
|
-
code: 'INVALID_SESSION'
|
|
198
|
-
}, { status: 400 });
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const body = await req.json();
|
|
202
|
-
|
|
203
|
-
// Refuse before reading the session: a caller must not learn whether its forged
|
|
204
|
-
// field would have landed, and nothing should be written on a rejected request.
|
|
205
|
-
const asserted = CALLER_ASSERTED_FIELDS.filter(
|
|
206
|
-
({ field }) => body != null && Object.prototype.hasOwnProperty.call(body, field)
|
|
207
|
-
);
|
|
208
|
-
if (asserted.length > 0) {
|
|
209
|
-
console.warn('[SESSION_ROUTE] REFUSED caller-asserted authentication state', {
|
|
210
|
-
fields: asserted.map(({ field }) => field),
|
|
211
|
-
userId: authSession.user?.id,
|
|
212
|
-
});
|
|
213
|
-
return NextResponse.json({
|
|
214
|
-
error: 'This route updates session metadata only. It cannot set authentication state.',
|
|
215
|
-
code: 'CALLER_ASSERTED_AUTH_STATE',
|
|
216
|
-
fields: asserted.map(({ field, why }) => ({ field, why })),
|
|
217
|
-
}, { status: 400 });
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const { metadata } = body;
|
|
221
|
-
|
|
222
|
-
// Get current session from Redis
|
|
223
|
-
const session = await getRedisSession(sessionToken);
|
|
224
|
-
if (!session) {
|
|
225
|
-
return NextResponse.json({
|
|
226
|
-
error: 'Session not found',
|
|
227
|
-
code: 'SESSION_NOT_FOUND'
|
|
228
|
-
}, { status: 404 });
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Update session with new data.
|
|
232
|
-
//
|
|
233
|
-
// METADATA ONLY. The token and 2FA-verdict writes that used to live here were
|
|
234
|
-
// removed, not merely guarded: see CALLER_ASSERTED_FIELDS above. Anything that
|
|
235
|
-
// needs to write authentication state must do so from a path that OBSERVED the
|
|
236
|
-
// fact - the IDP exchange for tokens, the verification path for the 2FA verdict.
|
|
237
|
-
const updatedSession = {
|
|
238
|
-
...session,
|
|
239
|
-
...(metadata ? {
|
|
240
|
-
metadata: {
|
|
241
|
-
...(session.metadata || {}),
|
|
242
|
-
...metadata,
|
|
243
|
-
updatedAt: new Date().toISOString()
|
|
244
|
-
}
|
|
245
|
-
} : {})
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
await updateSession(sessionToken, updatedSession);
|
|
249
|
-
|
|
250
|
-
return NextResponse.json({
|
|
251
|
-
success: true,
|
|
252
|
-
message: 'Session updated successfully'
|
|
253
|
-
});
|
|
254
|
-
} catch (error) {
|
|
255
|
-
const correlationId = logExceptionWithCorrelation('[SESSION_ROUTE] Error updating session:', error);
|
|
256
|
-
return NextResponse.json({
|
|
257
|
-
error: 'Failed to update session',
|
|
258
|
-
code: 'INTERNAL_ERROR',
|
|
259
|
-
correlationId,
|
|
260
|
-
}, { status: 500 });
|
|
261
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Ready-to-Use Session Management Route
|
|
3
|
+
*
|
|
4
|
+
* Provides pre-configured session handlers for checking and updating session state.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // app/api/auth/session/route.ts
|
|
9
|
+
* export { GET, POST } from '@payez/next-mvp/routes/auth/session';
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* @version 2.0.0
|
|
13
|
+
* @since auth-ready-v2
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
17
|
+
import { getSession as getBetterAuthSession } from '../../server/auth';
|
|
18
|
+
import { getSession as getRedisSession, updateSession } from '../../lib/session-store';
|
|
19
|
+
import { logExceptionWithCorrelation } from '../../lib/safe-error-response';
|
|
20
|
+
import { tokenLogId } from '../../lib/token-log-id';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* GET /api/auth/session - Check current session status
|
|
24
|
+
*
|
|
25
|
+
* Returns the current session information including:
|
|
26
|
+
* - User details
|
|
27
|
+
* - Token expiry status
|
|
28
|
+
* - Session validity
|
|
29
|
+
*/
|
|
30
|
+
export async function GET(req: NextRequest) {
|
|
31
|
+
try {
|
|
32
|
+
const authSession = await getBetterAuthSession(req);
|
|
33
|
+
|
|
34
|
+
if (!authSession) {
|
|
35
|
+
console.warn('[SESSION_ROUTE] Better Auth session not found');
|
|
36
|
+
// MUST return empty {} — useSession() treats any non-empty
|
|
37
|
+
// response object as "authenticated", causing redirect loops on login page.
|
|
38
|
+
return NextResponse.json({});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const redisSessionId = authSession.session?.token;
|
|
42
|
+
|
|
43
|
+
console.log('[SESSION_ROUTE] Session found:', {
|
|
44
|
+
userId: authSession.user?.id,
|
|
45
|
+
email: authSession.user?.email,
|
|
46
|
+
name: authSession.user?.name,
|
|
47
|
+
redisSessionId: redisSessionId ? tokenLogId(redisSessionId) : 'MISSING',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Fetch full session data from Redis
|
|
51
|
+
const session = redisSessionId ? await getRedisSession(redisSessionId) : null;
|
|
52
|
+
|
|
53
|
+
console.log('[SESSION_ROUTE] Redis session:', {
|
|
54
|
+
found: !!session,
|
|
55
|
+
userId: session?.userId,
|
|
56
|
+
roles: session?.roles,
|
|
57
|
+
hasAccessToken: !!session?.idpAccessToken,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// A Better Auth cookie is NOT authorization. The Redis session is the
|
|
61
|
+
// authoritative record of what this user is allowed to do; without it we
|
|
62
|
+
// have no roles and no IDP access token, so there is nothing to authorize
|
|
63
|
+
// against. Returning a populated user here produces the worst failure mode
|
|
64
|
+
// available: a client that renders as signed-in and is not.
|
|
65
|
+
//
|
|
66
|
+
// MUST return empty {} for the same reason as line 34 — useSession() treats
|
|
67
|
+
// ANY non-empty response object as "authenticated".
|
|
68
|
+
//
|
|
69
|
+
// NOTE: an MFA-pending session IS present in Redis (it just has
|
|
70
|
+
// mfaVerified=false), so this gate does not break /verify-code. Gating on a
|
|
71
|
+
// missing accessToken instead WOULD break it.
|
|
72
|
+
if (!session) {
|
|
73
|
+
console.error('[SESSION_ROUTE] Redis session MISS — refusing to report authenticated', {
|
|
74
|
+
userId: authSession.user?.id,
|
|
75
|
+
redisSessionId: redisSessionId ? tokenLogId(redisSessionId) : 'MISSING',
|
|
76
|
+
reason: redisSessionId ? 'session expired or evicted' : 'no session token on auth session',
|
|
77
|
+
});
|
|
78
|
+
return NextResponse.json({});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Return session format with Redis data
|
|
82
|
+
// useSession() expects: { user: {...}, expires: "..." }
|
|
83
|
+
// We enrich with all session data from Redis
|
|
84
|
+
return NextResponse.json({
|
|
85
|
+
user: {
|
|
86
|
+
id: session?.userId || authSession.user?.id,
|
|
87
|
+
email: session?.email || authSession.user?.email,
|
|
88
|
+
name: session?.name || authSession.user?.name,
|
|
89
|
+
image: authSession.user?.image || session?.image || null,
|
|
90
|
+
// Redis session data
|
|
91
|
+
roles: session?.roles || [],
|
|
92
|
+
twoFactorSessionVerified: session?.mfaVerified || false,
|
|
93
|
+
requiresTwoFactor: !session?.mfaVerified,
|
|
94
|
+
authenticationMethods: session?.authenticationMethods,
|
|
95
|
+
authenticationLevel: session?.authenticationLevel,
|
|
96
|
+
mfaCompletedAt: session?.mfaCompletedAt,
|
|
97
|
+
mfaExpiresAt: session?.mfaExpiresAt,
|
|
98
|
+
mfaValidityHours: session?.mfaValidityHours,
|
|
99
|
+
oauthProvider: session?.oauthProvider,
|
|
100
|
+
idpClientId: session?.idpClientId,
|
|
101
|
+
merchantId: session?.merchantId,
|
|
102
|
+
},
|
|
103
|
+
// PAY-1481 item 1: NO token material. This body is read by browser JS, so returning the session token and the
|
|
104
|
+
// IdP access/refresh tokens handed them to any script on the page (the refresh token outlives the session).
|
|
105
|
+
// Server routes read tokens from the session store; the UI only needs presence and expiry.
|
|
106
|
+
hasAccessToken: !!session?.idpAccessToken,
|
|
107
|
+
hasRefreshToken: !!session?.idpRefreshToken,
|
|
108
|
+
accessTokenExpires: session?.idpAccessTokenExpires,
|
|
109
|
+
expires: authSession.session?.expiresAt
|
|
110
|
+
? new Date(authSession.session.expiresAt).toISOString()
|
|
111
|
+
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// PAY-1438: pre-auth route - the exception text stays in the log, keyed by correlationId.
|
|
115
|
+
const correlationId = logExceptionWithCorrelation('[SESSION_ROUTE] Error checking session:', error);
|
|
116
|
+
return NextResponse.json({
|
|
117
|
+
error: 'Failed to check session',
|
|
118
|
+
code: 'INTERNAL_ERROR',
|
|
119
|
+
correlationId,
|
|
120
|
+
}, { status: 500 });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The fields this route USED to accept from the request body and must never accept
|
|
126
|
+
* again. Cards 308833 (critical) / 312092 (remediation spec) / 318272 / 311425.
|
|
127
|
+
*
|
|
128
|
+
* `twoFactorComplete` was written straight through to `mfaVerified`, so a caller
|
|
129
|
+
* holding any session supplied its OWN second-factor verdict - no factor presented,
|
|
130
|
+
* no comparison against `amr`, no server-side derivation. The caller can be PRE-2FA,
|
|
131
|
+
* which makes this a 2FA bypass rather than a user rewriting a flag he had earned.
|
|
132
|
+
*
|
|
133
|
+
* The package already states the invariant in four places, in its own voice:
|
|
134
|
+
* api-handlers/mfa/totp-verify.ts - "Enrolment must never set mfaVerified."
|
|
135
|
+
* api-handlers/auth/refresh.ts - "mfaVerified is derived from amrClaims"
|
|
136
|
+
* auth/better-auth.ts - "computed fresh ... re-derived fresh"
|
|
137
|
+
* (consumer) passkey-signin-plugin - "mfaVerified is DERIVED from amr, never asserted."
|
|
138
|
+
* Every other writer in the estate derives it. This one route took it from an HTTP body.
|
|
139
|
+
*
|
|
140
|
+
* PRESENCE, NOT TRUTHINESS, is what is refused - `twoFactorComplete: false` is rejected
|
|
141
|
+
* exactly like `true`. A caller must not be able to drive this field in EITHER direction,
|
|
142
|
+
* and per R486/R908 an absent signal is "cannot determine", never a negative assertion.
|
|
143
|
+
*
|
|
144
|
+
* REFUSED LOUDLY RATHER THAN IGNORED SILENTLY. Dropping the field would leave a caller
|
|
145
|
+
* believing it had recorded a 2FA completion that never happened - the same failure shape
|
|
146
|
+
* as the 4.1.14 -> 4.1.31 history below, where a write nobody read looked like it worked.
|
|
147
|
+
*
|
|
148
|
+
* *** DO NOT "FIX" A REGRESSION HERE BY REVERTING THE KEY NORMALIZATION (312092 s2). ***
|
|
149
|
+
* 4.1.14 had this same unvalidated write but stored bare `accessToken`/`twoFactorComplete`,
|
|
150
|
+
* keys no reader consulted, so the forgery landed where nothing looked. The 4.1.31
|
|
151
|
+
* normalization did not introduce the bug - IT ARMED IT. Reverting re-hides the hole and
|
|
152
|
+
* restores two real client-facing bugs. The write is authorized here instead.
|
|
153
|
+
*/
|
|
154
|
+
const CALLER_ASSERTED_FIELDS: ReadonlyArray<{ field: string; why: string }> = [
|
|
155
|
+
{ field: 'twoFactorComplete', why: 'the second-factor verdict is derived from the IDP token (amr), never asserted by the caller' },
|
|
156
|
+
{ field: 'mfaVerified', why: 'the second-factor verdict is derived from the IDP token (amr), never asserted by the caller' },
|
|
157
|
+
{ field: 'twoFactorMethod', why: 'the method is recorded by the server-side verification path that observed the factor' },
|
|
158
|
+
{ field: 'access_token', why: 'tokens are issued by the IDP; use POST /api/auth/refresh' },
|
|
159
|
+
{ field: 'refresh_token', why: 'tokens are issued by the IDP; use POST /api/auth/refresh' },
|
|
160
|
+
{ field: 'idpAccessToken', why: 'tokens are issued by the IDP; use POST /api/auth/refresh' },
|
|
161
|
+
{ field: 'idpRefreshToken', why: 'tokens are issued by the IDP; use POST /api/auth/refresh' },
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* POST /api/auth/session - Update session METADATA
|
|
166
|
+
*
|
|
167
|
+
* Updates session metadata only. This route holds no authority over authentication
|
|
168
|
+
* state: it cannot set the 2FA verdict and it cannot set tokens.
|
|
169
|
+
*
|
|
170
|
+
* Token refresh uses POST /api/auth/refresh. The 2FA verdict is written only by the
|
|
171
|
+
* server-side verification path that actually observed the factor.
|
|
172
|
+
*
|
|
173
|
+
* Body:
|
|
174
|
+
* - metadata: object - Custom metadata to store in session
|
|
175
|
+
*
|
|
176
|
+
* Any of CALLER_ASSERTED_FIELDS present in the body is a 400. The docstring above used
|
|
177
|
+
* to promise "metadata (not tokens)" while the handler wrote idpAccessToken,
|
|
178
|
+
* idpRefreshToken AND mfaVerified - that false parenthetical is the ONLY caution a
|
|
179
|
+
* mounter reads, and it is how this route came to be mounted (318272). The promise is
|
|
180
|
+
* now enforced by the code rather than asserted by the comment.
|
|
181
|
+
*/
|
|
182
|
+
export async function POST(req: NextRequest) {
|
|
183
|
+
try {
|
|
184
|
+
const authSession = await getBetterAuthSession(req);
|
|
185
|
+
|
|
186
|
+
if (!authSession) {
|
|
187
|
+
return NextResponse.json({
|
|
188
|
+
error: 'No session found',
|
|
189
|
+
code: 'UNAUTHORIZED'
|
|
190
|
+
}, { status: 401 });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const sessionToken = authSession.session?.token;
|
|
194
|
+
if (!sessionToken) {
|
|
195
|
+
return NextResponse.json({
|
|
196
|
+
error: 'Invalid session',
|
|
197
|
+
code: 'INVALID_SESSION'
|
|
198
|
+
}, { status: 400 });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const body = await req.json();
|
|
202
|
+
|
|
203
|
+
// Refuse before reading the session: a caller must not learn whether its forged
|
|
204
|
+
// field would have landed, and nothing should be written on a rejected request.
|
|
205
|
+
const asserted = CALLER_ASSERTED_FIELDS.filter(
|
|
206
|
+
({ field }) => body != null && Object.prototype.hasOwnProperty.call(body, field)
|
|
207
|
+
);
|
|
208
|
+
if (asserted.length > 0) {
|
|
209
|
+
console.warn('[SESSION_ROUTE] REFUSED caller-asserted authentication state', {
|
|
210
|
+
fields: asserted.map(({ field }) => field),
|
|
211
|
+
userId: authSession.user?.id,
|
|
212
|
+
});
|
|
213
|
+
return NextResponse.json({
|
|
214
|
+
error: 'This route updates session metadata only. It cannot set authentication state.',
|
|
215
|
+
code: 'CALLER_ASSERTED_AUTH_STATE',
|
|
216
|
+
fields: asserted.map(({ field, why }) => ({ field, why })),
|
|
217
|
+
}, { status: 400 });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const { metadata } = body;
|
|
221
|
+
|
|
222
|
+
// Get current session from Redis
|
|
223
|
+
const session = await getRedisSession(sessionToken);
|
|
224
|
+
if (!session) {
|
|
225
|
+
return NextResponse.json({
|
|
226
|
+
error: 'Session not found',
|
|
227
|
+
code: 'SESSION_NOT_FOUND'
|
|
228
|
+
}, { status: 404 });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Update session with new data.
|
|
232
|
+
//
|
|
233
|
+
// METADATA ONLY. The token and 2FA-verdict writes that used to live here were
|
|
234
|
+
// removed, not merely guarded: see CALLER_ASSERTED_FIELDS above. Anything that
|
|
235
|
+
// needs to write authentication state must do so from a path that OBSERVED the
|
|
236
|
+
// fact - the IDP exchange for tokens, the verification path for the 2FA verdict.
|
|
237
|
+
const updatedSession = {
|
|
238
|
+
...session,
|
|
239
|
+
...(metadata ? {
|
|
240
|
+
metadata: {
|
|
241
|
+
...(session.metadata || {}),
|
|
242
|
+
...metadata,
|
|
243
|
+
updatedAt: new Date().toISOString()
|
|
244
|
+
}
|
|
245
|
+
} : {})
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
await updateSession(sessionToken, updatedSession);
|
|
249
|
+
|
|
250
|
+
return NextResponse.json({
|
|
251
|
+
success: true,
|
|
252
|
+
message: 'Session updated successfully'
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const correlationId = logExceptionWithCorrelation('[SESSION_ROUTE] Error updating session:', error);
|
|
256
|
+
return NextResponse.json({
|
|
257
|
+
error: 'Failed to update session',
|
|
258
|
+
code: 'INTERNAL_ERROR',
|
|
259
|
+
correlationId,
|
|
260
|
+
}, { status: 500 });
|
|
261
|
+
}
|
|
262
262
|
}
|