@payez/next-mvp 4.8.1 → 4.8.3
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/api-handlers/admin/audit.js +13 -21
- package/dist/api-handlers/admin/sessions.js +59 -45
- package/dist/api-handlers/admin/stats.js +1 -1
- package/dist/api-handlers/admin/users.js +15 -23
- package/dist/api-handlers/admin/vibe-data.d.ts +5 -0
- package/dist/api-handlers/admin/vibe-data.js +5 -0
- 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/pages/login/page.js +28 -28
- package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
- package/dist/vibe/client.d.ts +1 -1
- package/dist/vibe/client.js +2 -1
- package/dist/vibe/filters.d.ts +36 -0
- package/dist/vibe/filters.js +47 -0
- package/dist/vibe/generic.js +2 -1
- 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 +223 -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 +341 -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 +240 -251
- package/src/api-handlers/admin/vibe-data.ts +338 -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/revocation-fields.ts +16 -16
- 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 +1141 -1141
- 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/service-unavailable/page.tsx +63 -63
- 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 +878 -877
- package/src/vibe/enterprise-auth.ts +208 -208
- package/src/vibe/errors.ts +209 -209
- package/src/vibe/filters.ts +52 -0
- package/src/vibe/generic.ts +340 -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,199 +1,199 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ONE definition of "is this callback target safe to navigate to", for the
|
|
3
|
-
* whole package (375656).
|
|
4
|
-
*
|
|
5
|
-
* *** THIS DELEGATES PARSING TO THE URL PARSER. DO NOT REPLACE IT WITH
|
|
6
|
-
* *** startsWith() CHECKS. THAT APPROACH FAILED FOUR TIMES.
|
|
7
|
-
*
|
|
8
|
-
* WHY THIS FILE EXISTS: `@payez/next-mvp` shipped THREE bodies named
|
|
9
|
-
* `makeAuthDecision`, each carrying its own `callbackUrl.startsWith('/account-auth/')`
|
|
10
|
-
* test. That test is a REDIRECT-LOOP guard sitting in the seat where an ORIGIN
|
|
11
|
-
* check belongs - its own comment says "prevents infinite loop" - so
|
|
12
|
-
* `https://evil.example.com` does not start with `/account-auth/`, passes
|
|
13
|
-
* through untouched, and becomes the redirect target. The exported copy
|
|
14
|
-
* (`auth/auth-decision.ts`, re-exported from `index.ts` and `edge/middleware.ts`)
|
|
15
|
-
* is public API, so that is a published open redirect independent of who calls it.
|
|
16
|
-
*
|
|
17
|
-
* HISTORY, because it is the whole argument for the current shape (267097 /
|
|
18
|
-
* 267174 / 267448), carried over verbatim from the portal's `src/lib/safe-callback.ts`
|
|
19
|
-
* where this guard was proven (0 leaks / 21 hostile inputs):
|
|
20
|
-
*
|
|
21
|
-
* v1 `startsWith('/') && !startsWith('//')` -> bypassed by /BACKSLASH host
|
|
22
|
-
* v2 + `!startsWith('/' + BACKSLASH)` -> bypassed by /TAB/host
|
|
23
|
-
* ... and /LF/host, /CR/host
|
|
24
|
-
* v3 resolve once + compare ORIGIN -> bypassed by /..//host
|
|
25
|
-
* and /.//host, /%2e%2e//host
|
|
26
|
-
* v4 + require the RESULT to be a FIXED POINT <- current
|
|
27
|
-
*
|
|
28
|
-
* *** DRIFT, 2026-09-11 (325543). THIS FILE WAS v3 WHILE THE PORTAL'S COPY WAS
|
|
29
|
-
* *** v4, AND THE PACKAGE COPY IS THE ONE THAT SHIPPED. The portal fixed its own
|
|
30
|
-
* *** src/lib/safe-callback.ts (e1f08fea) and deployed; the served bundle STILL
|
|
31
|
-
* *** escaped 5/5 in a real browser, because `src/contexts/ThemeContext.tsx`
|
|
32
|
-
* *** imports the BARE BARREL `@payez/next-mvp`, which pulls dist/index.js ->
|
|
33
|
-
* *** auth/auth-decision -> THIS FILE into the client graph, and because the
|
|
34
|
-
* *** portal's `src/middleware.ts` imports `@payez/next-mvp/edge`, which requires
|
|
35
|
-
* *** THIS FILE server-side on every request. It was read as a stale build
|
|
36
|
-
* *** artifact of the portal's file for most of a day. It was not. It was a
|
|
37
|
-
* *** SECOND DEFINITION, and no rebuild of the portal could have removed it.
|
|
38
|
-
* *** The header below says "ONE definition ... for the whole package" and that
|
|
39
|
-
* *** was true INSIDE this package boundary the entire time. The defect lived in
|
|
40
|
-
* *** the gap BETWEEN two boundaries that each enforced one-definition correctly.
|
|
41
|
-
* *** A guard that exists in N copies is only as strong as its weakest copy, and
|
|
42
|
-
* *** a per-repo grep gate cannot see N. IF YOU CHANGE THE PREDICATE HERE, CHANGE
|
|
43
|
-
* *** IT IN THE PORTAL'S src/lib/safe-callback.ts IN THE SAME HOUR, OR DELETE ONE
|
|
44
|
-
* *** OF THEM.
|
|
45
|
-
*
|
|
46
|
-
* Both bypasses work the same way: the browser NORMALISES the string AFTER the
|
|
47
|
-
* guard has finished looking at it. The WHATWG URL parser converts backslashes
|
|
48
|
-
* to forward slashes and STRIPS all ASCII tab, LF and CR before resolving. So a
|
|
49
|
-
* prefix check can always be defeated by a character that is not there yet when
|
|
50
|
-
* it runs. Each fix bought one character. The list was never provably complete.
|
|
51
|
-
*
|
|
52
|
-
* SO: stop re-implementing the parser and ask it instead. Resolve the input
|
|
53
|
-
* against a synthetic base and compare ORIGINS. Anything that escapes the
|
|
54
|
-
* origin - however it is spelled, including spellings nobody has found yet -
|
|
55
|
-
* fails one comparison. The returned value is the parser's own NORMALISED
|
|
56
|
-
* output, so an accepted path cannot smuggle a control character onward either.
|
|
57
|
-
*
|
|
58
|
-
* A synthetic base is used deliberately so this works identically in a server
|
|
59
|
-
* component (no window), on the client, and in the edge runtime.
|
|
60
|
-
*/
|
|
61
|
-
|
|
62
|
-
/** Where we send anyone whose requested target we will not honour. */
|
|
63
|
-
export const DEFAULT_CALLBACK = '/';
|
|
64
|
-
|
|
65
|
-
/** The auth surface. A callback pointing back into it causes a redirect cycle. */
|
|
66
|
-
const AUTH_PREFIX = '/account-auth/';
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* PAY-2315 (4.7.1): the middleware now treats the public /login page like /account-auth/login (a signed-in user is sent
|
|
70
|
-
* on), so a callback or a resolved path that is /login itself must be refused here too - otherwise /login with no
|
|
71
|
-
* callbackUrl would resolve its own pathname as the destination and redirect to itself forever.
|
|
72
|
-
*/
|
|
73
|
-
function isAuthSurface(pathname: string): boolean {
|
|
74
|
-
return pathname.startsWith(AUTH_PREFIX) || pathname === '/login' || pathname.startsWith('/login/');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Synthetic, unreachable base. Only ever used as the resolution origin for the
|
|
79
|
-
* comparison below - never emitted, never navigated to. `.invalid` is reserved
|
|
80
|
-
* by RFC 2606 and can never resolve.
|
|
81
|
-
*/
|
|
82
|
-
const BASE = 'http://safe-callback.invalid';
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* The value to navigate to: a same-origin relative path, NORMALISED by the URL
|
|
86
|
-
* parser, or `fallback` for anything that escapes the origin or loops back into
|
|
87
|
-
* the auth surface. Never throws.
|
|
88
|
-
*/
|
|
89
|
-
export function safeCallbackPath(raw: unknown, fallback: string = DEFAULT_CALLBACK): string {
|
|
90
|
-
if (typeof raw !== 'string' || raw.length === 0) return fallback;
|
|
91
|
-
|
|
92
|
-
let url: URL;
|
|
93
|
-
try {
|
|
94
|
-
url = new URL(raw, BASE);
|
|
95
|
-
} catch {
|
|
96
|
-
return fallback;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Anything that resolved to a different origin escaped, however it was spelled:
|
|
100
|
-
// absolute, protocol-relative //host, backslash /\host, tab/LF/CR variants, or
|
|
101
|
-
// a scheme like javascript: (whose origin is opaque and never equals BASE).
|
|
102
|
-
if (url.origin !== BASE) return fallback;
|
|
103
|
-
|
|
104
|
-
// Loopbacks into the auth surface cause redirect cycles.
|
|
105
|
-
if (isAuthSurface(url.pathname)) return fallback;
|
|
106
|
-
|
|
107
|
-
// PAY-2218: `_rsc` is Next's flight cache-buster. Once skipProxyUrlNormalize lets it reach middleware it leaks
|
|
108
|
-
// into return targets (`callbackUrl=/overview?_rsc=abc12`, NightHawk 72013); it is never a meaningful part of a
|
|
109
|
-
// destination. Only touched when present, so every other query keeps its exact spelling.
|
|
110
|
-
if (url.searchParams.has('_rsc')) url.searchParams.delete('_rsc');
|
|
111
|
-
|
|
112
|
-
// The parser's normalised output - control characters already stripped.
|
|
113
|
-
const candidate = url.pathname + url.search + url.hash;
|
|
114
|
-
|
|
115
|
-
// *** SECOND RESOLUTION. DO NOT DELETE THIS AS REDUNDANT - IT IS NOT. ***
|
|
116
|
-
//
|
|
117
|
-
// v3 (the origin comparison above) was bypassed too, and differently from v1/v2.
|
|
118
|
-
// The origin check constrains where `raw` RESOLVED TO. It does not constrain how
|
|
119
|
-
// the RESULT IS SPELLED - and the result is resolved a SECOND time, by whoever
|
|
120
|
-
// navigates to it. A returned path that begins with `//` is a PROTOCOL-RELATIVE
|
|
121
|
-
// URL at that second resolution, so it lands off-origin having passed every
|
|
122
|
-
// check here:
|
|
123
|
-
//
|
|
124
|
-
// '/..//evil.example.com' -> pathname '//evil.example.com' -> evil.example.com
|
|
125
|
-
// '/.//evil.example.com' -> pathname '//evil.example.com' -> evil.example.com
|
|
126
|
-
// '/%2e%2e//evil.example.com' -> pathname '//evil.example.com' -> evil.example.com
|
|
127
|
-
//
|
|
128
|
-
// The parser resolves `..` against the synthetic base and, at the root, leaves
|
|
129
|
-
// behind the EMPTY SEGMENT rather than collapsing it. Origin stayed BASE the
|
|
130
|
-
// whole time, so nothing upstream fired.
|
|
131
|
-
//
|
|
132
|
-
// MEASURED, not reasoned: in a real headless Chromium on the live origin
|
|
133
|
-
// https://portal.keelauth.com, 5 spellings resolved to https://evil.example.com
|
|
134
|
-
// (QAPert-NightHawk, 2026-09-11, card 325543), and on 2026-09-11 the SERVED
|
|
135
|
-
// BUNDLE was measured escaping 5/5 through THIS PACKAGE'S copy while the
|
|
136
|
-
// portal's own src/lib/safe-callback.ts was already fixed - the two copies had
|
|
137
|
-
// drifted. See the DRIFT note in this file's header.
|
|
138
|
-
//
|
|
139
|
-
// THE FIX FOLLOWS THIS FILE'S OWN DOCTRINE: do not pattern-match the spellings
|
|
140
|
-
// (that is the startsWith() trap that failed four times, and this would be the
|
|
141
|
-
// fifth). Ask the parser AGAIN about the exact value we are about to hand out,
|
|
142
|
-
// and require it to be a FIXED POINT. Anything whose meaning changes when it is
|
|
143
|
-
// resolved a second time is by definition not a stable same-origin path, however
|
|
144
|
-
// it is spelled - including spellings nobody has found yet.
|
|
145
|
-
let echo: URL;
|
|
146
|
-
try {
|
|
147
|
-
echo = new URL(candidate, BASE);
|
|
148
|
-
} catch {
|
|
149
|
-
return fallback;
|
|
150
|
-
}
|
|
151
|
-
if (echo.origin !== BASE) return fallback;
|
|
152
|
-
if (isAuthSurface(echo.pathname)) return fallback;
|
|
153
|
-
if (echo.pathname + echo.search + echo.hash !== candidate) return fallback;
|
|
154
|
-
|
|
155
|
-
return candidate;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Sentinel that `safeCallbackPath` can never legitimately return: it is not a
|
|
160
|
-
* fixed point of the URL parser (the parser would percent-encode the space), so
|
|
161
|
-
* it cannot collide with a genuine accepted value.
|
|
162
|
-
*/
|
|
163
|
-
const REJECTED = '\u0000rejected';
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* True when `raw` is a callback target we are willing to honour.
|
|
167
|
-
*
|
|
168
|
-
* DEFINED IN TERMS OF `safeCallbackPath` ON PURPOSE - it used to carry its own
|
|
169
|
-
* copy of the predicate, and that copy was missing the second resolution above
|
|
170
|
-
* while the two were maintained separately. This file's whole history is
|
|
171
|
-
* duplicated predicates drifting apart (see the header). One definition, one
|
|
172
|
-
* place to fix.
|
|
173
|
-
*/
|
|
174
|
-
export function isSafeCallbackPath(raw: unknown): raw is string {
|
|
175
|
-
return typeof raw === 'string' && raw.length > 0 && safeCallbackPath(raw, REJECTED) !== REJECTED;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Pick the post-login destination: an explicit `?callbackUrl=` when it is one we
|
|
180
|
-
* will honour, otherwise the current path, otherwise `/`.
|
|
181
|
-
*
|
|
182
|
-
* This replaces the package's three hand-rolled `getSafeCallbackUrl` /
|
|
183
|
-
* `getCallbackUrl` bodies. Note the ARGUMENT ORDER DEPENDENCY those had: they
|
|
184
|
-
* tested `pathname` FIRST and returned early, so a caller on the login page
|
|
185
|
-
* never reached the attacker-controlled parameter at all - safe, but only
|
|
186
|
-
* because that caller happened to pass a value that tripped the function's own
|
|
187
|
-
* first guard. Nothing in the code named that dependency, and an argument
|
|
188
|
-
* reorder would have opened it. Here BOTH inputs are validated, so the
|
|
189
|
-
* protection no longer depends on which one is passed.
|
|
190
|
-
*/
|
|
191
|
-
export function resolveCallbackDestination(
|
|
192
|
-
pathname: string,
|
|
193
|
-
searchParams?: URLSearchParams | null,
|
|
194
|
-
fallback: string = DEFAULT_CALLBACK
|
|
195
|
-
): string {
|
|
196
|
-
const requested = searchParams?.get('callbackUrl');
|
|
197
|
-
if (isSafeCallbackPath(requested)) return safeCallbackPath(requested, fallback);
|
|
198
|
-
return safeCallbackPath(pathname, fallback);
|
|
199
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* ONE definition of "is this callback target safe to navigate to", for the
|
|
3
|
+
* whole package (375656).
|
|
4
|
+
*
|
|
5
|
+
* *** THIS DELEGATES PARSING TO THE URL PARSER. DO NOT REPLACE IT WITH
|
|
6
|
+
* *** startsWith() CHECKS. THAT APPROACH FAILED FOUR TIMES.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS FILE EXISTS: `@payez/next-mvp` shipped THREE bodies named
|
|
9
|
+
* `makeAuthDecision`, each carrying its own `callbackUrl.startsWith('/account-auth/')`
|
|
10
|
+
* test. That test is a REDIRECT-LOOP guard sitting in the seat where an ORIGIN
|
|
11
|
+
* check belongs - its own comment says "prevents infinite loop" - so
|
|
12
|
+
* `https://evil.example.com` does not start with `/account-auth/`, passes
|
|
13
|
+
* through untouched, and becomes the redirect target. The exported copy
|
|
14
|
+
* (`auth/auth-decision.ts`, re-exported from `index.ts` and `edge/middleware.ts`)
|
|
15
|
+
* is public API, so that is a published open redirect independent of who calls it.
|
|
16
|
+
*
|
|
17
|
+
* HISTORY, because it is the whole argument for the current shape (267097 /
|
|
18
|
+
* 267174 / 267448), carried over verbatim from the portal's `src/lib/safe-callback.ts`
|
|
19
|
+
* where this guard was proven (0 leaks / 21 hostile inputs):
|
|
20
|
+
*
|
|
21
|
+
* v1 `startsWith('/') && !startsWith('//')` -> bypassed by /BACKSLASH host
|
|
22
|
+
* v2 + `!startsWith('/' + BACKSLASH)` -> bypassed by /TAB/host
|
|
23
|
+
* ... and /LF/host, /CR/host
|
|
24
|
+
* v3 resolve once + compare ORIGIN -> bypassed by /..//host
|
|
25
|
+
* and /.//host, /%2e%2e//host
|
|
26
|
+
* v4 + require the RESULT to be a FIXED POINT <- current
|
|
27
|
+
*
|
|
28
|
+
* *** DRIFT, 2026-09-11 (325543). THIS FILE WAS v3 WHILE THE PORTAL'S COPY WAS
|
|
29
|
+
* *** v4, AND THE PACKAGE COPY IS THE ONE THAT SHIPPED. The portal fixed its own
|
|
30
|
+
* *** src/lib/safe-callback.ts (e1f08fea) and deployed; the served bundle STILL
|
|
31
|
+
* *** escaped 5/5 in a real browser, because `src/contexts/ThemeContext.tsx`
|
|
32
|
+
* *** imports the BARE BARREL `@payez/next-mvp`, which pulls dist/index.js ->
|
|
33
|
+
* *** auth/auth-decision -> THIS FILE into the client graph, and because the
|
|
34
|
+
* *** portal's `src/middleware.ts` imports `@payez/next-mvp/edge`, which requires
|
|
35
|
+
* *** THIS FILE server-side on every request. It was read as a stale build
|
|
36
|
+
* *** artifact of the portal's file for most of a day. It was not. It was a
|
|
37
|
+
* *** SECOND DEFINITION, and no rebuild of the portal could have removed it.
|
|
38
|
+
* *** The header below says "ONE definition ... for the whole package" and that
|
|
39
|
+
* *** was true INSIDE this package boundary the entire time. The defect lived in
|
|
40
|
+
* *** the gap BETWEEN two boundaries that each enforced one-definition correctly.
|
|
41
|
+
* *** A guard that exists in N copies is only as strong as its weakest copy, and
|
|
42
|
+
* *** a per-repo grep gate cannot see N. IF YOU CHANGE THE PREDICATE HERE, CHANGE
|
|
43
|
+
* *** IT IN THE PORTAL'S src/lib/safe-callback.ts IN THE SAME HOUR, OR DELETE ONE
|
|
44
|
+
* *** OF THEM.
|
|
45
|
+
*
|
|
46
|
+
* Both bypasses work the same way: the browser NORMALISES the string AFTER the
|
|
47
|
+
* guard has finished looking at it. The WHATWG URL parser converts backslashes
|
|
48
|
+
* to forward slashes and STRIPS all ASCII tab, LF and CR before resolving. So a
|
|
49
|
+
* prefix check can always be defeated by a character that is not there yet when
|
|
50
|
+
* it runs. Each fix bought one character. The list was never provably complete.
|
|
51
|
+
*
|
|
52
|
+
* SO: stop re-implementing the parser and ask it instead. Resolve the input
|
|
53
|
+
* against a synthetic base and compare ORIGINS. Anything that escapes the
|
|
54
|
+
* origin - however it is spelled, including spellings nobody has found yet -
|
|
55
|
+
* fails one comparison. The returned value is the parser's own NORMALISED
|
|
56
|
+
* output, so an accepted path cannot smuggle a control character onward either.
|
|
57
|
+
*
|
|
58
|
+
* A synthetic base is used deliberately so this works identically in a server
|
|
59
|
+
* component (no window), on the client, and in the edge runtime.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/** Where we send anyone whose requested target we will not honour. */
|
|
63
|
+
export const DEFAULT_CALLBACK = '/';
|
|
64
|
+
|
|
65
|
+
/** The auth surface. A callback pointing back into it causes a redirect cycle. */
|
|
66
|
+
const AUTH_PREFIX = '/account-auth/';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* PAY-2315 (4.7.1): the middleware now treats the public /login page like /account-auth/login (a signed-in user is sent
|
|
70
|
+
* on), so a callback or a resolved path that is /login itself must be refused here too - otherwise /login with no
|
|
71
|
+
* callbackUrl would resolve its own pathname as the destination and redirect to itself forever.
|
|
72
|
+
*/
|
|
73
|
+
function isAuthSurface(pathname: string): boolean {
|
|
74
|
+
return pathname.startsWith(AUTH_PREFIX) || pathname === '/login' || pathname.startsWith('/login/');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Synthetic, unreachable base. Only ever used as the resolution origin for the
|
|
79
|
+
* comparison below - never emitted, never navigated to. `.invalid` is reserved
|
|
80
|
+
* by RFC 2606 and can never resolve.
|
|
81
|
+
*/
|
|
82
|
+
const BASE = 'http://safe-callback.invalid';
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The value to navigate to: a same-origin relative path, NORMALISED by the URL
|
|
86
|
+
* parser, or `fallback` for anything that escapes the origin or loops back into
|
|
87
|
+
* the auth surface. Never throws.
|
|
88
|
+
*/
|
|
89
|
+
export function safeCallbackPath(raw: unknown, fallback: string = DEFAULT_CALLBACK): string {
|
|
90
|
+
if (typeof raw !== 'string' || raw.length === 0) return fallback;
|
|
91
|
+
|
|
92
|
+
let url: URL;
|
|
93
|
+
try {
|
|
94
|
+
url = new URL(raw, BASE);
|
|
95
|
+
} catch {
|
|
96
|
+
return fallback;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Anything that resolved to a different origin escaped, however it was spelled:
|
|
100
|
+
// absolute, protocol-relative //host, backslash /\host, tab/LF/CR variants, or
|
|
101
|
+
// a scheme like javascript: (whose origin is opaque and never equals BASE).
|
|
102
|
+
if (url.origin !== BASE) return fallback;
|
|
103
|
+
|
|
104
|
+
// Loopbacks into the auth surface cause redirect cycles.
|
|
105
|
+
if (isAuthSurface(url.pathname)) return fallback;
|
|
106
|
+
|
|
107
|
+
// PAY-2218: `_rsc` is Next's flight cache-buster. Once skipProxyUrlNormalize lets it reach middleware it leaks
|
|
108
|
+
// into return targets (`callbackUrl=/overview?_rsc=abc12`, NightHawk 72013); it is never a meaningful part of a
|
|
109
|
+
// destination. Only touched when present, so every other query keeps its exact spelling.
|
|
110
|
+
if (url.searchParams.has('_rsc')) url.searchParams.delete('_rsc');
|
|
111
|
+
|
|
112
|
+
// The parser's normalised output - control characters already stripped.
|
|
113
|
+
const candidate = url.pathname + url.search + url.hash;
|
|
114
|
+
|
|
115
|
+
// *** SECOND RESOLUTION. DO NOT DELETE THIS AS REDUNDANT - IT IS NOT. ***
|
|
116
|
+
//
|
|
117
|
+
// v3 (the origin comparison above) was bypassed too, and differently from v1/v2.
|
|
118
|
+
// The origin check constrains where `raw` RESOLVED TO. It does not constrain how
|
|
119
|
+
// the RESULT IS SPELLED - and the result is resolved a SECOND time, by whoever
|
|
120
|
+
// navigates to it. A returned path that begins with `//` is a PROTOCOL-RELATIVE
|
|
121
|
+
// URL at that second resolution, so it lands off-origin having passed every
|
|
122
|
+
// check here:
|
|
123
|
+
//
|
|
124
|
+
// '/..//evil.example.com' -> pathname '//evil.example.com' -> evil.example.com
|
|
125
|
+
// '/.//evil.example.com' -> pathname '//evil.example.com' -> evil.example.com
|
|
126
|
+
// '/%2e%2e//evil.example.com' -> pathname '//evil.example.com' -> evil.example.com
|
|
127
|
+
//
|
|
128
|
+
// The parser resolves `..` against the synthetic base and, at the root, leaves
|
|
129
|
+
// behind the EMPTY SEGMENT rather than collapsing it. Origin stayed BASE the
|
|
130
|
+
// whole time, so nothing upstream fired.
|
|
131
|
+
//
|
|
132
|
+
// MEASURED, not reasoned: in a real headless Chromium on the live origin
|
|
133
|
+
// https://portal.keelauth.com, 5 spellings resolved to https://evil.example.com
|
|
134
|
+
// (QAPert-NightHawk, 2026-09-11, card 325543), and on 2026-09-11 the SERVED
|
|
135
|
+
// BUNDLE was measured escaping 5/5 through THIS PACKAGE'S copy while the
|
|
136
|
+
// portal's own src/lib/safe-callback.ts was already fixed - the two copies had
|
|
137
|
+
// drifted. See the DRIFT note in this file's header.
|
|
138
|
+
//
|
|
139
|
+
// THE FIX FOLLOWS THIS FILE'S OWN DOCTRINE: do not pattern-match the spellings
|
|
140
|
+
// (that is the startsWith() trap that failed four times, and this would be the
|
|
141
|
+
// fifth). Ask the parser AGAIN about the exact value we are about to hand out,
|
|
142
|
+
// and require it to be a FIXED POINT. Anything whose meaning changes when it is
|
|
143
|
+
// resolved a second time is by definition not a stable same-origin path, however
|
|
144
|
+
// it is spelled - including spellings nobody has found yet.
|
|
145
|
+
let echo: URL;
|
|
146
|
+
try {
|
|
147
|
+
echo = new URL(candidate, BASE);
|
|
148
|
+
} catch {
|
|
149
|
+
return fallback;
|
|
150
|
+
}
|
|
151
|
+
if (echo.origin !== BASE) return fallback;
|
|
152
|
+
if (isAuthSurface(echo.pathname)) return fallback;
|
|
153
|
+
if (echo.pathname + echo.search + echo.hash !== candidate) return fallback;
|
|
154
|
+
|
|
155
|
+
return candidate;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Sentinel that `safeCallbackPath` can never legitimately return: it is not a
|
|
160
|
+
* fixed point of the URL parser (the parser would percent-encode the space), so
|
|
161
|
+
* it cannot collide with a genuine accepted value.
|
|
162
|
+
*/
|
|
163
|
+
const REJECTED = '\u0000rejected';
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* True when `raw` is a callback target we are willing to honour.
|
|
167
|
+
*
|
|
168
|
+
* DEFINED IN TERMS OF `safeCallbackPath` ON PURPOSE - it used to carry its own
|
|
169
|
+
* copy of the predicate, and that copy was missing the second resolution above
|
|
170
|
+
* while the two were maintained separately. This file's whole history is
|
|
171
|
+
* duplicated predicates drifting apart (see the header). One definition, one
|
|
172
|
+
* place to fix.
|
|
173
|
+
*/
|
|
174
|
+
export function isSafeCallbackPath(raw: unknown): raw is string {
|
|
175
|
+
return typeof raw === 'string' && raw.length > 0 && safeCallbackPath(raw, REJECTED) !== REJECTED;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Pick the post-login destination: an explicit `?callbackUrl=` when it is one we
|
|
180
|
+
* will honour, otherwise the current path, otherwise `/`.
|
|
181
|
+
*
|
|
182
|
+
* This replaces the package's three hand-rolled `getSafeCallbackUrl` /
|
|
183
|
+
* `getCallbackUrl` bodies. Note the ARGUMENT ORDER DEPENDENCY those had: they
|
|
184
|
+
* tested `pathname` FIRST and returned early, so a caller on the login page
|
|
185
|
+
* never reached the attacker-controlled parameter at all - safe, but only
|
|
186
|
+
* because that caller happened to pass a value that tripped the function's own
|
|
187
|
+
* first guard. Nothing in the code named that dependency, and an argument
|
|
188
|
+
* reorder would have opened it. Here BOTH inputs are validated, so the
|
|
189
|
+
* protection no longer depends on which one is passed.
|
|
190
|
+
*/
|
|
191
|
+
export function resolveCallbackDestination(
|
|
192
|
+
pathname: string,
|
|
193
|
+
searchParams?: URLSearchParams | null,
|
|
194
|
+
fallback: string = DEFAULT_CALLBACK
|
|
195
|
+
): string {
|
|
196
|
+
const requested = searchParams?.get('callbackUrl');
|
|
197
|
+
if (isSafeCallbackPath(requested)) return safeCallbackPath(requested, fallback);
|
|
198
|
+
return safeCallbackPath(pathname, fallback);
|
|
199
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The ONE body that writes the session-cookie expiry Set-Cookie headers. Shared by the middleware
|
|
3
|
-
* (create-middleware.ts clearSessionCookies, behind its prefetch guard) and the PAY-2218 session-reset route, so
|
|
4
|
-
* the two clearing sites cannot drift apart on which cookies they expire or with which attributes.
|
|
5
|
-
*/
|
|
6
|
-
import type { NextResponse } from 'next/server';
|
|
7
|
-
import { getSessionCookieName, getSecureSessionCookieName } from '../lib/app-slug';
|
|
8
|
-
|
|
9
|
-
export function expireSessionCookies(response: NextResponse, requestCookieNames: Iterable<string>): void {
|
|
10
|
-
const sessionCookie = getSessionCookieName();
|
|
11
|
-
const secureCookie = getSecureSessionCookieName();
|
|
12
|
-
|
|
13
|
-
// Clear main cookies
|
|
14
|
-
response.cookies.set(sessionCookie, '', { path: '/', expires: new Date(0), httpOnly: true, sameSite: 'lax' });
|
|
15
|
-
response.cookies.set(secureCookie, '', { path: '/', expires: new Date(0), httpOnly: true, sameSite: 'lax', secure: true });
|
|
16
|
-
|
|
17
|
-
// Clear any chunked cookies (NextAuth chunks large cookies)
|
|
18
|
-
for (const name of requestCookieNames) {
|
|
19
|
-
if (name.startsWith(`${sessionCookie}.`) || name.startsWith(`${secureCookie}.`)) {
|
|
20
|
-
const isSecure = name.startsWith('__Secure-');
|
|
21
|
-
response.cookies.set(name, '', {
|
|
22
|
-
path: '/',
|
|
23
|
-
expires: new Date(0),
|
|
24
|
-
httpOnly: true,
|
|
25
|
-
sameSite: 'lax',
|
|
26
|
-
...(isSecure && { secure: true })
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The ONE body that writes the session-cookie expiry Set-Cookie headers. Shared by the middleware
|
|
3
|
+
* (create-middleware.ts clearSessionCookies, behind its prefetch guard) and the PAY-2218 session-reset route, so
|
|
4
|
+
* the two clearing sites cannot drift apart on which cookies they expire or with which attributes.
|
|
5
|
+
*/
|
|
6
|
+
import type { NextResponse } from 'next/server';
|
|
7
|
+
import { getSessionCookieName, getSecureSessionCookieName } from '../lib/app-slug';
|
|
8
|
+
|
|
9
|
+
export function expireSessionCookies(response: NextResponse, requestCookieNames: Iterable<string>): void {
|
|
10
|
+
const sessionCookie = getSessionCookieName();
|
|
11
|
+
const secureCookie = getSecureSessionCookieName();
|
|
12
|
+
|
|
13
|
+
// Clear main cookies
|
|
14
|
+
response.cookies.set(sessionCookie, '', { path: '/', expires: new Date(0), httpOnly: true, sameSite: 'lax' });
|
|
15
|
+
response.cookies.set(secureCookie, '', { path: '/', expires: new Date(0), httpOnly: true, sameSite: 'lax', secure: true });
|
|
16
|
+
|
|
17
|
+
// Clear any chunked cookies (NextAuth chunks large cookies)
|
|
18
|
+
for (const name of requestCookieNames) {
|
|
19
|
+
if (name.startsWith(`${sessionCookie}.`) || name.startsWith(`${secureCookie}.`)) {
|
|
20
|
+
const isSecure = name.startsWith('__Secure-');
|
|
21
|
+
response.cookies.set(name, '', {
|
|
22
|
+
path: '/',
|
|
23
|
+
expires: new Date(0),
|
|
24
|
+
httpOnly: true,
|
|
25
|
+
sameSite: 'lax',
|
|
26
|
+
...(isSecure && { secure: true })
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|