@payez/next-mvp 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (654) hide show
  1. package/README.md +782 -0
  2. package/dist/api/auth-handler.d.ts +67 -0
  3. package/dist/api/auth-handler.js +397 -0
  4. package/dist/api/index.d.ts +10 -0
  5. package/dist/api/index.js +19 -0
  6. package/dist/api-handlers/account/change-password.d.ts +9 -0
  7. package/dist/api-handlers/account/change-password.js +112 -0
  8. package/dist/api-handlers/account/masked-info.d.ts +2 -0
  9. package/dist/api-handlers/account/masked-info.js +41 -0
  10. package/dist/api-handlers/account/profile.d.ts +3 -0
  11. package/dist/api-handlers/account/profile.js +63 -0
  12. package/dist/api-handlers/account/recovery/initiate.d.ts +2 -0
  13. package/dist/api-handlers/account/recovery/initiate.js +26 -0
  14. package/dist/api-handlers/account/recovery/send-code.d.ts +2 -0
  15. package/dist/api-handlers/account/recovery/send-code.js +28 -0
  16. package/dist/api-handlers/account/recovery/verify-code.d.ts +2 -0
  17. package/dist/api-handlers/account/recovery/verify-code.js +28 -0
  18. package/dist/api-handlers/account/reset-password.d.ts +2 -0
  19. package/dist/api-handlers/account/reset-password.js +26 -0
  20. package/dist/api-handlers/account/send-code.d.ts +24 -0
  21. package/dist/api-handlers/account/send-code.js +60 -0
  22. package/dist/api-handlers/account/update-phone.d.ts +27 -0
  23. package/dist/api-handlers/account/update-phone.js +64 -0
  24. package/dist/api-handlers/account/validate-password.d.ts +17 -0
  25. package/dist/api-handlers/account/validate-password.js +81 -0
  26. package/dist/api-handlers/account/verify-email.d.ts +26 -0
  27. package/dist/api-handlers/account/verify-email.js +106 -0
  28. package/dist/api-handlers/account/verify-sms.d.ts +26 -0
  29. package/dist/api-handlers/account/verify-sms.js +106 -0
  30. package/dist/api-handlers/admin/analytics.d.ts +20 -0
  31. package/dist/api-handlers/admin/analytics.js +379 -0
  32. package/dist/api-handlers/admin/audit.d.ts +20 -0
  33. package/dist/api-handlers/admin/audit.js +214 -0
  34. package/dist/api-handlers/admin/index.d.ts +21 -0
  35. package/dist/api-handlers/admin/index.js +41 -0
  36. package/dist/api-handlers/admin/redis-sessions.d.ts +36 -0
  37. package/dist/api-handlers/admin/redis-sessions.js +204 -0
  38. package/dist/api-handlers/admin/sessions.d.ts +21 -0
  39. package/dist/api-handlers/admin/sessions.js +284 -0
  40. package/dist/api-handlers/admin/site-logs.d.ts +46 -0
  41. package/dist/api-handlers/admin/site-logs.js +318 -0
  42. package/dist/api-handlers/admin/users.d.ts +20 -0
  43. package/dist/api-handlers/admin/users.js +222 -0
  44. package/dist/api-handlers/admin/vibe-data.d.ts +80 -0
  45. package/dist/api-handlers/admin/vibe-data.js +268 -0
  46. package/dist/api-handlers/anon/preferences.d.ts +37 -0
  47. package/dist/api-handlers/anon/preferences.js +96 -0
  48. package/dist/api-handlers/auth/jwks.d.ts +2 -0
  49. package/dist/api-handlers/auth/jwks.js +24 -0
  50. package/dist/api-handlers/auth/login.d.ts +42 -0
  51. package/dist/api-handlers/auth/login.js +178 -0
  52. package/dist/api-handlers/auth/refresh.d.ts +74 -0
  53. package/dist/api-handlers/auth/refresh.js +635 -0
  54. package/dist/api-handlers/auth/signout.d.ts +37 -0
  55. package/dist/api-handlers/auth/signout.js +187 -0
  56. package/dist/api-handlers/auth/status.d.ts +8 -0
  57. package/dist/api-handlers/auth/status.js +26 -0
  58. package/dist/api-handlers/auth/update-session.d.ts +37 -0
  59. package/dist/api-handlers/auth/update-session.js +95 -0
  60. package/dist/api-handlers/auth/validate.d.ts +6 -0
  61. package/dist/api-handlers/auth/validate.js +43 -0
  62. package/dist/api-handlers/auth/verify-code.d.ts +43 -0
  63. package/dist/api-handlers/auth/verify-code.js +94 -0
  64. package/dist/api-handlers/session/refresh-viability.d.ts +14 -0
  65. package/dist/api-handlers/session/refresh-viability.js +39 -0
  66. package/dist/api-handlers/session/viability.d.ts +13 -0
  67. package/dist/api-handlers/session/viability.js +146 -0
  68. package/dist/api-handlers/test/force-expire.d.ts +23 -0
  69. package/dist/api-handlers/test/force-expire.js +65 -0
  70. package/dist/auth/auth-decision.d.ts +39 -0
  71. package/dist/auth/auth-decision.js +182 -0
  72. package/dist/auth/auth-options.d.ts +57 -0
  73. package/dist/auth/auth-options.js +213 -0
  74. package/dist/auth/callbacks/index.d.ts +6 -0
  75. package/dist/auth/callbacks/index.js +12 -0
  76. package/dist/auth/callbacks/jwt.d.ts +45 -0
  77. package/dist/auth/callbacks/jwt.js +305 -0
  78. package/dist/auth/callbacks/session.d.ts +60 -0
  79. package/dist/auth/callbacks/session.js +170 -0
  80. package/dist/auth/callbacks/signin.d.ts +23 -0
  81. package/dist/auth/callbacks/signin.js +44 -0
  82. package/dist/auth/events/index.d.ts +4 -0
  83. package/dist/auth/events/index.js +8 -0
  84. package/dist/auth/events/signout.d.ts +17 -0
  85. package/dist/auth/events/signout.js +32 -0
  86. package/dist/auth/providers/credentials.d.ts +32 -0
  87. package/dist/auth/providers/credentials.js +223 -0
  88. package/dist/auth/providers/index.d.ts +5 -0
  89. package/dist/auth/providers/index.js +21 -0
  90. package/dist/auth/providers/oauth.d.ts +26 -0
  91. package/dist/auth/providers/oauth.js +105 -0
  92. package/dist/auth/route-config.d.ts +66 -0
  93. package/dist/auth/route-config.js +190 -0
  94. package/dist/auth/types/auth-types.d.ts +417 -0
  95. package/dist/auth/types/auth-types.js +53 -0
  96. package/dist/auth/types/index.d.ts +6 -0
  97. package/dist/auth/types/index.js +22 -0
  98. package/dist/auth/unauthenticated-routes.d.ts +1 -0
  99. package/dist/auth/unauthenticated-routes.js +19 -0
  100. package/dist/auth/utils/idp-client.d.ts +94 -0
  101. package/dist/auth/utils/idp-client.js +383 -0
  102. package/dist/auth/utils/index.d.ts +5 -0
  103. package/dist/auth/utils/index.js +21 -0
  104. package/dist/auth/utils/token-utils.d.ts +84 -0
  105. package/dist/auth/utils/token-utils.js +219 -0
  106. package/dist/client/AuthContext.d.ts +19 -0
  107. package/dist/client/AuthContext.js +112 -0
  108. package/dist/client/fetch-with-auth.d.ts +11 -0
  109. package/dist/client/fetch-with-auth.js +44 -0
  110. package/dist/client/fetchWithSession.d.ts +3 -0
  111. package/dist/client/fetchWithSession.js +24 -0
  112. package/dist/client/index.d.ts +9 -0
  113. package/dist/client/index.js +20 -0
  114. package/dist/client/useAnonSession.d.ts +36 -0
  115. package/dist/client/useAnonSession.js +99 -0
  116. package/dist/components/SessionSync.d.ts +13 -0
  117. package/dist/components/SessionSync.js +119 -0
  118. package/dist/components/SignalRHealthCheck.d.ts +10 -0
  119. package/dist/components/SignalRHealthCheck.js +97 -0
  120. package/dist/components/account/UserAvatarMenu.d.ts +20 -0
  121. package/dist/components/account/UserAvatarMenu.js +80 -0
  122. package/dist/components/account/index.d.ts +7 -0
  123. package/dist/components/account/index.js +10 -0
  124. package/dist/components/admin/AlertSettingsTab.d.ts +48 -0
  125. package/dist/components/admin/AlertSettingsTab.js +351 -0
  126. package/dist/components/admin/AnalyticsTab.d.ts +22 -0
  127. package/dist/components/admin/AnalyticsTab.js +167 -0
  128. package/dist/components/admin/DataBrowserTab.d.ts +19 -0
  129. package/dist/components/admin/DataBrowserTab.js +252 -0
  130. package/dist/components/admin/LoggingSettingsTab.d.ts +73 -0
  131. package/dist/components/admin/LoggingSettingsTab.js +339 -0
  132. package/dist/components/admin/SessionsTab.d.ts +37 -0
  133. package/dist/components/admin/SessionsTab.js +165 -0
  134. package/dist/components/admin/StatsTab.d.ts +53 -0
  135. package/dist/components/admin/StatsTab.js +161 -0
  136. package/dist/components/admin/VibeAdminContext.d.ts +32 -0
  137. package/dist/components/admin/VibeAdminContext.js +38 -0
  138. package/dist/components/admin/VibeAdminLayout.d.ts +11 -0
  139. package/dist/components/admin/VibeAdminLayout.js +69 -0
  140. package/dist/components/admin/index.d.ts +29 -0
  141. package/dist/components/admin/index.js +44 -0
  142. package/dist/components/auth/FederatedAuthSection.d.ts +8 -0
  143. package/dist/components/auth/FederatedAuthSection.js +45 -0
  144. package/dist/components/auth/ModeAwareLoginPage.d.ts +10 -0
  145. package/dist/components/auth/ModeAwareLoginPage.js +42 -0
  146. package/dist/components/auth/ModeAwareSignupPage.d.ts +9 -0
  147. package/dist/components/auth/ModeAwareSignupPage.js +78 -0
  148. package/dist/components/auth/TraditionalAuthSection.d.ts +14 -0
  149. package/dist/components/auth/TraditionalAuthSection.js +20 -0
  150. package/dist/components/recovery/CompleteStep.d.ts +5 -0
  151. package/dist/components/recovery/CompleteStep.js +8 -0
  152. package/dist/components/recovery/InitiateRecoveryStep.d.ts +8 -0
  153. package/dist/components/recovery/InitiateRecoveryStep.js +20 -0
  154. package/dist/components/recovery/SelectMethodStep.d.ts +8 -0
  155. package/dist/components/recovery/SelectMethodStep.js +8 -0
  156. package/dist/components/recovery/SetPasswordStep.d.ts +6 -0
  157. package/dist/components/recovery/SetPasswordStep.js +20 -0
  158. package/dist/components/recovery/VerifyCodeStep.d.ts +10 -0
  159. package/dist/components/recovery/VerifyCodeStep.js +24 -0
  160. package/dist/components/reserved/ReservedRecoveryWarning.d.ts +38 -0
  161. package/dist/components/reserved/ReservedRecoveryWarning.js +92 -0
  162. package/dist/components/reserved/ReservedStatusBox.d.ts +30 -0
  163. package/dist/components/reserved/ReservedStatusBox.js +71 -0
  164. package/dist/components/ui/BetaBadge.d.ts +29 -0
  165. package/dist/components/ui/BetaBadge.js +38 -0
  166. package/dist/components/ui/Footer.d.ts +37 -0
  167. package/dist/components/ui/Footer.js +41 -0
  168. package/dist/config/env.d.ts +66 -0
  169. package/dist/config/env.js +57 -0
  170. package/dist/config/logger.d.ts +57 -0
  171. package/dist/config/logger.js +73 -0
  172. package/dist/config/logging-config.d.ts +30 -0
  173. package/dist/config/logging-config.js +122 -0
  174. package/dist/config/unauthenticated-routes.d.ts +17 -0
  175. package/dist/config/unauthenticated-routes.js +24 -0
  176. package/dist/config/vibe-log-transport.d.ts +79 -0
  177. package/dist/config/vibe-log-transport.js +203 -0
  178. package/dist/edge/internal-api-url.d.ts +53 -0
  179. package/dist/edge/internal-api-url.js +63 -0
  180. package/dist/edge/middleware.d.ts +14 -0
  181. package/dist/edge/middleware.js +32 -0
  182. package/dist/hooks/useAuth.d.ts +23 -0
  183. package/dist/hooks/useAuth.js +81 -0
  184. package/dist/hooks/useAuthSettings.d.ts +59 -0
  185. package/dist/hooks/useAuthSettings.js +93 -0
  186. package/dist/hooks/useAvailableProviders.d.ts +45 -0
  187. package/dist/hooks/useAvailableProviders.js +108 -0
  188. package/dist/hooks/usePasswordValidation.d.ts +27 -0
  189. package/dist/hooks/usePasswordValidation.js +102 -0
  190. package/dist/hooks/useProfile.d.ts +15 -0
  191. package/dist/hooks/useProfile.js +59 -0
  192. package/dist/hooks/usePublicAuthSettings.d.ts +56 -0
  193. package/dist/hooks/usePublicAuthSettings.js +131 -0
  194. package/dist/hooks/useSessionExpiration.d.ts +57 -0
  195. package/dist/hooks/useSessionExpiration.js +72 -0
  196. package/dist/hooks/useViabilitySession.d.ts +75 -0
  197. package/dist/hooks/useViabilitySession.js +268 -0
  198. package/dist/index.d.ts +12 -0
  199. package/dist/index.js +54 -0
  200. package/dist/lib/anon-session.d.ts +74 -0
  201. package/dist/lib/anon-session.js +169 -0
  202. package/dist/lib/api-handler.d.ts +123 -0
  203. package/dist/lib/api-handler.js +478 -0
  204. package/dist/lib/app-slug.d.ts +95 -0
  205. package/dist/lib/app-slug.js +172 -0
  206. package/dist/lib/demo-mode.d.ts +6 -0
  207. package/dist/lib/demo-mode.js +16 -0
  208. package/dist/lib/geolocation.d.ts +64 -0
  209. package/dist/lib/geolocation.js +235 -0
  210. package/dist/lib/idp-client-config.d.ts +75 -0
  211. package/dist/lib/idp-client-config.js +351 -0
  212. package/dist/lib/idp-fetch.d.ts +14 -0
  213. package/dist/lib/idp-fetch.js +91 -0
  214. package/dist/lib/internal-api.d.ts +87 -0
  215. package/dist/lib/internal-api.js +122 -0
  216. package/dist/lib/jwt-decode-client.d.ts +10 -0
  217. package/dist/lib/jwt-decode-client.js +46 -0
  218. package/dist/lib/jwt-decode.d.ts +48 -0
  219. package/dist/lib/jwt-decode.js +57 -0
  220. package/dist/lib/nextauth-secret.d.ts +10 -0
  221. package/dist/lib/nextauth-secret.js +104 -0
  222. package/dist/lib/rate-limit-service.d.ts +23 -0
  223. package/dist/lib/rate-limit-service.js +6 -0
  224. package/dist/lib/redis.d.ts +5 -0
  225. package/dist/lib/redis.js +28 -0
  226. package/dist/lib/refresh-token-validator.d.ts +13 -0
  227. package/dist/lib/refresh-token-validator.js +117 -0
  228. package/dist/lib/roles.d.ts +145 -0
  229. package/dist/lib/roles.js +168 -0
  230. package/dist/lib/secret-validation.d.ts +4 -0
  231. package/dist/lib/secret-validation.js +14 -0
  232. package/dist/lib/session-store.d.ts +166 -0
  233. package/dist/lib/session-store.js +537 -0
  234. package/dist/lib/session.d.ts +21 -0
  235. package/dist/lib/session.js +26 -0
  236. package/dist/lib/site-logger.d.ts +214 -0
  237. package/dist/lib/site-logger.js +210 -0
  238. package/dist/lib/standardized-client-api.d.ts +161 -0
  239. package/dist/lib/standardized-client-api.js +786 -0
  240. package/dist/lib/startup-init.d.ts +40 -0
  241. package/dist/lib/startup-init.js +261 -0
  242. package/dist/lib/test-aware-get-token.d.ts +2 -0
  243. package/dist/lib/test-aware-get-token.js +81 -0
  244. package/dist/lib/token-expiry.d.ts +14 -0
  245. package/dist/lib/token-expiry.js +39 -0
  246. package/dist/lib/token-lifecycle.d.ts +52 -0
  247. package/dist/lib/token-lifecycle.js +398 -0
  248. package/dist/lib/types/api-responses.d.ts +128 -0
  249. package/dist/lib/types/api-responses.js +171 -0
  250. package/dist/lib/user-agent-parser.d.ts +50 -0
  251. package/dist/lib/user-agent-parser.js +220 -0
  252. package/dist/logging/api/admin-analytics.d.ts +3 -0
  253. package/dist/logging/api/admin-analytics.js +45 -0
  254. package/dist/logging/api/audit-log.d.ts +3 -0
  255. package/dist/logging/api/audit-log.js +52 -0
  256. package/dist/logging/components/AdminAnalyticsLayout.d.ts +10 -0
  257. package/dist/logging/components/AdminAnalyticsLayout.js +11 -0
  258. package/dist/logging/components/AuditLogViewer.d.ts +7 -0
  259. package/dist/logging/components/AuditLogViewer.js +51 -0
  260. package/dist/logging/components/ErrorMetricsCard.d.ts +7 -0
  261. package/dist/logging/components/ErrorMetricsCard.js +16 -0
  262. package/dist/logging/components/HealthMetricsCard.d.ts +7 -0
  263. package/dist/logging/components/HealthMetricsCard.js +19 -0
  264. package/dist/logging/hooks/useAdminAnalytics.d.ts +24 -0
  265. package/dist/logging/hooks/useAdminAnalytics.js +22 -0
  266. package/dist/logging/hooks/useAuditLog.d.ts +6 -0
  267. package/dist/logging/hooks/useAuditLog.js +25 -0
  268. package/dist/logging/hooks/useErrorMetrics.d.ts +6 -0
  269. package/dist/logging/hooks/useErrorMetrics.js +38 -0
  270. package/dist/logging/hooks/useHealthMetrics.d.ts +6 -0
  271. package/dist/logging/hooks/useHealthMetrics.js +41 -0
  272. package/dist/logging/index.d.ts +11 -0
  273. package/dist/logging/index.js +40 -0
  274. package/dist/logging/types/analytics.d.ts +68 -0
  275. package/dist/logging/types/analytics.js +3 -0
  276. package/dist/logging/types/audit.d.ts +29 -0
  277. package/dist/logging/types/audit.js +2 -0
  278. package/dist/logging/types/index.d.ts +2 -0
  279. package/dist/logging/types/index.js +19 -0
  280. package/dist/middleware/auth-decision.d.ts +33 -0
  281. package/dist/middleware/auth-decision.js +65 -0
  282. package/dist/middleware/create-middleware.d.ts +100 -0
  283. package/dist/middleware/create-middleware.js +445 -0
  284. package/dist/middleware/rbac-check.d.ts +44 -0
  285. package/dist/middleware/rbac-check.js +191 -0
  286. package/dist/middleware/twofa-presets.d.ts +134 -0
  287. package/dist/middleware/twofa-presets.js +175 -0
  288. package/dist/models/DecodedAccessToken.d.ts +17 -0
  289. package/dist/models/DecodedAccessToken.js +2 -0
  290. package/dist/models/SessionModel.d.ts +122 -0
  291. package/dist/models/SessionModel.js +136 -0
  292. package/dist/pages/admin-login/page.d.ts +31 -0
  293. package/dist/pages/admin-login/page.js +83 -0
  294. package/dist/pages/admin-roles/RolesAdminPage.d.ts +15 -0
  295. package/dist/pages/admin-roles/RolesAdminPage.js +78 -0
  296. package/dist/pages/admin-roles/index.d.ts +8 -0
  297. package/dist/pages/admin-roles/index.js +15 -0
  298. package/dist/pages/admin-roles/modals.d.ts +72 -0
  299. package/dist/pages/admin-roles/modals.js +154 -0
  300. package/dist/pages/client-admin/ClientSiteAdminPage.d.ts +79 -0
  301. package/dist/pages/client-admin/ClientSiteAdminPage.js +177 -0
  302. package/dist/pages/client-admin/index.d.ts +32 -0
  303. package/dist/pages/client-admin/index.js +37 -0
  304. package/dist/pages/login/page.d.ts +22 -0
  305. package/dist/pages/login/page.js +239 -0
  306. package/dist/pages/profile/EnhancedProfilePage.d.ts +13 -0
  307. package/dist/pages/profile/EnhancedProfilePage.js +150 -0
  308. package/dist/pages/profile/index.d.ts +8 -0
  309. package/dist/pages/profile/index.js +16 -0
  310. package/dist/pages/profile/page.d.ts +19 -0
  311. package/dist/pages/profile/page.js +47 -0
  312. package/dist/pages/profile/profile-patch.d.ts +1 -0
  313. package/dist/pages/profile/profile-patch.js +281 -0
  314. package/dist/pages/recovery/page.d.ts +1 -0
  315. package/dist/pages/recovery/page.js +142 -0
  316. package/dist/pages/roles/MyRolesPage.d.ts +24 -0
  317. package/dist/pages/roles/MyRolesPage.js +71 -0
  318. package/dist/pages/roles/components.d.ts +63 -0
  319. package/dist/pages/roles/components.js +108 -0
  320. package/dist/pages/roles/index.d.ts +8 -0
  321. package/dist/pages/roles/index.js +19 -0
  322. package/dist/pages/security/EnhancedSecurityPage.d.ts +14 -0
  323. package/dist/pages/security/EnhancedSecurityPage.js +248 -0
  324. package/dist/pages/security/index.d.ts +8 -0
  325. package/dist/pages/security/index.js +16 -0
  326. package/dist/pages/security/page.d.ts +21 -0
  327. package/dist/pages/security/page.js +212 -0
  328. package/dist/pages/security/security-patch.d.ts +1 -0
  329. package/dist/pages/security/security-patch.js +302 -0
  330. package/dist/pages/settings/EnhancedSettingsPage.d.ts +46 -0
  331. package/dist/pages/settings/EnhancedSettingsPage.js +231 -0
  332. package/dist/pages/settings/index.d.ts +8 -0
  333. package/dist/pages/settings/index.js +16 -0
  334. package/dist/pages/settings/page.d.ts +7 -0
  335. package/dist/pages/settings/page.js +26 -0
  336. package/dist/pages/showcase/ShowcasePage.d.ts +13 -0
  337. package/dist/pages/showcase/ShowcasePage.js +140 -0
  338. package/dist/pages/showcase/index.d.ts +12 -0
  339. package/dist/pages/showcase/index.js +17 -0
  340. package/dist/pages/test-env/EmergencyLogoutPage.d.ts +14 -0
  341. package/dist/pages/test-env/EmergencyLogoutPage.js +98 -0
  342. package/dist/pages/test-env/JwtInspectPage.d.ts +14 -0
  343. package/dist/pages/test-env/JwtInspectPage.js +114 -0
  344. package/dist/pages/test-env/RefreshTokenPage.d.ts +15 -0
  345. package/dist/pages/test-env/RefreshTokenPage.js +91 -0
  346. package/dist/pages/test-env/TestEnvPage.d.ts +13 -0
  347. package/dist/pages/test-env/TestEnvPage.js +49 -0
  348. package/dist/pages/test-env/index.d.ts +24 -0
  349. package/dist/pages/test-env/index.js +32 -0
  350. package/dist/pages/verify-code/page.d.ts +30 -0
  351. package/dist/pages/verify-code/page.js +408 -0
  352. package/dist/routes/account/index.d.ts +28 -0
  353. package/dist/routes/account/index.js +71 -0
  354. package/dist/routes/account/masked-info.d.ts +33 -0
  355. package/dist/routes/account/masked-info.js +39 -0
  356. package/dist/routes/account/send-code.d.ts +37 -0
  357. package/dist/routes/account/send-code.js +42 -0
  358. package/dist/routes/account/update-phone.d.ts +13 -0
  359. package/dist/routes/account/update-phone.js +17 -0
  360. package/dist/routes/account/verify-email.d.ts +38 -0
  361. package/dist/routes/account/verify-email.js +43 -0
  362. package/dist/routes/account/verify-sms.d.ts +38 -0
  363. package/dist/routes/account/verify-sms.js +43 -0
  364. package/dist/routes/auth/index.d.ts +19 -0
  365. package/dist/routes/auth/index.js +64 -0
  366. package/dist/routes/auth/logout.d.ts +31 -0
  367. package/dist/routes/auth/logout.js +113 -0
  368. package/dist/routes/auth/nextauth.d.ts +19 -0
  369. package/dist/routes/auth/nextauth.js +72 -0
  370. package/dist/routes/auth/refresh.d.ts +30 -0
  371. package/dist/routes/auth/refresh.js +51 -0
  372. package/dist/routes/auth/session.d.ts +72 -0
  373. package/dist/routes/auth/session.js +180 -0
  374. package/dist/routes/auth/settings.d.ts +25 -0
  375. package/dist/routes/auth/settings.js +55 -0
  376. package/dist/routes/auth/viability.d.ts +52 -0
  377. package/dist/routes/auth/viability.js +201 -0
  378. package/dist/routes/index.d.ts +12 -0
  379. package/dist/routes/index.js +54 -0
  380. package/dist/routes/session/index.d.ts +6 -0
  381. package/dist/routes/session/index.js +10 -0
  382. package/dist/routes/session/refresh-viability.d.ts +16 -0
  383. package/dist/routes/session/refresh-viability.js +20 -0
  384. package/dist/services/signalrActivityService.d.ts +44 -0
  385. package/dist/services/signalrActivityService.js +257 -0
  386. package/dist/stores/authStore.d.ts +154 -0
  387. package/dist/stores/authStore.js +1531 -0
  388. package/dist/theme/ThemeProvider.d.ts +14 -0
  389. package/dist/theme/ThemeProvider.js +28 -0
  390. package/dist/theme/default.d.ts +8 -0
  391. package/dist/theme/default.js +33 -0
  392. package/dist/theme/index.d.ts +15 -0
  393. package/dist/theme/index.js +25 -0
  394. package/dist/theme/types.d.ts +56 -0
  395. package/dist/theme/types.js +8 -0
  396. package/dist/theme/useTheme.d.ts +60 -0
  397. package/dist/theme/useTheme.js +63 -0
  398. package/dist/theme/utils.d.ts +13 -0
  399. package/dist/theme/utils.js +39 -0
  400. package/dist/types/api.d.ts +134 -0
  401. package/dist/types/api.js +44 -0
  402. package/dist/types/auth.d.ts +19 -0
  403. package/dist/types/auth.js +2 -0
  404. package/dist/types/logging.d.ts +42 -0
  405. package/dist/types/logging.js +2 -0
  406. package/dist/types/recovery.d.ts +48 -0
  407. package/dist/types/recovery.js +2 -0
  408. package/dist/types/security.d.ts +1 -0
  409. package/dist/types/security.js +2 -0
  410. package/dist/utils/api.d.ts +85 -0
  411. package/dist/utils/api.js +287 -0
  412. package/dist/utils/circuitBreaker.d.ts +43 -0
  413. package/dist/utils/circuitBreaker.js +91 -0
  414. package/dist/utils/error-message.d.ts +1 -0
  415. package/dist/utils/error-message.js +103 -0
  416. package/dist/utils/layout/reservedSpace.d.ts +59 -0
  417. package/dist/utils/layout/reservedSpace.js +102 -0
  418. package/dist/utils/logout.d.ts +14 -0
  419. package/dist/utils/logout.js +32 -0
  420. package/dist/vibe/client.d.ts +261 -0
  421. package/dist/vibe/client.js +445 -0
  422. package/dist/vibe/errors.d.ts +83 -0
  423. package/dist/vibe/errors.js +146 -0
  424. package/dist/vibe/generic.d.ts +234 -0
  425. package/dist/vibe/generic.js +369 -0
  426. package/dist/vibe/hooks/index.d.ts +169 -0
  427. package/dist/vibe/hooks/index.js +252 -0
  428. package/dist/vibe/index.d.ts +23 -0
  429. package/dist/vibe/index.js +67 -0
  430. package/dist/vibe/sessions.d.ts +161 -0
  431. package/dist/vibe/sessions.js +391 -0
  432. package/dist/vibe/types.d.ts +353 -0
  433. package/dist/vibe/types.js +315 -0
  434. package/package.json +855 -0
  435. package/scripts/check-internal-url-usage.sh +73 -0
  436. package/scripts/dev-broker.ps1 +35 -0
  437. package/scripts/dev-local.ps1 +45 -0
  438. package/src/api/auth-handler.ts +550 -0
  439. package/src/api/index.ts +18 -0
  440. package/src/api-handlers/account/change-password.ts +145 -0
  441. package/src/api-handlers/account/masked-info.ts +45 -0
  442. package/src/api-handlers/account/profile.ts +80 -0
  443. package/src/api-handlers/account/recovery/initiate.ts +23 -0
  444. package/src/api-handlers/account/recovery/send-code.ts +25 -0
  445. package/src/api-handlers/account/recovery/verify-code.ts +25 -0
  446. package/src/api-handlers/account/reset-password.ts +23 -0
  447. package/src/api-handlers/account/send-code.ts +76 -0
  448. package/src/api-handlers/account/update-phone.ts +79 -0
  449. package/src/api-handlers/account/validate-password.ts +118 -0
  450. package/src/api-handlers/account/verify-email.ts +125 -0
  451. package/src/api-handlers/account/verify-sms.ts +125 -0
  452. package/src/api-handlers/admin/analytics.ts +445 -0
  453. package/src/api-handlers/admin/audit.ts +225 -0
  454. package/src/api-handlers/admin/index.ts +59 -0
  455. package/src/api-handlers/admin/redis-sessions.ts +253 -0
  456. package/src/api-handlers/admin/sessions.ts +320 -0
  457. package/src/api-handlers/admin/site-logs.ts +367 -0
  458. package/src/api-handlers/admin/users.ts +244 -0
  459. package/src/api-handlers/admin/vibe-data.ts +326 -0
  460. package/src/api-handlers/anon/preferences.ts +123 -0
  461. package/src/api-handlers/auth/jwks.ts +20 -0
  462. package/src/api-handlers/auth/login.ts +240 -0
  463. package/src/api-handlers/auth/refresh.ts +687 -0
  464. package/src/api-handlers/auth/signout.ts +212 -0
  465. package/src/api-handlers/auth/status.ts +23 -0
  466. package/src/api-handlers/auth/update-session.ts +125 -0
  467. package/src/api-handlers/auth/validate.ts +44 -0
  468. package/src/api-handlers/auth/verify-code.ts +129 -0
  469. package/src/api-handlers/session/refresh-viability.ts +36 -0
  470. package/src/api-handlers/session/viability.ts +166 -0
  471. package/src/api-handlers/test/force-expire.ts +67 -0
  472. package/src/auth/auth-decision.ts +230 -0
  473. package/src/auth/auth-options.ts +237 -0
  474. package/src/auth/callbacks/index.ts +7 -0
  475. package/src/auth/callbacks/jwt.ts +382 -0
  476. package/src/auth/callbacks/session.ts +243 -0
  477. package/src/auth/callbacks/signin.ts +56 -0
  478. package/src/auth/events/index.ts +5 -0
  479. package/src/auth/events/signout.ts +33 -0
  480. package/src/auth/providers/credentials.ts +256 -0
  481. package/src/auth/providers/index.ts +6 -0
  482. package/src/auth/providers/oauth.ts +114 -0
  483. package/src/auth/route-config.ts +220 -0
  484. package/src/auth/types/auth-types.ts +555 -0
  485. package/src/auth/types/index.ts +7 -0
  486. package/src/auth/unauthenticated-routes.ts +3 -0
  487. package/src/auth/utils/idp-client.ts +444 -0
  488. package/src/auth/utils/index.ts +6 -0
  489. package/src/auth/utils/token-utils.ts +244 -0
  490. package/src/client/AuthContext.tsx +140 -0
  491. package/src/client/fetch-with-auth.ts +48 -0
  492. package/src/client/fetchWithSession.ts +21 -0
  493. package/src/client/index.ts +13 -0
  494. package/src/client/useAnonSession.ts +131 -0
  495. package/src/components/SessionSync.tsx +137 -0
  496. package/src/components/SignalRHealthCheck.tsx +131 -0
  497. package/src/components/account/UserAvatarMenu.tsx +217 -0
  498. package/src/components/account/index.ts +8 -0
  499. package/src/components/admin/AlertSettingsTab.tsx +728 -0
  500. package/src/components/admin/AnalyticsTab.tsx +703 -0
  501. package/src/components/admin/DataBrowserTab.tsx +505 -0
  502. package/src/components/admin/LoggingSettingsTab.tsx +665 -0
  503. package/src/components/admin/SessionsTab.tsx +414 -0
  504. package/src/components/admin/StatsTab.tsx +379 -0
  505. package/src/components/admin/VibeAdminContext.tsx +87 -0
  506. package/src/components/admin/VibeAdminLayout.tsx +185 -0
  507. package/src/components/admin/index.ts +59 -0
  508. package/src/components/auth/FederatedAuthSection.tsx +95 -0
  509. package/src/components/auth/ModeAwareLoginPage.tsx +135 -0
  510. package/src/components/auth/ModeAwareSignupPage.tsx +267 -0
  511. package/src/components/auth/TraditionalAuthSection.tsx +99 -0
  512. package/src/components/recovery/CompleteStep.tsx +36 -0
  513. package/src/components/recovery/InitiateRecoveryStep.tsx +68 -0
  514. package/src/components/recovery/SelectMethodStep.tsx +73 -0
  515. package/src/components/recovery/SetPasswordStep.tsx +97 -0
  516. package/src/components/recovery/VerifyCodeStep.tsx +90 -0
  517. package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -0
  518. package/src/components/reserved/ReservedStatusBox.tsx +118 -0
  519. package/src/components/ui/BetaBadge.tsx +58 -0
  520. package/src/components/ui/Footer.tsx +93 -0
  521. package/src/config/env.ts +57 -0
  522. package/src/config/logger.ts +62 -0
  523. package/src/config/logging-config.ts +82 -0
  524. package/src/config/unauthenticated-routes.ts +19 -0
  525. package/src/config/vibe-log-transport.ts +250 -0
  526. package/src/edge/internal-api-url.ts +65 -0
  527. package/src/edge/middleware.ts +42 -0
  528. package/src/hooks/useAuth.ts +115 -0
  529. package/src/hooks/useAuthSettings.ts +97 -0
  530. package/src/hooks/useAvailableProviders.ts +118 -0
  531. package/src/hooks/usePasswordValidation.ts +127 -0
  532. package/src/hooks/useProfile.ts +75 -0
  533. package/src/hooks/usePublicAuthSettings.ts +149 -0
  534. package/src/hooks/useSessionExpiration.ts +102 -0
  535. package/src/hooks/useViabilitySession.ts +335 -0
  536. package/src/index.ts +63 -0
  537. package/src/lib/anon-session.ts +213 -0
  538. package/src/lib/api-handler.ts +625 -0
  539. package/src/lib/app-slug.ts +178 -0
  540. package/src/lib/demo-mode.ts +13 -0
  541. package/src/lib/geolocation.ts +265 -0
  542. package/src/lib/idp-client-config.ts +442 -0
  543. package/src/lib/idp-fetch.ts +101 -0
  544. package/src/lib/internal-api.ts +171 -0
  545. package/src/lib/jwt-decode-client.ts +45 -0
  546. package/src/lib/jwt-decode.ts +83 -0
  547. package/src/lib/nextauth-secret.ts +126 -0
  548. package/src/lib/rate-limit-service.ts +9 -0
  549. package/src/lib/redis.ts +27 -0
  550. package/src/lib/refresh-token-validator.ts +64 -0
  551. package/src/lib/roles.ts +177 -0
  552. package/src/lib/secret-validation.ts +8 -0
  553. package/src/lib/session-store.ts +637 -0
  554. package/src/lib/session.ts +34 -0
  555. package/src/lib/site-logger.ts +245 -0
  556. package/src/lib/standardized-client-api.ts +896 -0
  557. package/src/lib/startup-init.ts +247 -0
  558. package/src/lib/test-aware-get-token.ts +30 -0
  559. package/src/lib/token-expiry.ts +40 -0
  560. package/src/lib/token-lifecycle.ts +477 -0
  561. package/src/lib/types/api-responses.ts +336 -0
  562. package/src/lib/user-agent-parser.ts +252 -0
  563. package/src/logging/api/admin-analytics.ts +51 -0
  564. package/src/logging/api/audit-log.ts +53 -0
  565. package/src/logging/components/AdminAnalyticsLayout.tsx +49 -0
  566. package/src/logging/components/AuditLogViewer.tsx +125 -0
  567. package/src/logging/components/ErrorMetricsCard.tsx +98 -0
  568. package/src/logging/components/HealthMetricsCard.tsx +70 -0
  569. package/src/logging/hooks/useAdminAnalytics.ts +22 -0
  570. package/src/logging/hooks/useAuditLog.ts +24 -0
  571. package/src/logging/hooks/useErrorMetrics.ts +40 -0
  572. package/src/logging/hooks/useHealthMetrics.ts +44 -0
  573. package/src/logging/index.ts +18 -0
  574. package/src/logging/types/analytics.ts +81 -0
  575. package/src/logging/types/audit.ts +31 -0
  576. package/src/logging/types/index.ts +3 -0
  577. package/src/middleware/auth-decision.ts +43 -0
  578. package/src/middleware/create-middleware.ts +626 -0
  579. package/src/middleware/rbac-check.ts +244 -0
  580. package/src/middleware/twofa-presets.ts +224 -0
  581. package/src/models/DecodedAccessToken.ts +17 -0
  582. package/src/models/SessionModel.ts +258 -0
  583. package/src/pages/admin-login/page.tsx +229 -0
  584. package/src/pages/admin-roles/RolesAdminPage.tsx +357 -0
  585. package/src/pages/admin-roles/index.ts +9 -0
  586. package/src/pages/admin-roles/modals.tsx +469 -0
  587. package/src/pages/client-admin/ClientSiteAdminPage.tsx +380 -0
  588. package/src/pages/client-admin/index.ts +33 -0
  589. package/src/pages/login/page.tsx +463 -0
  590. package/src/pages/profile/EnhancedProfilePage.tsx +479 -0
  591. package/src/pages/profile/index.ts +9 -0
  592. package/src/pages/profile/page.tsx +166 -0
  593. package/src/pages/recovery/page.tsx +234 -0
  594. package/src/pages/roles/MyRolesPage.tsx +211 -0
  595. package/src/pages/roles/components.tsx +294 -0
  596. package/src/pages/roles/index.ts +17 -0
  597. package/src/pages/security/EnhancedSecurityPage.tsx +574 -0
  598. package/src/pages/security/index.ts +9 -0
  599. package/src/pages/security/page.tsx +507 -0
  600. package/src/pages/settings/EnhancedSettingsPage.tsx +642 -0
  601. package/src/pages/settings/index.ts +9 -0
  602. package/src/pages/settings/page.tsx +47 -0
  603. package/src/pages/showcase/ShowcasePage.tsx +530 -0
  604. package/src/pages/showcase/index.ts +13 -0
  605. package/src/pages/test-env/EmergencyLogoutPage.tsx +179 -0
  606. package/src/pages/test-env/JwtInspectPage.tsx +418 -0
  607. package/src/pages/test-env/RefreshTokenPage.tsx +155 -0
  608. package/src/pages/test-env/TestEnvPage.tsx +116 -0
  609. package/src/pages/test-env/index.ts +25 -0
  610. package/src/pages/verify-code/page.tsx +648 -0
  611. package/src/routes/account/index.ts +32 -0
  612. package/src/routes/account/masked-info.ts +37 -0
  613. package/src/routes/account/send-code.ts +40 -0
  614. package/src/routes/account/update-phone.ts +13 -0
  615. package/src/routes/account/verify-email.ts +41 -0
  616. package/src/routes/account/verify-sms.ts +41 -0
  617. package/src/routes/auth/index.ts +23 -0
  618. package/src/routes/auth/logout.ts +127 -0
  619. package/src/routes/auth/nextauth.ts +71 -0
  620. package/src/routes/auth/refresh.ts +54 -0
  621. package/src/routes/auth/session.ts +193 -0
  622. package/src/routes/auth/settings.ts +75 -0
  623. package/src/routes/auth/viability.ts +220 -0
  624. package/src/routes/index.ts +18 -0
  625. package/src/routes/session/index.ts +7 -0
  626. package/src/routes/session/refresh-viability.ts +17 -0
  627. package/src/services/signalrActivityService.ts +258 -0
  628. package/src/stores/authStore.ts +1904 -0
  629. package/src/templates/instrumentation.ts +41 -0
  630. package/src/theme/ThemeProvider.tsx +39 -0
  631. package/src/theme/default.ts +33 -0
  632. package/src/theme/index.ts +31 -0
  633. package/src/theme/types.ts +69 -0
  634. package/src/theme/useTheme.ts +57 -0
  635. package/src/theme/utils.ts +40 -0
  636. package/src/types/api.ts +13 -0
  637. package/src/types/auth.d.ts +15 -0
  638. package/src/types/auth.ts +22 -0
  639. package/src/types/logging.ts +11 -0
  640. package/src/types/next-auth.d.ts +15 -0
  641. package/src/types/recovery.ts +54 -0
  642. package/src/types/security.ts +1 -0
  643. package/src/utils/api.ts +353 -0
  644. package/src/utils/circuitBreaker.ts +40 -0
  645. package/src/utils/error-message.ts +108 -0
  646. package/src/utils/layout/reservedSpace.ts +124 -0
  647. package/src/utils/logout.ts +30 -0
  648. package/src/vibe/client.ts +590 -0
  649. package/src/vibe/errors.ts +185 -0
  650. package/src/vibe/generic.ts +429 -0
  651. package/src/vibe/hooks/index.ts +367 -0
  652. package/src/vibe/index.ts +121 -0
  653. package/src/vibe/sessions.ts +551 -0
  654. package/src/vibe/types.ts +577 -0
