@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.
Files changed (314) hide show
  1. package/POST-INSTALL.md +408 -408
  2. package/README.md +859 -859
  3. package/dist/api-handlers/admin/audit.js +13 -21
  4. package/dist/api-handlers/admin/sessions.js +59 -45
  5. package/dist/api-handlers/admin/stats.js +1 -1
  6. package/dist/api-handlers/admin/users.js +15 -23
  7. package/dist/api-handlers/admin/vibe-data.d.ts +5 -0
  8. package/dist/api-handlers/admin/vibe-data.js +5 -0
  9. package/dist/components/account/MobileNavDrawer.js +18 -18
  10. package/dist/components/account/UserAvatarMenu.js +1 -1
  11. package/dist/components/admin/AlertSettingsTab.js +40 -40
  12. package/dist/components/admin/LoggingSettingsTab.js +28 -28
  13. package/dist/lib/session-store.js +21 -21
  14. package/dist/lib/startup-init.js +19 -19
  15. package/dist/pages/login/page.js +28 -28
  16. package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
  17. package/dist/vibe/client.d.ts +1 -1
  18. package/dist/vibe/client.js +2 -1
  19. package/dist/vibe/filters.d.ts +36 -0
  20. package/dist/vibe/filters.js +47 -0
  21. package/dist/vibe/generic.js +2 -1
  22. package/dist/vibe/sessions.js +28 -3
  23. package/package.json +2 -2
  24. package/scripts/check-publish-eol.mjs +33 -15
  25. package/src/api/auth-handler.ts +550 -550
  26. package/src/api/index.ts +17 -17
  27. package/src/api-handlers/account/change-password.ts +170 -170
  28. package/src/api-handlers/account/masked-info.ts +46 -46
  29. package/src/api-handlers/account/profile.ts +99 -99
  30. package/src/api-handlers/account/recovery/forward.ts +117 -117
  31. package/src/api-handlers/account/recovery/initiate.ts +59 -59
  32. package/src/api-handlers/account/recovery/send-code.ts +21 -21
  33. package/src/api-handlers/account/recovery/verify-code.ts +20 -20
  34. package/src/api-handlers/account/reset-password.ts +87 -87
  35. package/src/api-handlers/account/send-code.ts +70 -70
  36. package/src/api-handlers/account/update-phone.ts +84 -84
  37. package/src/api-handlers/account/validate-password.ts +139 -139
  38. package/src/api-handlers/account/verify-email.ts +134 -134
  39. package/src/api-handlers/account/verify-sms.ts +134 -134
  40. package/src/api-handlers/admin/analytics.ts +452 -452
  41. package/src/api-handlers/admin/audit.ts +223 -232
  42. package/src/api-handlers/admin/index.ts +63 -63
  43. package/src/api-handlers/admin/redis-sessions.ts +259 -259
  44. package/src/api-handlers/admin/sessions.ts +341 -327
  45. package/src/api-handlers/admin/site-logs.ts +388 -388
  46. package/src/api-handlers/admin/stats.ts +260 -260
  47. package/src/api-handlers/admin/users.ts +240 -251
  48. package/src/api-handlers/admin/vibe-data.ts +338 -333
  49. package/src/api-handlers/anon/preferences.ts +123 -123
  50. package/src/api-handlers/auth/credential-login-email-code.ts +92 -92
  51. package/src/api-handlers/auth/credential-login.ts +948 -948
  52. package/src/api-handlers/auth/jwks.ts +31 -31
  53. package/src/api-handlers/auth/login.ts +252 -252
  54. package/src/api-handlers/auth/passkey-signin.ts +116 -116
  55. package/src/api-handlers/auth/refresh.ts +829 -829
  56. package/src/api-handlers/auth/signout.ts +201 -201
  57. package/src/api-handlers/auth/status.ts +23 -23
  58. package/src/api-handlers/auth/update-session.ts +144 -144
  59. package/src/api-handlers/auth/validate.ts +45 -45
  60. package/src/api-handlers/auth/verify-code.ts +116 -116
  61. package/src/api-handlers/mfa/backup-code-verify.ts +170 -170
  62. package/src/api-handlers/mfa/backup-codes.ts +42 -42
  63. package/src/api-handlers/mfa/methods.ts +47 -47
  64. package/src/api-handlers/mfa/totp-confirm.ts +43 -43
  65. package/src/api-handlers/mfa/totp-enroll.ts +43 -43
  66. package/src/api-handlers/mfa/totp-verify.ts +172 -172
  67. package/src/api-handlers/oauth/resume.ts +182 -182
  68. package/src/api-handlers/onboarding/send-email-code.ts +18 -18
  69. package/src/api-handlers/onboarding/set-password.ts +21 -21
  70. package/src/api-handlers/onboarding/verify-code.ts +20 -20
  71. package/src/api-handlers/onboarding/verify-welcome-email.ts +17 -17
  72. package/src/api-handlers/passkeys/list.ts +32 -32
  73. package/src/api-handlers/passkeys/manage.ts +55 -55
  74. package/src/api-handlers/passkeys/register-options.ts +43 -43
  75. package/src/api-handlers/passkeys/register-verify.ts +42 -42
  76. package/src/api-handlers/session/refresh-viability.ts +56 -56
  77. package/src/api-handlers/session/viability.ts +238 -238
  78. package/src/api-handlers/test/force-expire.ts +167 -167
  79. package/src/auth/auth-decision.ts +292 -292
  80. package/src/auth/better-auth.ts +1688 -1688
  81. package/src/auth/idp-oauth.ts +310 -310
  82. package/src/auth/password-reset-bridge.ts +98 -98
  83. package/src/auth/revocation-fields.ts +16 -16
  84. package/src/auth/route-config.ts +236 -236
  85. package/src/auth/safe-callback.ts +199 -199
  86. package/src/auth/session-cookie-clear.ts +30 -30
  87. package/src/auth/session-reset-link.ts +159 -159
  88. package/src/auth/session-response-sanitizer.ts +151 -151
  89. package/src/auth/types/auth-types.ts +555 -555
  90. package/src/auth/types/index.ts +7 -7
  91. package/src/auth/unauthenticated-routes.ts +2 -2
  92. package/src/auth/utils/idp-client.ts +364 -364
  93. package/src/auth/utils/index.ts +6 -6
  94. package/src/auth/utils/token-utils.ts +243 -243
  95. package/src/client/AuthContext.tsx +192 -192
  96. package/src/client/better-auth-client.ts +70 -70
  97. package/src/client/fetch-with-auth.ts +47 -47
  98. package/src/client/fetchWithSession.ts +21 -21
  99. package/src/client/index.ts +13 -13
  100. package/src/client/useAnonSession.ts +131 -131
  101. package/src/components/SessionSync.tsx +138 -138
  102. package/src/components/SignalRHealthCheck.tsx +131 -131
  103. package/src/components/account/MobileNavDrawer.tsx +305 -305
  104. package/src/components/account/UserAvatarMenu.tsx +277 -277
  105. package/src/components/account/index.ts +13 -13
  106. package/src/components/admin/AlertSettingsTab.tsx +728 -728
  107. package/src/components/admin/AnalyticsTab.tsx +703 -703
  108. package/src/components/admin/DataBrowserTab.tsx +508 -508
  109. package/src/components/admin/LoggingSettingsTab.tsx +665 -665
  110. package/src/components/admin/SessionsTab.tsx +421 -421
  111. package/src/components/admin/StatsTab.tsx +379 -379
  112. package/src/components/admin/VibeAdminContext.tsx +87 -87
  113. package/src/components/admin/VibeAdminLayout.tsx +187 -187
  114. package/src/components/admin/index.ts +59 -59
  115. package/src/components/auth/CredentialSignInForm.tsx +240 -240
  116. package/src/components/auth/FederatedAuthSection.tsx +129 -129
  117. package/src/components/auth/ModeAwareLoginPage.tsx +146 -146
  118. package/src/components/auth/ModeAwareSignupPage.tsx +200 -200
  119. package/src/components/auth/PasswordResetBridge.tsx +30 -30
  120. package/src/components/auth/TraditionalAuthSection.tsx +103 -103
  121. package/src/components/recovery/CompleteStep.tsx +36 -36
  122. package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
  123. package/src/components/recovery/RequestedStep.tsx +30 -30
  124. package/src/components/recovery/SelectMethodStep.tsx +73 -73
  125. package/src/components/recovery/SetPasswordStep.tsx +118 -118
  126. package/src/components/recovery/VerifyCodeStep.tsx +90 -90
  127. package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -160
  128. package/src/components/reserved/ReservedStatusBox.tsx +118 -118
  129. package/src/components/ui/BetaBadge.tsx +58 -58
  130. package/src/components/ui/Footer.tsx +93 -93
  131. package/src/config/env.ts +55 -55
  132. package/src/config/logger.ts +62 -62
  133. package/src/config/logging-config.ts +82 -82
  134. package/src/config/unauthenticated-routes.ts +19 -19
  135. package/src/config/vibe-log-transport.ts +264 -264
  136. package/src/edge/internal-api-url.ts +65 -65
  137. package/src/edge/middleware.ts +42 -42
  138. package/src/hooks/useAuth.ts +117 -117
  139. package/src/hooks/useAuthSettings.ts +109 -109
  140. package/src/hooks/useAvailableProviders.ts +143 -143
  141. package/src/hooks/usePasswordValidation.ts +127 -127
  142. package/src/hooks/useProfile.ts +82 -82
  143. package/src/hooks/usePublicAuthSettings.ts +171 -171
  144. package/src/hooks/useSessionExpiration.ts +101 -101
  145. package/src/hooks/useViabilitySession.ts +337 -337
  146. package/src/index.ts +64 -64
  147. package/src/lib/anon-session.ts +213 -213
  148. package/src/lib/api-handler.ts +643 -643
  149. package/src/lib/app-slug.ts +228 -228
  150. package/src/lib/authorize-resume.ts +172 -172
  151. package/src/lib/bearer-fetch.ts +31 -31
  152. package/src/lib/client-key.ts +88 -88
  153. package/src/lib/code-step-copy.ts +83 -83
  154. package/src/lib/credential-sign-in-client.ts +101 -101
  155. package/src/lib/demo-mode.ts +18 -18
  156. package/src/lib/device-id.ts +67 -67
  157. package/src/lib/ensure-fresh-access-token.ts +509 -509
  158. package/src/lib/geolocation.ts +265 -265
  159. package/src/lib/idp-client-config.ts +1005 -1005
  160. package/src/lib/idp-fetch.ts +169 -169
  161. package/src/lib/idp-url-guard.ts +37 -37
  162. package/src/lib/internal-api.ts +171 -171
  163. package/src/lib/jwt-decode-client.ts +45 -45
  164. package/src/lib/jwt-decode.ts +82 -82
  165. package/src/lib/lock-window.ts +49 -49
  166. package/src/lib/login-error-message.ts +41 -41
  167. package/src/lib/never-in-response.ts +64 -64
  168. package/src/lib/nextauth-secret.ts +168 -168
  169. package/src/lib/onboarding-proxy.ts +57 -57
  170. package/src/lib/password-rules.ts +50 -50
  171. package/src/lib/rate-limit-service.ts +9 -9
  172. package/src/lib/redis.ts +105 -105
  173. package/src/lib/refresh-token-validator.ts +64 -64
  174. package/src/lib/roles.ts +177 -177
  175. package/src/lib/safe-error-response.ts +149 -149
  176. package/src/lib/secret-validation.ts +8 -8
  177. package/src/lib/session-cookie-shape.ts +185 -185
  178. package/src/lib/session-store.ts +1092 -1092
  179. package/src/lib/session.ts +34 -34
  180. package/src/lib/single-flight.ts +64 -64
  181. package/src/lib/site-logger.ts +245 -245
  182. package/src/lib/standardized-client-api.ts +901 -901
  183. package/src/lib/startup-init.ts +276 -276
  184. package/src/lib/test-aware-get-token.ts +41 -41
  185. package/src/lib/token-expiry.ts +40 -40
  186. package/src/lib/token-lifecycle.ts +546 -546
  187. package/src/lib/token-log-id.ts +69 -69
  188. package/src/lib/twofa-rejection.ts +91 -91
  189. package/src/lib/types/api-responses.ts +336 -336
  190. package/src/lib/user-agent-parser.ts +252 -252
  191. package/src/logging/api/admin-analytics.ts +51 -51
  192. package/src/logging/api/audit-log.ts +53 -53
  193. package/src/logging/components/AdminAnalyticsLayout.tsx +49 -49
  194. package/src/logging/components/AuditLogViewer.tsx +125 -125
  195. package/src/logging/components/ErrorMetricsCard.tsx +98 -98
  196. package/src/logging/components/HealthMetricsCard.tsx +70 -70
  197. package/src/logging/hooks/useAdminAnalytics.ts +22 -22
  198. package/src/logging/hooks/useAuditLog.ts +24 -24
  199. package/src/logging/hooks/useErrorMetrics.ts +40 -40
  200. package/src/logging/hooks/useHealthMetrics.ts +44 -44
  201. package/src/logging/index.ts +18 -18
  202. package/src/logging/types/analytics.ts +81 -81
  203. package/src/logging/types/audit.ts +31 -31
  204. package/src/logging/types/index.ts +3 -3
  205. package/src/middleware/create-middleware.ts +1141 -1141
  206. package/src/middleware/rbac-check.ts +279 -279
  207. package/src/middleware/twofa-presets.ts +224 -224
  208. package/src/models/DecodedAccessToken.ts +16 -16
  209. package/src/models/SessionModel.ts +331 -331
  210. package/src/pages/admin-login/page.tsx +111 -111
  211. package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -527
  212. package/src/pages/admin-page-permissions/index.ts +7 -7
  213. package/src/pages/admin-roles/RolesAdminPage.tsx +533 -533
  214. package/src/pages/admin-roles/index.ts +9 -9
  215. package/src/pages/admin-roles/modals.tsx +469 -469
  216. package/src/pages/client-admin/ClientSiteAdminPage.tsx +382 -382
  217. package/src/pages/client-admin/index.ts +33 -33
  218. package/src/pages/coming-soon/page.tsx +83 -83
  219. package/src/pages/login/page.tsx +338 -338
  220. package/src/pages/onboarding/index.ts +11 -11
  221. package/src/pages/onboarding/page.tsx +212 -212
  222. package/src/pages/profile/EnhancedProfilePage.tsx +514 -514
  223. package/src/pages/profile/index.ts +9 -9
  224. package/src/pages/profile/page.tsx +166 -166
  225. package/src/pages/recovery/page.tsx +267 -267
  226. package/src/pages/roles/MyRolesPage.tsx +211 -211
  227. package/src/pages/roles/components.tsx +294 -294
  228. package/src/pages/roles/index.ts +17 -17
  229. package/src/pages/security/EnhancedSecurityPage.tsx +660 -660
  230. package/src/pages/security/index.ts +12 -12
  231. package/src/pages/security/page.tsx +507 -507
  232. package/src/pages/service-unavailable/page.tsx +63 -63
  233. package/src/pages/settings/EnhancedSettingsPage.tsx +642 -642
  234. package/src/pages/settings/index.ts +9 -9
  235. package/src/pages/settings/page.tsx +47 -47
  236. package/src/pages/showcase/ShowcasePage.tsx +532 -532
  237. package/src/pages/showcase/index.ts +13 -13
  238. package/src/pages/test-env/EmergencyLogoutPage.tsx +192 -192
  239. package/src/pages/test-env/JwtInspectPage.tsx +449 -449
  240. package/src/pages/test-env/RefreshTokenPage.tsx +178 -178
  241. package/src/pages/test-env/TestEnvPage.tsx +130 -130
  242. package/src/pages/test-env/guarded.tsx +81 -81
  243. package/src/pages/test-env/index.ts +77 -77
  244. package/src/pages/test-env/redact-credentials.ts +96 -96
  245. package/src/pages/verify-code/page.tsx +746 -746
  246. package/src/routes/account/index.ts +31 -31
  247. package/src/routes/account/masked-info.ts +36 -36
  248. package/src/routes/account/send-code.ts +39 -39
  249. package/src/routes/account/update-phone.ts +13 -13
  250. package/src/routes/account/verify-email.ts +40 -40
  251. package/src/routes/account/verify-sms.ts +40 -40
  252. package/src/routes/auth/credential-login-email-code.ts +6 -6
  253. package/src/routes/auth/credential-login.ts +6 -6
  254. package/src/routes/auth/index.ts +22 -22
  255. package/src/routes/auth/logout.ts +111 -111
  256. package/src/routes/auth/nextauth.ts +45 -45
  257. package/src/routes/auth/passkey-signin.ts +13 -13
  258. package/src/routes/auth/refresh.ts +77 -77
  259. package/src/routes/auth/session-reset.ts +37 -37
  260. package/src/routes/auth/session.ts +261 -261
  261. package/src/routes/auth/settings.ts +181 -181
  262. package/src/routes/auth/viability.ts +519 -519
  263. package/src/routes/index.ts +17 -17
  264. package/src/routes/mfa/backup-code-verify.ts +1 -1
  265. package/src/routes/mfa/backup-codes.ts +10 -10
  266. package/src/routes/mfa/methods.ts +22 -22
  267. package/src/routes/mfa/totp-confirm.ts +10 -10
  268. package/src/routes/mfa/totp-enroll.ts +10 -10
  269. package/src/routes/mfa/totp-verify.ts +14 -14
  270. package/src/routes/onboarding/index.ts +15 -15
  271. package/src/routes/onboarding/send-email-code.ts +2 -2
  272. package/src/routes/onboarding/set-password.ts +2 -2
  273. package/src/routes/onboarding/verify-code.ts +2 -2
  274. package/src/routes/onboarding/verify-welcome-email.ts +2 -2
  275. package/src/routes/passkeys/list.ts +10 -10
  276. package/src/routes/passkeys/manage.ts +23 -23
  277. package/src/routes/passkeys/register-options.ts +10 -10
  278. package/src/routes/passkeys/register-verify.ts +10 -10
  279. package/src/routes/session/index.ts +7 -7
  280. package/src/routes/session/refresh-viability.ts +17 -17
  281. package/src/server/auth-guard.ts +203 -203
  282. package/src/server/auth.ts +486 -486
  283. package/src/server/decode-session.ts +309 -309
  284. package/src/server/slim-middleware.ts +117 -117
  285. package/src/server/verified-session-token.ts +103 -103
  286. package/src/server/with-auth.ts +93 -93
  287. package/src/services/signalrActivityService.ts +258 -258
  288. package/src/stores/authStore.ts +1900 -1900
  289. package/src/templates/instrumentation.ts +60 -60
  290. package/src/theme/ThemeProvider.tsx +39 -39
  291. package/src/theme/default.ts +33 -33
  292. package/src/theme/index.ts +31 -31
  293. package/src/theme/types.ts +69 -69
  294. package/src/theme/useTheme.ts +57 -57
  295. package/src/theme/utils.ts +40 -40
  296. package/src/types/api.ts +12 -12
  297. package/src/types/auth.d.ts +23 -23
  298. package/src/types/auth.ts +48 -48
  299. package/src/types/logging.ts +11 -11
  300. package/src/types/recovery.ts +54 -54
  301. package/src/utils/api.ts +401 -401
  302. package/src/utils/circuitBreaker.ts +40 -40
  303. package/src/utils/error-message.ts +108 -108
  304. package/src/utils/layout/reservedSpace.ts +124 -124
  305. package/src/utils/logout.ts +30 -30
  306. package/src/vibe/client.ts +878 -877
  307. package/src/vibe/enterprise-auth.ts +208 -208
  308. package/src/vibe/errors.ts +209 -209
  309. package/src/vibe/filters.ts +52 -0
  310. package/src/vibe/generic.ts +340 -339
  311. package/src/vibe/hooks/index.ts +367 -367
  312. package/src/vibe/index.ts +133 -133
  313. package/src/vibe/sessions.ts +925 -899
  314. package/src/vibe/types.ts +577 -577
