@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,99 +1,99 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { idpFetchJSON } from '../../lib/idp-fetch';
3
- import { ENV_CONFIG } from '../../config/env';
4
-
5
- /**
6
- * Account Profile API Handler
7
- *
8
- * GET /api/account/profile - Get user profile
9
- * PUT /api/account/profile - Update user profile
10
- *
11
- * WHY THIS WAS REWRITTEN (2026-08-02, QAPert defect #7)
12
- * Reported as "/api/account/profile 401s on the same cookie masked-info
13
- * accepts (200)". Two endpoints disagreeing about one cookie meant two auth
14
- * implementations, and they were:
15
- *
16
- * masked-info.ts idpFetchJSON(req, ...) resolves the caller's token
17
- * from the server-side session
18
- * profile.ts req.headers.get('authorization') required an INBOUND bearer
19
- *
20
- * A browser sends a session COOKIE and no Authorization header, so the old
21
- * profile.ts 401'd EVERY browser request by construction. Nothing was revoked
22
- * and no token was stale — the handler demanded a credential the app does not
23
- * use.
24
- *
25
- * THE OLD AUTH CHECK WAS A VENEER. It tested that the header was PRESENT, never
26
- * that it was valid, then forwarded it upstream unexamined. Any string at all
27
- * got past it and the IDP did the real work. It looked like enforcement and
28
- * enforced nothing.
29
- *
30
- * WHAT THE WORKAROUND COST US: consumers could not call this with a cookie, so
31
- * the graft's useProfile hook fetched the raw IDP access token from
32
- * /api/session/idp-token INTO BROWSER JAVASCRIPT and re-sent it as a bearer.
33
- * That defeats the reason idpFetchJSON exists — it keeps the token server-side
34
- * precisely so client-side script can never hold it. The bearer-only handler
35
- * did not merely fail; it pushed its callers into exposing the token.
36
- *
37
- * Switching to idpFetchJSON makes the cookie work, so the hook's token round
38
- * trip can be deleted and /api/session/idp-token drops to a single deliberate
39
- * caller: the jwt-inspect debug page.
40
- *
41
- * @package @payez/next-mvp
42
- */
43
-
44
- export async function GET(req: NextRequest) {
45
- const result = await idpFetchJSON(req, `${ENV_CONFIG.IDP_URL}/api/Account/profile`, {
46
- method: 'GET',
47
- headers: { 'Content-Type': 'application/json' },
48
- });
49
-
50
- if (!result.ok) {
51
- return NextResponse.json(
52
- {
53
- success: false,
54
- error: {
55
- code: 'UPSTREAM_SERVICE_ERROR',
56
- message: result.json?.message || 'Failed to fetch profile',
57
- status: result.status,
58
- },
59
- meta: { attemptedRefresh: result.attemptedRefresh },
60
- },
61
- { status: result.status }
62
- );
63
- }
64
-
65
- return NextResponse.json(result.json, { status: 200 });
66
- }
67
-
68
- export async function PUT(req: NextRequest) {
69
- let body = '{}';
70
- try {
71
- const raw = await req.text();
72
- if (raw && raw.trim().length > 0) body = raw;
73
- } catch {
74
- // Empty body is valid here; the IDP validates the payload.
75
- }
76
-
77
- const result = await idpFetchJSON(req, `${ENV_CONFIG.IDP_URL}/api/Account/profile`, {
78
- method: 'PUT',
79
- headers: { 'Content-Type': 'application/json' },
80
- body,
81
- });
82
-
83
- if (!result.ok) {
84
- return NextResponse.json(
85
- {
86
- success: false,
87
- error: {
88
- code: 'UPSTREAM_SERVICE_ERROR',
89
- message: result.json?.message || 'Failed to update profile',
90
- status: result.status,
91
- },
92
- meta: { attemptedRefresh: result.attemptedRefresh },
93
- },
94
- { status: result.status }
95
- );
96
- }
97
-
98
- return NextResponse.json(result.json, { status: 200 });
99
- }
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { idpFetchJSON } from '../../lib/idp-fetch';
3
+ import { ENV_CONFIG } from '../../config/env';
4
+
5
+ /**
6
+ * Account Profile API Handler
7
+ *
8
+ * GET /api/account/profile - Get user profile
9
+ * PUT /api/account/profile - Update user profile
10
+ *
11
+ * WHY THIS WAS REWRITTEN (2026-08-02, QAPert defect #7)
12
+ * Reported as "/api/account/profile 401s on the same cookie masked-info
13
+ * accepts (200)". Two endpoints disagreeing about one cookie meant two auth
14
+ * implementations, and they were:
15
+ *
16
+ * masked-info.ts idpFetchJSON(req, ...) resolves the caller's token
17
+ * from the server-side session
18
+ * profile.ts req.headers.get('authorization') required an INBOUND bearer
19
+ *
20
+ * A browser sends a session COOKIE and no Authorization header, so the old
21
+ * profile.ts 401'd EVERY browser request by construction. Nothing was revoked
22
+ * and no token was stale — the handler demanded a credential the app does not
23
+ * use.
24
+ *
25
+ * THE OLD AUTH CHECK WAS A VENEER. It tested that the header was PRESENT, never
26
+ * that it was valid, then forwarded it upstream unexamined. Any string at all
27
+ * got past it and the IDP did the real work. It looked like enforcement and
28
+ * enforced nothing.
29
+ *
30
+ * WHAT THE WORKAROUND COST US: consumers could not call this with a cookie, so
31
+ * the graft's useProfile hook fetched the raw IDP access token from
32
+ * /api/session/idp-token INTO BROWSER JAVASCRIPT and re-sent it as a bearer.
33
+ * That defeats the reason idpFetchJSON exists — it keeps the token server-side
34
+ * precisely so client-side script can never hold it. The bearer-only handler
35
+ * did not merely fail; it pushed its callers into exposing the token.
36
+ *
37
+ * Switching to idpFetchJSON makes the cookie work, so the hook's token round
38
+ * trip can be deleted and /api/session/idp-token drops to a single deliberate
39
+ * caller: the jwt-inspect debug page.
40
+ *
41
+ * @package @payez/next-mvp
42
+ */
43
+
44
+ export async function GET(req: NextRequest) {
45
+ const result = await idpFetchJSON(req, `${ENV_CONFIG.IDP_URL}/api/Account/profile`, {
46
+ method: 'GET',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ });
49
+
50
+ if (!result.ok) {
51
+ return NextResponse.json(
52
+ {
53
+ success: false,
54
+ error: {
55
+ code: 'UPSTREAM_SERVICE_ERROR',
56
+ message: result.json?.message || 'Failed to fetch profile',
57
+ status: result.status,
58
+ },
59
+ meta: { attemptedRefresh: result.attemptedRefresh },
60
+ },
61
+ { status: result.status }
62
+ );
63
+ }
64
+
65
+ return NextResponse.json(result.json, { status: 200 });
66
+ }
67
+
68
+ export async function PUT(req: NextRequest) {
69
+ let body = '{}';
70
+ try {
71
+ const raw = await req.text();
72
+ if (raw && raw.trim().length > 0) body = raw;
73
+ } catch {
74
+ // Empty body is valid here; the IDP validates the payload.
75
+ }
76
+
77
+ const result = await idpFetchJSON(req, `${ENV_CONFIG.IDP_URL}/api/Account/profile`, {
78
+ method: 'PUT',
79
+ headers: { 'Content-Type': 'application/json' },
80
+ body,
81
+ });
82
+
83
+ if (!result.ok) {
84
+ return NextResponse.json(
85
+ {
86
+ success: false,
87
+ error: {
88
+ code: 'UPSTREAM_SERVICE_ERROR',
89
+ message: result.json?.message || 'Failed to update profile',
90
+ status: result.status,
91
+ },
92
+ meta: { attemptedRefresh: result.attemptedRefresh },
93
+ },
94
+ { status: result.status }
95
+ );
96
+ }
97
+
98
+ return NextResponse.json(result.json, { status: 200 });
99
+ }
@@ -1,117 +1,117 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { ENV_CONFIG } from '../../../config/env';
3
- import { getIDPClientConfig } from '../../../lib/idp-client-config';
4
- import { resolveClientKey } from '../../../lib/client-key';
5
- import { idpErrorEnvelope } from '../../../lib/safe-error-response';
6
-
7
- /**
8
- * Account recovery - the ONE forwarder behind /recovery/send-code and /recovery/verify-code (PAY-1588).
9
- *
10
- * THE DEFECT (QAPert, W1 walk on :4797 -> dev-93 IdP 90f766bd0, 2026-09-19): portal /account-auth/recovery,
11
- * initiate -> 200, send-code -> 401 "Upstream error". The browser sends `Authorization: Bearer <recovery session
12
- * token>` (the token `initiate` minted); these handlers went through idpFetchJSON, which sets Authorization ONLY from
13
- * the server-side SESSION and never forwards the inbound header, so the IdP got no Bearer at all
14
- * (AccountController.cs:872-885 = 401 MISSING_TOKEN). Positive control direct to the IdP: without Authorization 401,
15
- * with it 200 and the email is sent.
16
- *
17
- * WHY THE 2026-08-02 REWRITE GOT IT WRONG. It reasoned "browsers send a session COOKIE and no Authorization header"
18
- * and moved both handlers onto idpFetchJSON. That is true of every OTHER authenticated route. It is false of this
19
- * flow: the caller is LOCKED OUT, so there is no session to resolve - the recovery session token IS the credential,
20
- * and the browser is the only party that holds it. Resolving a token from a session that does not exist is why the
21
- * entire recovery path, both halves, has been dead: it fails exactly when it is the only thing left that could help.
22
- *
23
- * WHAT THIS DOES INSTEAD
24
- * - Forwards the inbound `Authorization: Bearer <token>` to the IdP and nothing else from the caller's headers.
25
- * - Uses a direct fetch, NOT idpFetchJSON: a stray session belonging to someone else on the same browser must not
26
- * be able to override the recovery credential with its own bearer (idpFetchJSON's session bearer wins over any
27
- * caller-supplied Authorization).
28
- * - FAILS LOUD when there is no usable bearer: 401 `missing_recovery_token`, without calling upstream. The old
29
- * behaviour returned the IdP's opaque rejection, so the user saw recovery "not working" and the log showed an
30
- * upstream error rather than the auth fault it was.
31
- * - Shape-checks the token (a bearer scheme, one opaque token, sane length) before forwarding, so the header cannot
32
- * be used to smuggle anything but a credential to the IdP. It is never logged.
33
- * - Anonymous route (PAY-1438): on failure only the whitelisted envelope, never the IdP body.
34
- *
35
- * SERVER VALUE WINS for client_id (cards 268012 / 268410): applied AFTER the caller's payload spread, because these are
36
- * unauthenticated endpoints and a browser-supplied client_id would be a tenant-targeting lever on a password-reset path.
37
- *
38
- * @package @payez/next-mvp
39
- */
40
-
41
- /** One opaque token: JWT / base64url / opaque-hex alphabet, no whitespace or control characters, bounded length. */
42
- const TOKEN_SHAPE = /^[A-Za-z0-9\-._~+/]+=*$/;
43
-
44
- /** The recovery bearer from the inbound request, or null when absent / malformed. Never throws. */
45
- export function inboundRecoveryBearer(req: Pick<NextRequest, 'headers'>): string | null {
46
- const raw = req.headers.get('authorization');
47
- if (!raw) return null;
48
- const m = /^Bearer[ ]+(\S+)$/i.exec(raw.trim());
49
- if (!m) return null;
50
- const token = m[1];
51
- if (token.length < 16 || token.length > 4096) return null;
52
- return TOKEN_SHAPE.test(token) ? token : null;
53
- }
54
-
55
- async function recoveryClientKey(where: string): Promise<string | null> {
56
- try {
57
- const config = await getIDPClientConfig();
58
- return resolveClientKey(config, where).key;
59
- } catch (e) {
60
- console.error(`[${where}] could not resolve IDP client config for client_id:`, e);
61
- return null;
62
- }
63
- }
64
-
65
- export interface RecoveryForward {
66
- /** IdP path after the host, e.g. /api/Account/recovery/send-code */
67
- upstreamPath: string;
68
- /** log/route label used for the client-key resolver */
69
- where: string;
70
- /** stable `error` code for an upstream that could not be reached */
71
- failureCode: string;
72
- /** user-facing fallback when the IdP gave no message */
73
- fallbackMessage: string;
74
- }
75
-
76
- export async function forwardRecoveryCall(req: NextRequest, spec: RecoveryForward): Promise<NextResponse> {
77
- let payload: unknown = {};
78
- try {
79
- payload = await req.json();
80
- } catch {
81
- return NextResponse.json({ success: false, error: 'invalid_request', message: 'A JSON body is required' }, { status: 400 });
82
- }
83
-
84
- const bearer = inboundRecoveryBearer(req);
85
- if (!bearer) {
86
- return NextResponse.json(
87
- {
88
- success: false,
89
- error: 'missing_recovery_token',
90
- message: 'Your recovery session could not be found. Start the recovery again from the beginning.',
91
- },
92
- { status: 401 },
93
- );
94
- }
95
-
96
- const clientKey = await recoveryClientKey(spec.where);
97
-
98
- let upstream: Response;
99
- try {
100
- upstream = await fetch(`${ENV_CONFIG.IDP_URL}${spec.upstreamPath}`, {
101
- method: 'POST',
102
- headers: {
103
- 'Content-Type': 'application/json',
104
- Accept: 'application/json',
105
- Authorization: `Bearer ${bearer}`,
106
- ...(clientKey ? { 'X-Client-Id': clientKey } : {}),
107
- },
108
- body: JSON.stringify({ ...(payload as Record<string, unknown>), ...(clientKey ? { client_id: clientKey } : {}) }),
109
- });
110
- } catch {
111
- return NextResponse.json({ success: false, error: spec.failureCode }, { status: 502 });
112
- }
113
-
114
- const data = await upstream.json().catch(() => ({}));
115
- if (!upstream.ok) return NextResponse.json(idpErrorEnvelope(data, spec.fallbackMessage), { status: upstream.status });
116
- return NextResponse.json(data, { status: 200 });
117
- }
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { ENV_CONFIG } from '../../../config/env';
3
+ import { getIDPClientConfig } from '../../../lib/idp-client-config';
4
+ import { resolveClientKey } from '../../../lib/client-key';
5
+ import { idpErrorEnvelope } from '../../../lib/safe-error-response';
6
+
7
+ /**
8
+ * Account recovery - the ONE forwarder behind /recovery/send-code and /recovery/verify-code (PAY-1588).
9
+ *
10
+ * THE DEFECT (QAPert, W1 walk on :4797 -> dev-93 IdP 90f766bd0, 2026-09-19): portal /account-auth/recovery,
11
+ * initiate -> 200, send-code -> 401 "Upstream error". The browser sends `Authorization: Bearer <recovery session
12
+ * token>` (the token `initiate` minted); these handlers went through idpFetchJSON, which sets Authorization ONLY from
13
+ * the server-side SESSION and never forwards the inbound header, so the IdP got no Bearer at all
14
+ * (AccountController.cs:872-885 = 401 MISSING_TOKEN). Positive control direct to the IdP: without Authorization 401,
15
+ * with it 200 and the email is sent.
16
+ *
17
+ * WHY THE 2026-08-02 REWRITE GOT IT WRONG. It reasoned "browsers send a session COOKIE and no Authorization header"
18
+ * and moved both handlers onto idpFetchJSON. That is true of every OTHER authenticated route. It is false of this
19
+ * flow: the caller is LOCKED OUT, so there is no session to resolve - the recovery session token IS the credential,
20
+ * and the browser is the only party that holds it. Resolving a token from a session that does not exist is why the
21
+ * entire recovery path, both halves, has been dead: it fails exactly when it is the only thing left that could help.
22
+ *
23
+ * WHAT THIS DOES INSTEAD
24
+ * - Forwards the inbound `Authorization: Bearer <token>` to the IdP and nothing else from the caller's headers.
25
+ * - Uses a direct fetch, NOT idpFetchJSON: a stray session belonging to someone else on the same browser must not
26
+ * be able to override the recovery credential with its own bearer (idpFetchJSON's session bearer wins over any
27
+ * caller-supplied Authorization).
28
+ * - FAILS LOUD when there is no usable bearer: 401 `missing_recovery_token`, without calling upstream. The old
29
+ * behaviour returned the IdP's opaque rejection, so the user saw recovery "not working" and the log showed an
30
+ * upstream error rather than the auth fault it was.
31
+ * - Shape-checks the token (a bearer scheme, one opaque token, sane length) before forwarding, so the header cannot
32
+ * be used to smuggle anything but a credential to the IdP. It is never logged.
33
+ * - Anonymous route (PAY-1438): on failure only the whitelisted envelope, never the IdP body.
34
+ *
35
+ * SERVER VALUE WINS for client_id (cards 268012 / 268410): applied AFTER the caller's payload spread, because these are
36
+ * unauthenticated endpoints and a browser-supplied client_id would be a tenant-targeting lever on a password-reset path.
37
+ *
38
+ * @package @payez/next-mvp
39
+ */
40
+
41
+ /** One opaque token: JWT / base64url / opaque-hex alphabet, no whitespace or control characters, bounded length. */
42
+ const TOKEN_SHAPE = /^[A-Za-z0-9\-._~+/]+=*$/;
43
+
44
+ /** The recovery bearer from the inbound request, or null when absent / malformed. Never throws. */
45
+ export function inboundRecoveryBearer(req: Pick<NextRequest, 'headers'>): string | null {
46
+ const raw = req.headers.get('authorization');
47
+ if (!raw) return null;
48
+ const m = /^Bearer[ ]+(\S+)$/i.exec(raw.trim());
49
+ if (!m) return null;
50
+ const token = m[1];
51
+ if (token.length < 16 || token.length > 4096) return null;
52
+ return TOKEN_SHAPE.test(token) ? token : null;
53
+ }
54
+
55
+ async function recoveryClientKey(where: string): Promise<string | null> {
56
+ try {
57
+ const config = await getIDPClientConfig();
58
+ return resolveClientKey(config, where).key;
59
+ } catch (e) {
60
+ console.error(`[${where}] could not resolve IDP client config for client_id:`, e);
61
+ return null;
62
+ }
63
+ }
64
+
65
+ export interface RecoveryForward {
66
+ /** IdP path after the host, e.g. /api/Account/recovery/send-code */
67
+ upstreamPath: string;
68
+ /** log/route label used for the client-key resolver */
69
+ where: string;
70
+ /** stable `error` code for an upstream that could not be reached */
71
+ failureCode: string;
72
+ /** user-facing fallback when the IdP gave no message */
73
+ fallbackMessage: string;
74
+ }
75
+
76
+ export async function forwardRecoveryCall(req: NextRequest, spec: RecoveryForward): Promise<NextResponse> {
77
+ let payload: unknown = {};
78
+ try {
79
+ payload = await req.json();
80
+ } catch {
81
+ return NextResponse.json({ success: false, error: 'invalid_request', message: 'A JSON body is required' }, { status: 400 });
82
+ }
83
+
84
+ const bearer = inboundRecoveryBearer(req);
85
+ if (!bearer) {
86
+ return NextResponse.json(
87
+ {
88
+ success: false,
89
+ error: 'missing_recovery_token',
90
+ message: 'Your recovery session could not be found. Start the recovery again from the beginning.',
91
+ },
92
+ { status: 401 },
93
+ );
94
+ }
95
+
96
+ const clientKey = await recoveryClientKey(spec.where);
97
+
98
+ let upstream: Response;
99
+ try {
100
+ upstream = await fetch(`${ENV_CONFIG.IDP_URL}${spec.upstreamPath}`, {
101
+ method: 'POST',
102
+ headers: {
103
+ 'Content-Type': 'application/json',
104
+ Accept: 'application/json',
105
+ Authorization: `Bearer ${bearer}`,
106
+ ...(clientKey ? { 'X-Client-Id': clientKey } : {}),
107
+ },
108
+ body: JSON.stringify({ ...(payload as Record<string, unknown>), ...(clientKey ? { client_id: clientKey } : {}) }),
109
+ });
110
+ } catch {
111
+ return NextResponse.json({ success: false, error: spec.failureCode }, { status: 502 });
112
+ }
113
+
114
+ const data = await upstream.json().catch(() => ({}));
115
+ if (!upstream.ok) return NextResponse.json(idpErrorEnvelope(data, spec.fallbackMessage), { status: upstream.status });
116
+ return NextResponse.json(data, { status: 200 });
117
+ }
@@ -1,59 +1,59 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { getIDPClientConfig } from '../../../lib/idp-client-config';
3
- import { resolveClientKey } from '../../../lib/client-key';
4
- import { idpErrorEnvelope } from '../../../lib/safe-error-response';
5
-
6
- function getIdpUrl(): string {
7
- const url = process.env.IDP_URL;
8
- if (!url) throw new Error('[IDP_URL] FATAL: IDP_URL environment variable is REQUIRED.');
9
- return url;
10
- }
11
-
12
- /**
13
- * RECOVERY CLIENT KEY — cards 268012 (S12) and 268410.
14
- *
15
- * These four proxies populated NONE of PayEzClientIdResolver's six slots, so
16
- * RequirePayEzUnAuthorized 404'd the request BEFORE the action ran and no
17
- * recovery token was ever minted. That is why S12 failed at step 1.
18
- *
19
- * NOT `ENV_CONFIG.CLIENT_ID`, which is what card 268012 originally prescribed.
20
- * That variable is the ENVIRONMENT-SPECIFIC NUMERIC id, and the IDP accepts a
21
- * numeric with NO existence check (RequirePayEzUnAuthorizedAttribute.cs:31-33)
22
- * while it VALIDATES a slug (:41-47). Hardcoding the numeric here would have
23
- * added four more sites to the 268410 family and made a stale id answer for
24
- * the WRONG TENANT on a password-reset path, silently. Slug-preferring, with
25
- * the numeric fallback logged, is the same resolver the other nine sites use.
26
- *
27
- * SERVER VALUE WINS: client_id is applied AFTER the caller's payload spread.
28
- * These are unauthenticated recovery endpoints, so a browser-supplied
29
- * client_id would be a tenant-targeting lever. The caller does not get to
30
- * choose which tenant their password reset is addressed to.
31
- */
32
- async function recoveryClientKey(): Promise<string | null> {
33
- try {
34
- const config = await getIDPClientConfig();
35
- return resolveClientKey(config, 'account/recovery/initiate').key;
36
- } catch (e) {
37
- console.error('[account/recovery/initiate] could not resolve IDP client config for client_id:', e);
38
- return null;
39
- }
40
- }
41
-
42
- export async function POST(req: NextRequest) {
43
- const IDP_URL = getIdpUrl();
44
- try {
45
- const { email } = await req.json();
46
- const clientKey = await recoveryClientKey();
47
- const upstream = await fetch(`${IDP_URL}/api/Account/recovery/initiate`, {
48
- method: 'POST',
49
- headers: { 'Content-Type': 'application/json', ...(clientKey ? { 'X-Client-Id': clientKey } : {}) },
50
- body: JSON.stringify({ email, ...(clientKey ? { client_id: clientKey } : {}) }),
51
- });
52
- const data = await upstream.json().catch(() => ({}));
53
- // PAY-1438: anonymous route - on failure only the whitelisted envelope, never the IDP body.
54
- if (!upstream.ok) return NextResponse.json(idpErrorEnvelope(data, 'Failed to initiate recovery'), { status: upstream.status });
55
- return NextResponse.json(data, { status: upstream.status });
56
- } catch (e) {
57
- return NextResponse.json({ success: false, error: 'initiate_failed' }, { status: 500 });
58
- }
59
- }
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getIDPClientConfig } from '../../../lib/idp-client-config';
3
+ import { resolveClientKey } from '../../../lib/client-key';
4
+ import { idpErrorEnvelope } from '../../../lib/safe-error-response';
5
+
6
+ function getIdpUrl(): string {
7
+ const url = process.env.IDP_URL;
8
+ if (!url) throw new Error('[IDP_URL] FATAL: IDP_URL environment variable is REQUIRED.');
9
+ return url;
10
+ }
11
+
12
+ /**
13
+ * RECOVERY CLIENT KEY — cards 268012 (S12) and 268410.
14
+ *
15
+ * These four proxies populated NONE of PayEzClientIdResolver's six slots, so
16
+ * RequirePayEzUnAuthorized 404'd the request BEFORE the action ran and no
17
+ * recovery token was ever minted. That is why S12 failed at step 1.
18
+ *
19
+ * NOT `ENV_CONFIG.CLIENT_ID`, which is what card 268012 originally prescribed.
20
+ * That variable is the ENVIRONMENT-SPECIFIC NUMERIC id, and the IDP accepts a
21
+ * numeric with NO existence check (RequirePayEzUnAuthorizedAttribute.cs:31-33)
22
+ * while it VALIDATES a slug (:41-47). Hardcoding the numeric here would have
23
+ * added four more sites to the 268410 family and made a stale id answer for
24
+ * the WRONG TENANT on a password-reset path, silently. Slug-preferring, with
25
+ * the numeric fallback logged, is the same resolver the other nine sites use.
26
+ *
27
+ * SERVER VALUE WINS: client_id is applied AFTER the caller's payload spread.
28
+ * These are unauthenticated recovery endpoints, so a browser-supplied
29
+ * client_id would be a tenant-targeting lever. The caller does not get to
30
+ * choose which tenant their password reset is addressed to.
31
+ */
32
+ async function recoveryClientKey(): Promise<string | null> {
33
+ try {
34
+ const config = await getIDPClientConfig();
35
+ return resolveClientKey(config, 'account/recovery/initiate').key;
36
+ } catch (e) {
37
+ console.error('[account/recovery/initiate] could not resolve IDP client config for client_id:', e);
38
+ return null;
39
+ }
40
+ }
41
+
42
+ export async function POST(req: NextRequest) {
43
+ const IDP_URL = getIdpUrl();
44
+ try {
45
+ const { email } = await req.json();
46
+ const clientKey = await recoveryClientKey();
47
+ const upstream = await fetch(`${IDP_URL}/api/Account/recovery/initiate`, {
48
+ method: 'POST',
49
+ headers: { 'Content-Type': 'application/json', ...(clientKey ? { 'X-Client-Id': clientKey } : {}) },
50
+ body: JSON.stringify({ email, ...(clientKey ? { client_id: clientKey } : {}) }),
51
+ });
52
+ const data = await upstream.json().catch(() => ({}));
53
+ // PAY-1438: anonymous route - on failure only the whitelisted envelope, never the IDP body.
54
+ if (!upstream.ok) return NextResponse.json(idpErrorEnvelope(data, 'Failed to initiate recovery'), { status: upstream.status });
55
+ return NextResponse.json(data, { status: upstream.status });
56
+ } catch (e) {
57
+ return NextResponse.json({ success: false, error: 'initiate_failed' }, { status: 500 });
58
+ }
59
+ }
@@ -1,21 +1,21 @@
1
- import { NextRequest } from 'next/server';
2
- import { forwardRecoveryCall } from './forward';
3
-
4
- /**
5
- * Account recovery - send-code.
6
- *
7
- * PAY-1588: the recovery session token minted by `initiate` is the caller's only credential (they are locked out; there is
8
- * no session), so this forwards the inbound `Authorization: Bearer <token>` to the IdP and fails loud (401
9
- * `missing_recovery_token`) when it is absent. The whole reasoning, and why the 2026-08-02 idpFetchJSON rewrite killed
10
- * this flow, is in ./forward.ts. send-code and verify-code share that ONE forwarder so they cannot drift apart again.
11
- *
12
- * @package @payez/next-mvp
13
- */
14
- export async function POST(req: NextRequest) {
15
- return forwardRecoveryCall(req, {
16
- upstreamPath: '/api/Account/recovery/send-code',
17
- where: 'account/recovery/send-code',
18
- failureCode: 'send_code_failed',
19
- fallbackMessage: 'Failed to send the verification code',
20
- });
21
- }
1
+ import { NextRequest } from 'next/server';
2
+ import { forwardRecoveryCall } from './forward';
3
+
4
+ /**
5
+ * Account recovery - send-code.
6
+ *
7
+ * PAY-1588: the recovery session token minted by `initiate` is the caller's only credential (they are locked out; there is
8
+ * no session), so this forwards the inbound `Authorization: Bearer <token>` to the IdP and fails loud (401
9
+ * `missing_recovery_token`) when it is absent. The whole reasoning, and why the 2026-08-02 idpFetchJSON rewrite killed
10
+ * this flow, is in ./forward.ts. send-code and verify-code share that ONE forwarder so they cannot drift apart again.
11
+ *
12
+ * @package @payez/next-mvp
13
+ */
14
+ export async function POST(req: NextRequest) {
15
+ return forwardRecoveryCall(req, {
16
+ upstreamPath: '/api/Account/recovery/send-code',
17
+ where: 'account/recovery/send-code',
18
+ failureCode: 'send_code_failed',
19
+ fallbackMessage: 'Failed to send the verification code',
20
+ });
21
+ }
@@ -1,20 +1,20 @@
1
- import { NextRequest } from 'next/server';
2
- import { forwardRecoveryCall } from './forward';
3
-
4
- /**
5
- * Account recovery - verify-code.
6
- *
7
- * PAY-1588: forwards the inbound recovery `Authorization: Bearer <token>` to the IdP and fails loud (401
8
- * `missing_recovery_token`) when it is absent. See ./forward.ts for the full reasoning; send-code and verify-code share
9
- * that ONE forwarder so they cannot drift apart again.
10
- *
11
- * @package @payez/next-mvp
12
- */
13
- export async function POST(req: NextRequest) {
14
- return forwardRecoveryCall(req, {
15
- upstreamPath: '/api/Account/recovery/verify-code',
16
- where: 'account/recovery/verify-code',
17
- failureCode: 'verify_code_failed',
18
- fallbackMessage: 'Failed to verify the code',
19
- });
20
- }
1
+ import { NextRequest } from 'next/server';
2
+ import { forwardRecoveryCall } from './forward';
3
+
4
+ /**
5
+ * Account recovery - verify-code.
6
+ *
7
+ * PAY-1588: forwards the inbound recovery `Authorization: Bearer <token>` to the IdP and fails loud (401
8
+ * `missing_recovery_token`) when it is absent. See ./forward.ts for the full reasoning; send-code and verify-code share
9
+ * that ONE forwarder so they cannot drift apart again.
10
+ *
11
+ * @package @payez/next-mvp
12
+ */
13
+ export async function POST(req: NextRequest) {
14
+ return forwardRecoveryCall(req, {
15
+ upstreamPath: '/api/Account/recovery/verify-code',
16
+ where: 'account/recovery/verify-code',
17
+ failureCode: 'verify_code_failed',
18
+ fallbackMessage: 'Failed to verify the code',
19
+ });
20
+ }