@@ -0,0 +1,687 @@
1
+ /**
2
+ * CRITICAL REFRESH TOKEN API HANDLER
3
+ *
4
+ * ASK BEFORE EDITING - TESTED AND WORKING SYSTEM
5
+ *
6
+ * This handler manages the server-side refresh token cycle with:
7
+ * - NextAuth JWT token extraction
8
+ * - Session token fallback for internal calls
9
+ * - PayEz IDP refresh token exchange
10
+ * - Session state updates with new tokens
11
+ * - Proper error handling and logging
12
+ * - Single-use semantics enforcement
13
+ *
14
+ * @version 2.0
15
+ */
16
+
17
+ import { NextRequest, NextResponse } from 'next/server';
18
+ import { getToken } from 'next-auth/jwt';
19
+ import { getSession, updateSession, acquireRefreshLock, releaseRefreshLock, checkRefreshLock } from '../../lib/session-store';
20
+ import { computeTokenExpiries } from '../../lib/token-expiry';
21
+ import { getJwtCookieName } from '../../lib/app-slug';
22
+ import { extractKidFromToken } from '../../auth/utils/token-utils';
23
+
24
+ interface RefreshConfig {
25
+ idpBaseUrl: string;
26
+ clientId: string;
27
+ nextAuthSecret: string;
28
+ refreshEndpoint?: string;
29
+ }
30
+
31
+ /**
32
+ * Creates a refresh token handler for Next.js API routes
33
+ *
34
+ * @param config Configuration for IDP connection and NextAuth
35
+ * @returns Next.js POST handler function
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * // In your app's /app/api/auth/refresh/route.ts
40
+ * import { createRefreshHandler } from '@payez/next-mvp/api-handlers/auth/refresh';
41
+ *
42
+ * export const POST = createRefreshHandler({
43
+ * idpBaseUrl: process.env.IDP_URL!,
44
+ * clientId: process.env.CLIENT_ID!,
45
+ * nextAuthSecret: process.env.NEXTAUTH_SECRET!,
46
+ * refreshEndpoint: '/api/ExternalAuth/refresh'
47
+ * });
48
+ * ```
49
+ */
50
+ export function createRefreshHandler(config: RefreshConfig) {
51
+ const { idpBaseUrl, clientId, nextAuthSecret, refreshEndpoint = '/api/ExternalAuth/refresh' } = config;
52
+
53
+ return async function POST(req: NextRequest) {
54
+ try {
55
+ // Extract session token from NextAuth JWT
56
+ const token = await getToken({ req, secret: nextAuthSecret, cookieName: getJwtCookieName() });
57
+
58
+ // Support both field names: sessionToken (auth.ts JWT) and redisSessionId (legacy)
59
+ let sessionToken = (token?.sessionToken || token?.redisSessionId) as string | undefined;
60
+ let userId = token?.sub;
61
+
62
+ if (!sessionToken) {
63
+ // Fallback: check for session token in header (for internal server-to-server calls)
64
+ const headerSessionToken = req.headers.get('x-session-token');
65
+ if (headerSessionToken) {
66
+ sessionToken = headerSessionToken;
67
+ const currentSession = await getSession(sessionToken);
68
+ userId = currentSession?.userId || currentSession?.email;
69
+ }
70
+ }
71
+
72
+ if (!sessionToken || !userId) {
73
+ console.warn('[AUTH_REFRESH] Missing sessionToken or user id on token');
74
+ return NextResponse.json({
75
+ error: 'No session available for refresh',
76
+ code: 'UNAUTHORIZED'
77
+ }, { status: 401 });
78
+ }
79
+
80
+ // Get current session
81
+ const currentSession = await getSession(sessionToken);
82
+ // NOTE: Field is idpRefreshToken (not refreshToken) per normalized naming convention
83
+ if (!currentSession?.idpRefreshToken) {
84
+ console.warn('[AUTH_REFRESH] No refresh token available', { userId });
85
+ return NextResponse.json({
86
+ error: 'No refresh token available',
87
+ code: 'NO_REFRESH_TOKEN', // Terminal state - session cannot be refreshed
88
+ terminal: true, // Signal to frontend: don't retry, redirect to login
89
+ resolution: 'User must re-authenticate'
90
+ }, { status: 401 });
91
+ }
92
+
93
+ // ============================================================================
94
+ // HIGH VISIBILITY: PRE-FLIGHT REFRESH DIAGNOSTICS
95
+ // ============================================================================
96
+ const now = Date.now();
97
+ const refreshTokenAge = currentSession.idpRefreshTokenIssuedAt
98
+ ? Math.round((now - currentSession.idpRefreshTokenIssuedAt) / 1000)
99
+ : 'unknown';
100
+ const refreshTokenExpiresIn = currentSession.idpRefreshTokenExpires
101
+ ? Math.round((currentSession.idpRefreshTokenExpires - now) / 1000)
102
+ : 'unknown';
103
+ const accessTokenExpiresIn = currentSession.idpAccessTokenExpires
104
+ ? Math.round((currentSession.idpAccessTokenExpires - now) / 1000)
105
+ : 'unknown';
106
+
107
+ console.log('╔══════════════════════════════════════════════════════════════════════════════╗');
108
+ console.log('║ REFRESH TOKEN ATTEMPT - PRE-FLIGHT ║');
109
+ console.log('╠══════════════════════════════════════════════════════════════════════════════╣');
110
+ console.log(`║ User: ${(userId || 'unknown').substring(0, 50).padEnd(50)} ║`);
111
+ console.log(`║ Session: ${sessionToken.substring(0, 8)}... ║`);
112
+ console.log('╠──────────────────────────────────────────────────────────────────────────────╣');
113
+ console.log(`║ Access Token Expires In: ${String(accessTokenExpiresIn).padEnd(10)} seconds ║`);
114
+ console.log(`║ Refresh Token Age: ${String(refreshTokenAge).padEnd(10)} seconds ║`);
115
+ console.log(`║ Refresh Token Expires In: ${String(refreshTokenExpiresIn).padEnd(10)} seconds ║`);
116
+ console.log(`║ Refresh Token Length: ${String(currentSession.idpRefreshToken?.length || 0).padEnd(10)} chars ║`);
117
+ console.log(`║ 2FA Complete: ${String(!!currentSession.mfaVerified).padEnd(10)} ║`);
118
+ console.log(`║ Auth Level (ACR): ${String(currentSession.authenticationLevel || '1').padEnd(10)} ║`);
119
+ console.log('╚══════════════════════════════════════════════════════════════════════════════╝');
120
+
121
+ // Try to acquire refresh lock to prevent concurrent refresh attempts
122
+ const requestId = req.headers.get('x-request-id') ?? `refresh_${Date.now()}`;
123
+ const lockAcquired = await acquireRefreshLock(sessionToken, requestId, 5000);
124
+
125
+ let weAcquiredLock = false;
126
+ let releaseLockVersion: number | undefined;
127
+
128
+ if (!lockAcquired.acquired) {
129
+ const existingLock = await checkRefreshLock(sessionToken);
130
+ if (existingLock && existingLock.acquiredBy === requestId) {
131
+ console.info('[AUTH_REFRESH] Proceeding under existing caller-held lock', { requestId });
132
+ } else {
133
+ // Wait for the lock to release, then check if token is now fresh
134
+ console.info('[AUTH_REFRESH] Refresh in progress by another request, waiting for completion...', { requestId });
135
+
136
+ const maxWaitMs = 5000;
137
+ const checkIntervalMs = 200;
138
+ const startWait = Date.now();
139
+
140
+ while (Date.now() - startWait < maxWaitMs) {
141
+ await new Promise(resolve => setTimeout(resolve, checkIntervalMs));
142
+
143
+ // Check if lock was released
144
+ const lockCheck = await checkRefreshLock(sessionToken);
145
+ if (!lockCheck) {
146
+ // Lock released - check if token is now fresh
147
+ const refreshedSession = await getSession(sessionToken);
148
+ if (refreshedSession?.idpAccessToken && refreshedSession?.idpAccessTokenExpires) {
149
+ const timeUntilExpiry = refreshedSession.idpAccessTokenExpires - Date.now();
150
+ const fiveMinutes = 5 * 60 * 1000;
151
+
152
+ if (timeUntilExpiry > fiveMinutes) {
153
+ console.info('[AUTH_REFRESH] Lock released, token is fresh - returning success', {
154
+ requestId,
155
+ accessTokenExpires: new Date(refreshedSession.idpAccessTokenExpires).toISOString(),
156
+ waitedMs: Date.now() - startWait,
157
+ });
158
+ return NextResponse.json({
159
+ refreshed: true,
160
+ reason: 'completed_by_concurrent_request',
161
+ accessTokenExpires: refreshedSession.idpAccessTokenExpires,
162
+ hasRefreshToken: !!refreshedSession.idpRefreshToken,
163
+ });
164
+ }
165
+ }
166
+ // Lock released but token not fresh - try to acquire lock ourselves
167
+ break;
168
+ }
169
+ }
170
+
171
+ // Still locked after waiting - return 409
172
+ console.warn('[AUTH_REFRESH] Refresh still in progress after waiting', { requestId, waitedMs: Date.now() - startWait });
173
+ return NextResponse.json({
174
+ error: 'Refresh already in progress',
175
+ code: 'CONFLICT'
176
+ }, { status: 409 });
177
+ }
178
+ } else {
179
+ weAcquiredLock = true;
180
+ releaseLockVersion = lockAcquired.lockInfo?.lockVersion;
181
+ }
182
+
183
+ // Before performing network refresh, re-check if tokens are already fresh
184
+ const latestAfterLock = await getSession(sessionToken);
185
+ const fiveMinutes = 5 * 60 * 1000;
186
+ const timeUntilExpiry = latestAfterLock?.idpAccessTokenExpires
187
+ ? latestAfterLock.idpAccessTokenExpires - Date.now()
188
+ : -1;
189
+
190
+ // CRITICAL: Also check the actual JWT's exp claim - Redis might have stale data
191
+ let actualJwtExpMs = -1;
192
+ let tokenMismatch = false;
193
+ if (latestAfterLock?.idpAccessToken) {
194
+ try {
195
+ const tokenParts = latestAfterLock.idpAccessToken.split('.');
196
+ if (tokenParts.length === 3) {
197
+ const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64url').toString());
198
+ actualJwtExpMs = (payload.exp || 0) * 1000;
199
+ const now = Date.now();
200
+ // If the actual JWT is expired, we MUST refresh regardless of what Redis says
201
+ if (actualJwtExpMs < now) {
202
+ tokenMismatch = true;
203
+ }
204
+ }
205
+ } catch (e) {
206
+ // If we can't decode, proceed with normal logic
207
+ }
208
+ }
209
+
210
+ console.log('[AUTH_REFRESH] Pre-refresh check:', {
211
+ userId,
212
+ hasAccessToken: !!latestAfterLock?.idpAccessToken,
213
+ accessTokenExpires: latestAfterLock?.idpAccessTokenExpires
214
+ ? new Date(latestAfterLock.idpAccessTokenExpires).toISOString()
215
+ : 'undefined',
216
+ actualJwtExp: actualJwtExpMs > 0 ? new Date(actualJwtExpMs).toISOString() : 'unknown',
217
+ now: new Date().toISOString(),
218
+ timeUntilExpiryMs: timeUntilExpiry,
219
+ tokenMismatch,
220
+ willRefresh: timeUntilExpiry <= fiveMinutes || tokenMismatch
221
+ });
222
+
223
+ // Only skip refresh if BOTH Redis says fresh AND the actual JWT is not expired
224
+ if (latestAfterLock?.idpAccessToken && latestAfterLock?.idpAccessTokenExpires &&
225
+ timeUntilExpiry > fiveMinutes && !tokenMismatch) {
226
+ console.info('[AUTH_REFRESH] Skipping refresh: tokens already fresh', { userId, timeUntilExpiryMs: timeUntilExpiry });
227
+ if (weAcquiredLock) {
228
+ await releaseRefreshLock(sessionToken, requestId, releaseLockVersion);
229
+ weAcquiredLock = false;
230
+ }
231
+ return NextResponse.json({
232
+ refreshed: false,
233
+ reason: 'already_fresh',
234
+ accessTokenExpires: latestAfterLock.idpAccessTokenExpires,
235
+ hasRefreshToken: !!latestAfterLock.idpRefreshToken,
236
+ });
237
+ }
238
+
239
+ // If we detected a token mismatch, log it prominently
240
+ if (tokenMismatch) {
241
+ console.warn('[AUTH_REFRESH] Token mismatch detected - forcing refresh despite Redis claiming fresh', {
242
+ userId,
243
+ redisExpires: latestAfterLock?.idpAccessTokenExpires ? new Date(latestAfterLock.idpAccessTokenExpires).toISOString() : 'N/A',
244
+ actualJwtExp: new Date(actualJwtExpMs).toISOString()
245
+ });
246
+ }
247
+
248
+ // Copy refresh token for use in IDP call
249
+ // Note: Token is NOT cleared preemptively - it will be replaced on success.
250
+ // The lock mechanism prevents concurrent refresh attempts within the same session.
251
+ // If IDP call fails, user can retry with the same token.
252
+ const originalRefreshToken = currentSession.idpRefreshToken as string;
253
+
254
+ try {
255
+ // Extract 2FA claims from current session
256
+ let authMethods: string[] = [];
257
+ if (currentSession.authenticationMethods) {
258
+ if (typeof currentSession.authenticationMethods === 'string') {
259
+ try {
260
+ authMethods = JSON.parse(currentSession.authenticationMethods);
261
+ } catch (e) {
262
+ console.warn('[AUTH_REFRESH] Failed to parse authenticationMethods', { authenticationMethods: currentSession.authenticationMethods });
263
+ }
264
+ } else if (Array.isArray(currentSession.authenticationMethods)) {
265
+ authMethods = currentSession.authenticationMethods;
266
+ }
267
+ }
268
+
269
+ // For OAuth sessions with empty AMR claims, provide defaults
270
+ // OAuth IS a form of multi-factor auth (you authenticated with another provider)
271
+ let isOAuthSession = !!currentSession.oauthProvider;
272
+ if (authMethods.length === 0 && isOAuthSession) {
273
+ // OAuth sessions get default AMR claims
274
+ authMethods = ['pwd', 'mfa'];
275
+ console.log('[AUTH_REFRESH] OAuth session with empty AMR - using defaults:', {
276
+ oauthProvider: currentSession.oauthProvider,
277
+ defaultAmr: authMethods
278
+ });
279
+ }
280
+
281
+ // Check authMethods first, then fallback to twoFactorMethod field (set by transitionTo2FASession)
282
+ // For OAuth sessions, use 'oauth' as the 2FA method since OAuth itself is the authentication
283
+ const twoFactorMethod = authMethods.find(m => ['sms', 'totp', 'email'].includes(m))
284
+ || currentSession.mfaMethod
285
+ || (isOAuthSession ? 'oauth' : null);
286
+
287
+ // DEBUG: Log what we have for 2FA
288
+ console.log('[AUTH_REFRESH] 2FA Debug:', {
289
+ authMethods,
290
+ authMethodsRaw: currentSession.authenticationMethods,
291
+ twoFactorMethodFromSession: currentSession.mfaMethod,
292
+ twoFactorMethodResolved: twoFactorMethod,
293
+ twoFactorComplete: currentSession.mfaVerified,
294
+ sessionKeys: Object.keys(currentSession)
295
+ });
296
+
297
+ // Build refresh request body per PayEz wire standard (snake_case)
298
+ // See: 2FA-TOKEN-REFRESH-CONTEXT.md - "The Ninja Shit"
299
+ // For OAuth sessions with 2FA complete, use ACR level 2
300
+ let acrValue = String(currentSession.authenticationLevel ?? '1');
301
+ if (currentSession.oauthProvider && currentSession.mfaVerified && acrValue === '1') {
302
+ acrValue = '2'; // OAuth with 2FA complete = full authentication
303
+ }
304
+ const refreshRequestBody: any = {
305
+ refresh_token: originalRefreshToken,
306
+ amr: authMethods,
307
+ acr: acrValue
308
+ };
309
+
310
+ // DEBUG: Log exact request body
311
+ console.log('[AUTH_REFRESH] Request body:', JSON.stringify({
312
+ refresh_token: '***REDACTED***',
313
+ amr: authMethods,
314
+ acr: acrValue,
315
+ acrType: typeof acrValue
316
+ }));
317
+
318
+ const twoFactorVerified = !!currentSession.mfaVerified;
319
+ if (twoFactorVerified) {
320
+ refreshRequestBody.two_factor_verified = true;
321
+ }
322
+ if (twoFactorMethod) {
323
+ refreshRequestBody.two_factor_method = twoFactorMethod;
324
+ }
325
+ if (currentSession.mfaCompletedAt) {
326
+ refreshRequestBody.two_factor_completed_at = new Date(currentSession.mfaCompletedAt).toISOString();
327
+ }
328
+
329
+ // Log the full request details for debugging
330
+ console.log('[AUTH_REFRESH] Sending refresh request:', {
331
+ url: `${idpBaseUrl}${refreshEndpoint}`,
332
+ clientId,
333
+ hasRefreshToken: !!refreshRequestBody.refresh_token,
334
+ refreshTokenLength: refreshRequestBody.refresh_token?.length,
335
+ amr: refreshRequestBody.amr,
336
+ acr: refreshRequestBody.acr
337
+ });
338
+
339
+ let refreshResponse: Response;
340
+ try {
341
+ refreshResponse = await fetch(`${idpBaseUrl}${refreshEndpoint}`, {
342
+ method: 'POST',
343
+ headers: {
344
+ 'Content-Type': 'application/json',
345
+ 'X-Client-Id': clientId,
346
+ },
347
+ body: JSON.stringify(refreshRequestBody),
348
+ });
349
+ } catch (fetchError) {
350
+ // Network error - IDP unreachable
351
+ // Note: Lock will be released by finally block
352
+ console.error('[AUTH_REFRESH] IDP unreachable:', {
353
+ error: fetchError instanceof Error ? fetchError.message : String(fetchError),
354
+ idpUrl: idpBaseUrl,
355
+ userId
356
+ });
357
+ return NextResponse.json({
358
+ error: 'IDP service unavailable',
359
+ code: 'UPSTREAM_SERVICE_UNAVAILABLE',
360
+ retryable: true,
361
+ discardToken: false
362
+ }, { status: 503 });
363
+ }
364
+
365
+ // Parse response body - handle empty or invalid JSON
366
+ let responseData: any;
367
+ try {
368
+ const responseText = await refreshResponse.text();
369
+ if (!responseText || responseText.trim() === '') {
370
+ // Note: Lock will be released by finally block
371
+ console.error('[AUTH_REFRESH] Empty response from IDP:', {
372
+ status: refreshResponse.status,
373
+ statusText: refreshResponse.statusText,
374
+ userId
375
+ });
376
+ return NextResponse.json({
377
+ error: 'Empty response from IDP',
378
+ code: 'UPSTREAM_SERVICE_ERROR',
379
+ retryable: true,
380
+ discardToken: false
381
+ }, { status: 502 });
382
+ }
383
+ responseData = JSON.parse(responseText);
384
+ } catch (parseError) {
385
+ // Note: Lock will be released by finally block
386
+ console.error('[AUTH_REFRESH] Failed to parse IDP response:', {
387
+ error: parseError instanceof Error ? parseError.message : String(parseError),
388
+ status: refreshResponse.status,
389
+ userId
390
+ });
391
+ return NextResponse.json({
392
+ error: 'Invalid response from IDP',
393
+ code: 'UPSTREAM_SERVICE_ERROR',
394
+ retryable: true,
395
+ discardToken: false
396
+ }, { status: 502 });
397
+ }
398
+
399
+ // Handle non-OK responses with structured error format
400
+ if (!refreshResponse.ok) {
401
+ // Parse structured error from IDP
402
+ const idpError = responseData?.error || {};
403
+ const errorCode = idpError.code || 'UNKNOWN_ERROR';
404
+ const errorMessage = idpError.message || 'Token refresh failed';
405
+ // CRITICAL: 401 means token is definitively invalid - always discard
406
+ // This prevents redirect loops where viability check keeps returning canRefresh:true
407
+ const discardToken = refreshResponse.status === 401 || idpError.discard_token === true;
408
+ const retryable = refreshResponse.status !== 401 && idpError.retryable === true;
409
+ const resolution = idpError.resolution || null;
410
+
411
+ // ============================================================================
412
+ // HIGH VISIBILITY: REFRESH TOKEN FAILURE DIAGNOSTICS
413
+ // ============================================================================
414
+ // Translate error codes to human-readable explanations
415
+ const failureReasons: Record<string, string> = {
416
+ 'UNAUTHORIZED': 'Token was already used (rotation) OR token is expired OR token was revoked',
417
+ 'INVALID_REFRESH_TOKEN': 'Token format invalid OR token not found in IDP database',
418
+ 'TOKEN_EXPIRED': 'Refresh token exceeded its max lifetime',
419
+ 'TOKEN_REVOKED': 'Token was explicitly revoked (logout, password change, admin action)',
420
+ 'TOKEN_REUSE_DETECTED': 'Same refresh token used twice - possible token theft, all tokens revoked',
421
+ 'UPSTREAM_SERVICE_ERROR': 'IDP internal error - token may have been rotated before failure',
422
+ 'INTERNAL_ERROR': 'IDP internal error - check IDP logs for details',
423
+ 'RATE_LIMITED': 'Too many refresh attempts - try again later',
424
+ };
425
+
426
+ const whyItFailed = failureReasons[errorCode] || 'Unknown error - check IDP logs';
427
+
428
+ console.error('╔══════════════════════════════════════════════════════════════════════════════╗');
429
+ console.error('║ ❌ REFRESH TOKEN FAILURE - DIAGNOSTIC REPORT ║');
430
+ console.error('╠══════════════════════════════════════════════════════════════════════════════╣');
431
+ console.error(`║ HTTP Status: ${String(refreshResponse.status).padEnd(60)}║`);
432
+ console.error(`║ Error Code: ${errorCode.padEnd(60)}║`);
433
+ console.error(`║ Discard Token: ${String(discardToken).padEnd(60)}║`);
434
+ console.error(`║ Retryable: ${String(retryable).padEnd(60)}║`);
435
+ console.error('╠──────────────────────────────────────────────────────────────────────────────╣');
436
+ console.error(`║ WHY IT FAILED: ║`);
437
+ console.error(`║ ${whyItFailed.substring(0, 76).padEnd(76)} ║`);
438
+ console.error('╠──────────────────────────────────────────────────────────────────────────────╣');
439
+ console.error(`║ User: ${(userId || 'unknown').substring(0, 60).padEnd(60)}║`);
440
+ console.error(`║ Session: ${sessionToken.substring(0, 8)}... ║`);
441
+ console.error(`║ Resolution: ${(resolution || 'Check IDP logs').substring(0, 60).padEnd(60)}║`);
442
+ console.error('╚══════════════════════════════════════════════════════════════════════════════╝');
443
+
444
+ // Also log the full response for detailed debugging
445
+ console.error('[AUTH_REFRESH] Full IDP error response:', JSON.stringify(responseData, null, 2).substring(0, 1000));
446
+
447
+ // If IDP signals to discard token, clear it from Redis
448
+ if (discardToken) {
449
+ console.error('[AUTH_REFRESH] ⚠️ REFRESH_TOKEN_REMOVAL: IDP signaled discard_token', {
450
+ reason: 'IDP_DISCARD_TOKEN',
451
+ errorCode,
452
+ userId,
453
+ sessionToken: sessionToken.substring(0, 8) + '...',
454
+ hadRefreshToken: !!currentSession.idpRefreshToken,
455
+ stack: new Error().stack
456
+ });
457
+ await updateSession(sessionToken, {
458
+ idpRefreshToken: '',
459
+ idpRefreshTokenExpires: undefined,
460
+ refreshTokenClearedAt: Date.now(),
461
+ refreshTokenClearedReason: `IDP_DISCARD_TOKEN:${errorCode}`
462
+ });
463
+ }
464
+
465
+ return NextResponse.json({
466
+ error: errorMessage,
467
+ code: errorCode,
468
+ discardToken,
469
+ retryable,
470
+ resolution,
471
+ status: refreshResponse.status
472
+ }, { status: refreshResponse.status });
473
+ }
474
+
475
+ // Validate PayEz canonical envelope for success responses
476
+ const isCompliant = responseData && typeof responseData === 'object' &&
477
+ Object.prototype.hasOwnProperty.call(responseData, 'success') &&
478
+ (responseData.success === true ? Object.prototype.hasOwnProperty.call(responseData, 'data') : Object.prototype.hasOwnProperty.call(responseData, 'error')) &&
479
+ Object.prototype.hasOwnProperty.call(responseData, 'meta');
480
+
481
+ if (!isCompliant) {
482
+ console.error('[AUTH_REFRESH] Upstream non-compliant IDP response', { bodySample: responseData });
483
+ return NextResponse.json({
484
+ error: 'Upstream non-compliance',
485
+ code: 'UPSTREAM_SERVICE_ERROR',
486
+ retryable: true,
487
+ discardToken: false
488
+ }, { status: 502 });
489
+ }
490
+
491
+ if (responseData.success !== true || !responseData.data) {
492
+ // Handle success:false with structured error
493
+ const idpError = responseData?.error || {};
494
+ const errorCode = idpError.code || 'UPSTREAM_VALIDATION_ERROR';
495
+ const discardToken = idpError.discard_token === true;
496
+ const retryable = idpError.retryable === true;
497
+
498
+ console.warn('[AUTH_REFRESH] IDP refresh unsuccessful', {
499
+ code: errorCode,
500
+ discardToken,
501
+ body: responseData
502
+ });
503
+
504
+ if (discardToken) {
505
+ console.error('[AUTH_REFRESH] ⚠️ REFRESH_TOKEN_REMOVAL: IDP success:false with discard_token', {
506
+ reason: 'IDP_SUCCESS_FALSE_DISCARD',
507
+ errorCode,
508
+ userId,
509
+ sessionToken: sessionToken.substring(0, 8) + '...',
510
+ hadRefreshToken: !!currentSession.idpRefreshToken,
511
+ stack: new Error().stack
512
+ });
513
+ await updateSession(sessionToken, {
514
+ idpRefreshToken: '',
515
+ idpRefreshTokenExpires: undefined,
516
+ refreshTokenClearedAt: Date.now(),
517
+ refreshTokenClearedReason: `IDP_SUCCESS_FALSE_DISCARD:${errorCode}`
518
+ });
519
+ }
520
+
521
+ return NextResponse.json({
522
+ error: idpError.message || 'Token refresh failed',
523
+ code: errorCode,
524
+ discardToken,
525
+ retryable,
526
+ resolution: idpError.resolution || null
527
+ }, { status: refreshResponse.status || 400 });
528
+ }
529
+
530
+ // Extract tokens from response
531
+ const payload = responseData.data as any;
532
+ const accessToken = payload?.access_token;
533
+ const refreshToken = payload?.refresh_token;
534
+
535
+ if (!accessToken) {
536
+ console.error('[AUTH_REFRESH] Missing access token in IDP response');
537
+ return NextResponse.json({
538
+ error: 'Invalid token response from IDP',
539
+ code: 'INTERNAL_SERVER_ERROR'
540
+ }, { status: 500 });
541
+ }
542
+
543
+ // Decode and compute token expiries
544
+ let decodedAccessToken: any;
545
+ let accessTokenExpires: number;
546
+ let computedRefreshTokenExpires: number | undefined;
547
+ try {
548
+ const result = computeTokenExpiries({ accessToken, refreshToken, preferJwt: true });
549
+ decodedAccessToken = result.decodedAccessToken;
550
+ accessTokenExpires = result.accessTokenExpires;
551
+ computedRefreshTokenExpires = result.refreshTokenExpires;
552
+
553
+ console.info('[AUTH_REFRESH] Successfully decoded new token pair', {
554
+ accessTokenExpires: new Date(accessTokenExpires).toISOString(),
555
+ refreshTokenExpires: computedRefreshTokenExpires ? new Date(computedRefreshTokenExpires).toISOString() : null
556
+ });
557
+ } catch (decodeError) {
558
+ console.error('[AUTH_REFRESH] Failed to compute token expiries', { error: decodeError });
559
+ return NextResponse.json({
560
+ error: 'Failed to decode JWT tokens',
561
+ code: 'INTERNAL_SERVER_ERROR'
562
+ }, { status: 500 });
563
+ }
564
+
565
+ // Extract 2FA claims from the new access token
566
+ let amrClaims: string[] = [];
567
+ if (decodedAccessToken.amr) {
568
+ try {
569
+ amrClaims = typeof decodedAccessToken.amr === 'string'
570
+ ? JSON.parse(decodedAccessToken.amr)
571
+ : decodedAccessToken.amr;
572
+ } catch (e) {
573
+ console.warn('[AUTH_REFRESH] Failed to parse AMR claims', { amr: decodedAccessToken.amr });
574
+ amrClaims = currentSession.authenticationMethods || [];
575
+ }
576
+ } else {
577
+ amrClaims = currentSession.authenticationMethods || [];
578
+ }
579
+ const acrLevel = String(decodedAccessToken.acr || currentSession.authenticationLevel || '1');
580
+
581
+ // Extract MFA timing claims
582
+ const mfaTime = decodedAccessToken.mfa_time ? parseInt(decodedAccessToken.mfa_time) * 1000 : currentSession.mfaCompletedAt;
583
+ const mfaExpires = decodedAccessToken.mfa_expires ? parseInt(decodedAccessToken.mfa_expires) * 1000 : currentSession.mfaExpiresAt;
584
+ const mfaValidityHours = decodedAccessToken.mfa_validity_hours ? parseInt(decodedAccessToken.mfa_validity_hours) : currentSession.mfaValidityHours;
585
+
586
+ // Update session with new tokens
587
+ const hasNewRefresh = typeof refreshToken === 'string' && refreshToken.length > 0;
588
+ const newRefreshTokenExpires = hasNewRefresh ? computedRefreshTokenExpires : undefined;
589
+
590
+ // CRITICAL: Log if we're about to clear the refresh token due to missing new token
591
+ if (!hasNewRefresh && currentSession.idpRefreshToken) {
592
+ console.error('[AUTH_REFRESH] ⚠️ REFRESH_TOKEN_REMOVAL: No new refresh token in IDP response', {
593
+ reason: 'NO_NEW_REFRESH_TOKEN_IN_RESPONSE',
594
+ userId,
595
+ sessionToken: sessionToken.substring(0, 8) + '...',
596
+ hadRefreshToken: true,
597
+ refreshTokenFromResponse: refreshToken,
598
+ refreshTokenType: typeof refreshToken,
599
+ payloadKeys: Object.keys(payload || {}),
600
+ stack: new Error().stack
601
+ });
602
+ }
603
+
604
+ // Keep existing refresh token if IDP doesn't provide a new one
605
+ // Only clear if IDP explicitly signals discard_token=true (handled in error paths)
606
+ // NOTE: Use normalized field names (idp* prefix) per SessionData interface
607
+
608
+ // CRITICAL: Extract kid from JWT header - IDP may rotate keys during refresh
609
+ const newBearerKeyId = extractKidFromToken(accessToken);
610
+ if (newBearerKeyId) {
611
+ console.log('[AUTH_REFRESH] Extracted bearerKeyId (kid) from new JWT header:', newBearerKeyId);
612
+ } else {
613
+ console.warn('[AUTH_REFRESH] No kid found in new JWT header');
614
+ }
615
+
616
+ const sessionUpdate: any = {
617
+ ...currentSession,
618
+ idpAccessToken: accessToken,
619
+ idpAccessTokenExpires: accessTokenExpires,
620
+ idpRefreshToken: hasNewRefresh ? (refreshToken as string) : currentSession.idpRefreshToken,
621
+ idpRefreshTokenExpires: hasNewRefresh ? newRefreshTokenExpires : currentSession.idpRefreshTokenExpires,
622
+ decodedAccessToken: decodedAccessToken,
623
+ // Bearer key ID from JWT header (may change on key rotation)
624
+ bearerKeyId: newBearerKeyId || currentSession.bearerKeyId,
625
+ authenticationMethods: amrClaims,
626
+ authenticationLevel: acrLevel,
627
+ mfaVerified: amrClaims.includes('mfa') || currentSession.mfaVerified,
628
+ mfaCompletedAt: mfaTime,
629
+ mfaExpiresAt: mfaExpires,
630
+ mfaValidityHours: mfaValidityHours
631
+ };
632
+
633
+ await updateSession(sessionToken, sessionUpdate);
634
+
635
+ console.info('[AUTH_REFRESH] Token refreshed successfully', {
636
+ expiresAt: new Date(accessTokenExpires).toISOString(),
637
+ userId,
638
+ hasNewRefreshToken: hasNewRefresh,
639
+ tokenPreview: accessToken ? accessToken.substring(0, 20) + '...' : 'none'
640
+ });
641
+
642
+ } catch (error) {
643
+ console.error('[AUTH_REFRESH] Error during token refresh', { error });
644
+ return NextResponse.json({
645
+ error: 'Token refresh error',
646
+ code: 'INTERNAL_SERVER_ERROR'
647
+ }, { status: 500 });
648
+ } finally {
649
+ if (weAcquiredLock) {
650
+ await releaseRefreshLock(sessionToken, requestId, releaseLockVersion);
651
+ }
652
+ }
653
+
654
+ // Load the latest session to return basic status
655
+ const latest = await getSession(sessionToken);
656
+ if (!latest?.idpAccessToken) {
657
+ return NextResponse.json({
658
+ error: 'No access token after refresh',
659
+ code: 'INTERNAL_SERVER_ERROR'
660
+ }, { status: 500 });
661
+ }
662
+
663
+ return NextResponse.json({
664
+ refreshed: true,
665
+ accessTokenExpires: latest.idpAccessTokenExpires,
666
+ hasRefreshToken: !!latest.idpRefreshToken,
667
+ });
668
+ } catch (err: any) {
669
+ console.error('[AUTH_REFRESH] Unexpected error', { error: err?.message || String(err) });
670
+ return NextResponse.json({
671
+ error: 'Unexpected error during refresh',
672
+ code: 'INTERNAL_SERVER_ERROR'
673
+ }, { status: 500 });
674
+ }
675
+ };
676
+ }
677
+
678
+ /**
679
+ * Default export for backward compatibility
680
+ * Requires environment variables: IDP_URL, CLIENT_ID, NEXTAUTH_SECRET
681
+ */
682
+ export const POST = createRefreshHandler({
683
+ idpBaseUrl: process.env.IDP_URL!,
684
+ clientId: process.env.CLIENT_ID || 'payez_default_client',
685
+ nextAuthSecret: process.env.NEXTAUTH_SECRET || '',
686
+ refreshEndpoint: '/api/ExternalAuth/refresh'
687
+ });