@@ -1,167 +1,167 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { getSession as getBetterAuthSession } from '../../server/auth';
3
- import { getSession, updateSession } from '../../lib/session-store';
4
- import { logExceptionWithCorrelation } from '../../lib/safe-error-response';
5
- import { tokenLogId } from '../../lib/token-log-id';
6
-
7
- /**
8
- * ⚠️ DESTRUCTIVE TEST-ONLY HANDLER — DO NOT MOUNT IN PRODUCTION. ⚠️
9
- *
10
- * Force-expire access token for testing refresh flow. Sets the access token
11
- * expiry to 2 minutes in the past, which will trigger a refresh on the next
12
- * API call.
13
- *
14
- * THIS MUTATES SESSION STATE. It is a test affordance, not a product feature.
15
- *
16
- * ── WHY THIS WARNING EXISTS (QAPert 34983) ────────────────────────────────
17
- * The usage block below is not decoration: THIS DOCSTRING IS THE SPREAD
18
- * MECHANISM. It ships compiled into dist and into the .d.ts, so it reaches
19
- * every consumer's editor as hover text, and it is a one-line copy-paste that
20
- * mounts a destructive endpoint. Every one of the three surfaces this route
21
- * ended up on followed exactly these instructions.
22
- *
23
- * The handler itself was fixed (244877: default-deny behind ENABLE_TEST_ROUTES,
24
- * plus the caller-supplied-token IDOR removed). But a fixed handler shipped
25
- * with unqualified mounting instructions still spreads: the guidance was never
26
- * corrected alongside the code, and a package that documents a dangerous route
27
- * as a plain convenience is instructing the weaker configuration.
28
- *
29
- * ── IF YOU MOUNT IT, MOUNT IT LIKE THIS ───────────────────────────────────
30
- * Required: set ENABLE_TEST_ROUTES=true IN THAT ENVIRONMENT ONLY. Absent, empty
31
- * or any other value default-DENIES (404). Never set it in production.
32
- *
33
- * ```typescript
34
- * // app/api/test/force-expire/route.ts — TEST/DEV ENVIRONMENTS ONLY
35
- * // Requires ENABLE_TEST_ROUTES=true in that environment; 404s otherwise.
36
- * export { POST } from '@payez/next-mvp/api-handlers/test/force-expire';
37
- * ```
38
- *
39
- * If you do not need to exercise the refresh flow, DO NOT MOUNT THIS AT ALL.
40
- * The safest configuration is the absence of the route.
41
- */
42
- export const POST = async (req: NextRequest) => {
43
- // 244877 (package-level): DEFAULT DENY behind ENABLE_TEST_ROUTES.
44
- //
45
- // This handler MUTATES session state — it rewrites idpAccessTokenExpires two
46
- // minutes into the past — and was gated on nothing but "any signed-in
47
- // session": no role check, no env kill-switch. Because consuming apps mount
48
- // it with a one-line re-export, EVERY consumer inherited that, including
49
- // deployments on production hosts.
50
- //
51
- // The card was raised against one app's local copy. Sweeping the pattern
52
- // rather than the instance found the same route in three places — a local
53
- // copy in the graft, a local copy in idealvibe.online, and idealresume.online
54
- // re-exporting THIS handler — so the fix belongs here, at the source all
55
- // three ultimately derive from, not only in whichever copy was noticed first.
56
- //
57
- // The gate is the FIRST statement, before any session decode, so a disabled
58
- // build does not reveal that the route is live. Absent, empty, or any value
59
- // other than 'true' disables it. Distinguishable body on purpose: a bare 404
60
- // reads as "route missing / build broken" and sends someone debugging
61
- // deployment instead of reading a flag.
62
- //
63
- // A consumer that genuinely needs this in a test environment sets
64
- // ENABLE_TEST_ROUTES=true there, which is the same posture the token
65
- // inspector already uses for a strictly LESS dangerous, read-only route.
66
- if (process.env.ENABLE_TEST_ROUTES !== 'true') {
67
- // DISCLOSURE IS CONDITIONAL ON ENVIRONMENT (QAPert-NightHawk 34539, after
68
- // QAPert caught that my first version shipped the very leak we had just
69
- // criticised elsewhere).
70
- //
71
- // My original body named the flag: "Set ENABLE_TEST_ROUTES=true...". That
72
- // is the same class of disclosure as TOKEN_INSPECTOR_DISABLED — it confirms
73
- // the route exists, names the capability, and hands a prober the exact
74
- // switch that turns it on. Having argued against that pattern one directory
75
- // over, shipping it here would have been incoherent.
76
- //
77
- // Deleting the message outright is also wrong: it is what stops a developer
78
- // losing an hour deciding whether their build is broken. The tension
79
- // resolves by AUDIENCE, not by picking a side:
80
- // production -> 404, EMPTY body. Nothing about the capability, the
81
- // flag, or even that a handler is here.
82
- // non-production -> 404 + the helpful message naming the flag.
83
- //
84
- // The operator affordance survives in production too, via the log line
85
- // below: logs are visible to operators and not to probers, which is exactly
86
- // the split the response body cannot make.
87
- console.warn(
88
- '[FORCE-EXPIRE] blocked: ENABLE_TEST_ROUTES is not "true" (test routes default-deny).',
89
- );
90
-
91
- if (process.env.NODE_ENV === 'production') {
92
- return new NextResponse(null, { status: 404 });
93
- }
94
-
95
- return NextResponse.json(
96
- {
97
- error: 'TEST_ROUTES_DISABLED',
98
- message:
99
- 'Test routes are disabled in this build. Set ENABLE_TEST_ROUTES=true to enable them (development only).',
100
- },
101
- { status: 404 },
102
- );
103
- }
104
-
105
- try {
106
- const betterAuthSession = await getBetterAuthSession(req);
107
-
108
- // ACCESS CONTROL (QAPert 34553). This handler previously fell back to a
109
- // CALLER-SUPPLIED `X-Session-Token` header when the request carried no
110
- // session of its own, and then force-expired whatever token that header
111
- // named — with NO check that the token belonged to the caller. The 401
112
- // fired only when a caller supplied NEITHER a session NOR the header. So
113
- // an UNAUTHENTICATED caller who presented any valid session token could
114
- // expire ANOTHER USER'S session: an unauthenticated cross-session write.
115
- //
116
- // The fallback is removed rather than ownership-checked, because an
117
- // ownership check would make it exactly equivalent to reading the caller's
118
- // own session — the header could then never name anything else, so it
119
- // would be a parameter that cannot legally vary. The session to expire is
120
- // now ALWAYS the caller's own, which is also what the graft's local copy
121
- // has always done.
122
- //
123
- // This is defence in depth, not the only control: the ENABLE_TEST_ROUTES
124
- // gate above already 404s this handler by default. But the gate is
125
- // switched ON in test environments, and "unauthenticated users can expire
126
- // each other's sessions" is not acceptable there either — a control that
127
- // only holds while a feature is switched off is not an access control.
128
- //
129
- // Corrects NextPert's own scope claim on card 244877 ("a caller could only
130
- // ever expire their OWN session"): that was read off the graft's LOCAL
131
- // copy, which derives the token from the caller's JWT. It was never true
132
- // of THIS handler, which is the one consumers actually mount.
133
- const sessionToken = betterAuthSession?.session?.token as string | undefined;
134
- if (!sessionToken) {
135
- console.warn('[TEST_EXPIRE] No session on the request');
136
- return NextResponse.json({ success: false, error: 'No session token' }, { status: 401 });
137
- }
138
-
139
- const session = await getSession(sessionToken);
140
- if (!session) {
141
- return NextResponse.json({ success: false, error: 'Session not found' }, { status: 404 });
142
- }
143
-
144
- const now = Date.now();
145
- const forced = now - (2 * 60 * 1000); // two minutes ago
146
- const prev = session.idpAccessTokenExpires || null;
147
-
148
- await updateSession(sessionToken, { idpAccessTokenExpires: forced });
149
-
150
- console.log('[TEST_EXPIRE] Forced access token expiry for session', {
151
- sessionToken: tokenLogId(sessionToken),
152
- previous: prev ? new Date(prev).toISOString() : null,
153
- newExpiry: new Date(forced).toISOString()
154
- });
155
-
156
- return NextResponse.json({
157
- success: true,
158
- previous: prev,
159
- previousIso: prev ? new Date(prev).toISOString() : null,
160
- newExpiry: forced,
161
- newExpiryIso: new Date(forced).toISOString()
162
- });
163
- } catch (e) {
164
- const correlationId = logExceptionWithCorrelation('[TEST_EXPIRE] Error:', e);
165
- return NextResponse.json({ success: false, error: 'Failed to force expiry', code: 'INTERNAL_ERROR', correlationId }, { status: 500 });
166
- }
167
- };
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getSession as getBetterAuthSession } from '../../server/auth';
3
+ import { getSession, updateSession } from '../../lib/session-store';
4
+ import { logExceptionWithCorrelation } from '../../lib/safe-error-response';
5
+ import { tokenLogId } from '../../lib/token-log-id';
6
+
7
+ /**
8
+ * ⚠️ DESTRUCTIVE TEST-ONLY HANDLER — DO NOT MOUNT IN PRODUCTION. ⚠️
9
+ *
10
+ * Force-expire access token for testing refresh flow. Sets the access token
11
+ * expiry to 2 minutes in the past, which will trigger a refresh on the next
12
+ * API call.
13
+ *
14
+ * THIS MUTATES SESSION STATE. It is a test affordance, not a product feature.
15
+ *
16
+ * ── WHY THIS WARNING EXISTS (QAPert 34983) ────────────────────────────────
17
+ * The usage block below is not decoration: THIS DOCSTRING IS THE SPREAD
18
+ * MECHANISM. It ships compiled into dist and into the .d.ts, so it reaches
19
+ * every consumer's editor as hover text, and it is a one-line copy-paste that
20
+ * mounts a destructive endpoint. Every one of the three surfaces this route
21
+ * ended up on followed exactly these instructions.
22
+ *
23
+ * The handler itself was fixed (244877: default-deny behind ENABLE_TEST_ROUTES,
24
+ * plus the caller-supplied-token IDOR removed). But a fixed handler shipped
25
+ * with unqualified mounting instructions still spreads: the guidance was never
26
+ * corrected alongside the code, and a package that documents a dangerous route
27
+ * as a plain convenience is instructing the weaker configuration.
28
+ *
29
+ * ── IF YOU MOUNT IT, MOUNT IT LIKE THIS ───────────────────────────────────
30
+ * Required: set ENABLE_TEST_ROUTES=true IN THAT ENVIRONMENT ONLY. Absent, empty
31
+ * or any other value default-DENIES (404). Never set it in production.
32
+ *
33
+ * ```typescript
34
+ * // app/api/test/force-expire/route.ts — TEST/DEV ENVIRONMENTS ONLY
35
+ * // Requires ENABLE_TEST_ROUTES=true in that environment; 404s otherwise.
36
+ * export { POST } from '@payez/next-mvp/api-handlers/test/force-expire';
37
+ * ```
38
+ *
39
+ * If you do not need to exercise the refresh flow, DO NOT MOUNT THIS AT ALL.
40
+ * The safest configuration is the absence of the route.
41
+ */
42
+ export const POST = async (req: NextRequest) => {
43
+ // 244877 (package-level): DEFAULT DENY behind ENABLE_TEST_ROUTES.
44
+ //
45
+ // This handler MUTATES session state — it rewrites idpAccessTokenExpires two
46
+ // minutes into the past — and was gated on nothing but "any signed-in
47
+ // session": no role check, no env kill-switch. Because consuming apps mount
48
+ // it with a one-line re-export, EVERY consumer inherited that, including
49
+ // deployments on production hosts.
50
+ //
51
+ // The card was raised against one app's local copy. Sweeping the pattern
52
+ // rather than the instance found the same route in three places — a local
53
+ // copy in the graft, a local copy in idealvibe.online, and idealresume.online
54
+ // re-exporting THIS handler — so the fix belongs here, at the source all
55
+ // three ultimately derive from, not only in whichever copy was noticed first.
56
+ //
57
+ // The gate is the FIRST statement, before any session decode, so a disabled
58
+ // build does not reveal that the route is live. Absent, empty, or any value
59
+ // other than 'true' disables it. Distinguishable body on purpose: a bare 404
60
+ // reads as "route missing / build broken" and sends someone debugging
61
+ // deployment instead of reading a flag.
62
+ //
63
+ // A consumer that genuinely needs this in a test environment sets
64
+ // ENABLE_TEST_ROUTES=true there, which is the same posture the token
65
+ // inspector already uses for a strictly LESS dangerous, read-only route.
66
+ if (process.env.ENABLE_TEST_ROUTES !== 'true') {
67
+ // DISCLOSURE IS CONDITIONAL ON ENVIRONMENT (QAPert-NightHawk 34539, after
68
+ // QAPert caught that my first version shipped the very leak we had just
69
+ // criticised elsewhere).
70
+ //
71
+ // My original body named the flag: "Set ENABLE_TEST_ROUTES=true...". That
72
+ // is the same class of disclosure as TOKEN_INSPECTOR_DISABLED — it confirms
73
+ // the route exists, names the capability, and hands a prober the exact
74
+ // switch that turns it on. Having argued against that pattern one directory
75
+ // over, shipping it here would have been incoherent.
76
+ //
77
+ // Deleting the message outright is also wrong: it is what stops a developer
78
+ // losing an hour deciding whether their build is broken. The tension
79
+ // resolves by AUDIENCE, not by picking a side:
80
+ // production -> 404, EMPTY body. Nothing about the capability, the
81
+ // flag, or even that a handler is here.
82
+ // non-production -> 404 + the helpful message naming the flag.
83
+ //
84
+ // The operator affordance survives in production too, via the log line
85
+ // below: logs are visible to operators and not to probers, which is exactly
86
+ // the split the response body cannot make.
87
+ console.warn(
88
+ '[FORCE-EXPIRE] blocked: ENABLE_TEST_ROUTES is not "true" (test routes default-deny).',
89
+ );
90
+
91
+ if (process.env.NODE_ENV === 'production') {
92
+ return new NextResponse(null, { status: 404 });
93
+ }
94
+
95
+ return NextResponse.json(
96
+ {
97
+ error: 'TEST_ROUTES_DISABLED',
98
+ message:
99
+ 'Test routes are disabled in this build. Set ENABLE_TEST_ROUTES=true to enable them (development only).',
100
+ },
101
+ { status: 404 },
102
+ );
103
+ }
104
+
105
+ try {
106
+ const betterAuthSession = await getBetterAuthSession(req);
107
+
108
+ // ACCESS CONTROL (QAPert 34553). This handler previously fell back to a
109
+ // CALLER-SUPPLIED `X-Session-Token` header when the request carried no
110
+ // session of its own, and then force-expired whatever token that header
111
+ // named — with NO check that the token belonged to the caller. The 401
112
+ // fired only when a caller supplied NEITHER a session NOR the header. So
113
+ // an UNAUTHENTICATED caller who presented any valid session token could
114
+ // expire ANOTHER USER'S session: an unauthenticated cross-session write.
115
+ //
116
+ // The fallback is removed rather than ownership-checked, because an
117
+ // ownership check would make it exactly equivalent to reading the caller's
118
+ // own session — the header could then never name anything else, so it
119
+ // would be a parameter that cannot legally vary. The session to expire is
120
+ // now ALWAYS the caller's own, which is also what the graft's local copy
121
+ // has always done.
122
+ //
123
+ // This is defence in depth, not the only control: the ENABLE_TEST_ROUTES
124
+ // gate above already 404s this handler by default. But the gate is
125
+ // switched ON in test environments, and "unauthenticated users can expire
126
+ // each other's sessions" is not acceptable there either — a control that
127
+ // only holds while a feature is switched off is not an access control.
128
+ //
129
+ // Corrects NextPert's own scope claim on card 244877 ("a caller could only
130
+ // ever expire their OWN session"): that was read off the graft's LOCAL
131
+ // copy, which derives the token from the caller's JWT. It was never true
132
+ // of THIS handler, which is the one consumers actually mount.
133
+ const sessionToken = betterAuthSession?.session?.token as string | undefined;
134
+ if (!sessionToken) {
135
+ console.warn('[TEST_EXPIRE] No session on the request');
136
+ return NextResponse.json({ success: false, error: 'No session token' }, { status: 401 });
137
+ }
138
+
139
+ const session = await getSession(sessionToken);
140
+ if (!session) {
141
+ return NextResponse.json({ success: false, error: 'Session not found' }, { status: 404 });
142
+ }
143
+
144
+ const now = Date.now();
145
+ const forced = now - (2 * 60 * 1000); // two minutes ago
146
+ const prev = session.idpAccessTokenExpires || null;
147
+
148
+ await updateSession(sessionToken, { idpAccessTokenExpires: forced });
149
+
150
+ console.log('[TEST_EXPIRE] Forced access token expiry for session', {
151
+ sessionToken: tokenLogId(sessionToken),
152
+ previous: prev ? new Date(prev).toISOString() : null,
153
+ newExpiry: new Date(forced).toISOString()
154
+ });
155
+
156
+ return NextResponse.json({
157
+ success: true,
158
+ previous: prev,
159
+ previousIso: prev ? new Date(prev).toISOString() : null,
160
+ newExpiry: forced,
161
+ newExpiryIso: new Date(forced).toISOString()
162
+ });
163
+ } catch (e) {
164
+ const correlationId = logExceptionWithCorrelation('[TEST_EXPIRE] Error:', e);
165
+ return NextResponse.json({ success: false, error: 'Failed to force expiry', code: 'INTERNAL_ERROR', correlationId }, { status: 500 });
166
+ }
167
+ };