@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,1531 @@
1
+ "use strict";
2
+ /**
3
+ * 🚀 CENTRALIZED AUTH STORE - THE SINGLE SOURCE OF TRUTH
4
+ *
5
+ * This Zustand store replaces ALL scattered useState patterns for auth-related state.
6
+ * No more prop drilling, no more duplicate loading states, no more auth chaos.
7
+ *
8
+ * Features:
9
+ * - Centralized session, token, and user state
10
+ * - Built-in API calling with auto token refresh
11
+ * - Loading state management for all async operations
12
+ * - Type-safe throughout
13
+ * - Integrates seamlessly with existing NextAuth
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.initializeAuthStore = exports.useAuthStore = void 0;
50
+ const zustand_1 = require("zustand");
51
+ const middleware_1 = require("zustand/middleware");
52
+ const react_1 = require("next-auth/react");
53
+ const session_1 = require("../lib/session");
54
+ const logger_1 = require("../config/logger");
55
+ const signalr_1 = require("@microsoft/signalr");
56
+ const signalrActivityService_1 = require("../services/signalrActivityService");
57
+ const app_slug_1 = require("../lib/app-slug");
58
+ // ===============================
59
+ // CACHE CONFIGURATION
60
+ // ===============================
61
+ const CACHE_DURATION = {
62
+ USER_STATS: 5 * 60 * 1000, // 5 minutes
63
+ CLIENTS: 10 * 60 * 1000, // 10 minutes
64
+ ROLES: 15 * 60 * 1000, // 15 minutes
65
+ USERS: 5 * 60 * 1000, // 5 minutes (dynamic data)
66
+ USER_DETAILS: 2 * 60 * 1000, // 2 minutes (detailed data)
67
+ ROLE_CATEGORIES: 30 * 60 * 1000, // 30 minutes (stable data)
68
+ USER_ASSIGNMENTS: 2 * 60 * 1000, // 2 minutes (assignment data changes frequently)
69
+ };
70
+ // ===============================
71
+ // ZUSTAND STORE IMPLEMENTATION
72
+ // ===============================
73
+ exports.useAuthStore = (0, zustand_1.create)()((0, middleware_1.devtools)((set, get) => ({
74
+ // ===============================
75
+ // INITIAL STATE
76
+ // ===============================
77
+ session: null,
78
+ user: null,
79
+ accessToken: null,
80
+ refreshToken: null,
81
+ isAuthenticated: false,
82
+ isInitialized: false,
83
+ // Loading States
84
+ isLoading: true,
85
+ isRefreshingToken: false,
86
+ isLoadingUserStats: false,
87
+ isLoadingClients: false,
88
+ isLoadingRoles: false,
89
+ isLoadingUsers: false,
90
+ isLoadingUserDetails: {},
91
+ isLoadingRoleCategories: false,
92
+ isLoadingUserAssignments: {},
93
+ isLoadingClientAuthorizations: {},
94
+ // Error States
95
+ error: null,
96
+ tokenError: null,
97
+ // Cached Data
98
+ userStats: null,
99
+ clients: null,
100
+ roles: null,
101
+ users: null,
102
+ userDetails: {},
103
+ userAssignments: {},
104
+ roleCategories: null,
105
+ clientAuthorizations: {},
106
+ // Cache Timestamps
107
+ userStatsLastFetch: null,
108
+ clientsLastFetch: null,
109
+ rolesLastFetch: null,
110
+ usersLastFetch: null,
111
+ roleCategoriesLastFetch: null,
112
+ // SignalR Connection State
113
+ signalrConnection: null,
114
+ signalrConnectionState: signalr_1.HubConnectionState.Disconnected,
115
+ isConnectedToSignalR: false,
116
+ // ===============================
117
+ // SESSION MANAGEMENT ACTIONS
118
+ // ===============================
119
+ setSession: (session) => {
120
+ // CRITICAL FIX: Validate and sanitize the session before setting it
121
+ // This prevents storing sessions with empty user IDs or other invalid data
122
+ const cleanSession = (0, session_1.sanitizeSession)(session);
123
+ if (!cleanSession) {
124
+ // Session is invalid (empty userId, empty email, or missing accessToken)
125
+ logger_1.authLogger.warn('[AuthStore] Rejecting invalid session', {
126
+ hasSession: false,
127
+ reason: 'invalid_or_partial_session',
128
+ hasIncomingSession: !!session,
129
+ incomingUserId: session?.user?.id || '(empty)',
130
+ incomingUserEmail: session?.user?.email || '(empty)',
131
+ hasAccessToken: !!session?.accessToken
132
+ });
133
+ // Clear the session state completely
134
+ set({
135
+ session: null,
136
+ user: null,
137
+ accessToken: null,
138
+ refreshToken: null,
139
+ isAuthenticated: false,
140
+ isInitialized: true,
141
+ isLoading: false,
142
+ error: 'Invalid session data',
143
+ });
144
+ return;
145
+ }
146
+ // Session is valid - proceed with setting it
147
+ logger_1.authLogger.info('[AuthStore] Setting valid session:', {
148
+ hasSession: true,
149
+ userId: cleanSession.user?.id,
150
+ userEmail: cleanSession.user?.email
151
+ });
152
+ const user = {
153
+ id: cleanSession.user?.id || '',
154
+ email: cleanSession.user?.email || '',
155
+ roles: Array.isArray(cleanSession.user?.roles) ? cleanSession.user.roles : [],
156
+ twoFactorSessionVerified: cleanSession.user?.twoFactorSessionVerified || false,
157
+ requiresTwoFactor: cleanSession.user?.requiresTwoFactor || false,
158
+ twoFactorMethod: cleanSession.user?.twoFactorMethod,
159
+ authenticationMethods: cleanSession.user.authenticationMethods,
160
+ authenticationLevel: cleanSession.user.authenticationLevel,
161
+ // Administrative States (with safe defaults)
162
+ isApproved: cleanSession.user.isApproved ?? true,
163
+ isSuspended: cleanSession.user.isSuspended ?? false,
164
+ lockoutEnabled: cleanSession.user.lockoutEnabled ?? false,
165
+ lockoutEnd: cleanSession.user.lockoutEnd ? new Date(cleanSession.user.lockoutEnd) : null,
166
+ // Suspension Metadata
167
+ pausedAt: cleanSession.user.pausedAt ? new Date(cleanSession.user.pausedAt) : null,
168
+ pausedBy: cleanSession.user.pausedBy || null,
169
+ suspensionReason: cleanSession.user.suspensionReason || null,
170
+ };
171
+ set({
172
+ session: cleanSession,
173
+ user,
174
+ accessToken: cleanSession.accessToken || null,
175
+ refreshToken: cleanSession.refreshToken || null,
176
+ // FIXED: Use strict validation - both accessToken AND user.id must be non-empty
177
+ isAuthenticated: true, // Already validated by sanitizeSession
178
+ isInitialized: true,
179
+ isLoading: false,
180
+ error: cleanSession.error || null,
181
+ });
182
+ // Auto-initialize SignalR for authenticated users
183
+ // Use a longer delay and add error handling to prevent login blocking
184
+ setTimeout(async () => {
185
+ try {
186
+ await get().initializeSignalR();
187
+ }
188
+ catch (error) {
189
+ // Don't let SignalR initialization errors block the login process
190
+ logger_1.authLogger.warn('[AuthStore] SignalR initialization failed (non-blocking):', error);
191
+ }
192
+ }, 500); // Longer delay to ensure login flow completes first
193
+ },
194
+ clearSession: () => {
195
+ logger_1.authLogger.debug('[AuthStore] Clearing session');
196
+ // Disconnect SignalR before clearing session
197
+ get().disconnectSignalR();
198
+ set({
199
+ session: null,
200
+ user: null,
201
+ accessToken: null,
202
+ refreshToken: null,
203
+ isAuthenticated: false,
204
+ error: null,
205
+ tokenError: null,
206
+ // Keep cache data but clear sensitive auth data
207
+ });
208
+ },
209
+ refreshSession: async () => {
210
+ const state = get();
211
+ if (!state.session?.sessionToken || !state.user?.id || state.isRefreshingToken) {
212
+ return;
213
+ }
214
+ set({ isRefreshingToken: true, tokenError: null });
215
+ try {
216
+ // Call refresh endpoint directly (no middleware chaos)
217
+ const stateBefore = get();
218
+ const refreshResponse = await fetch('/api/auth/refresh', {
219
+ method: 'POST',
220
+ headers: {
221
+ 'Content-Type': 'application/json',
222
+ 'X-Session-Token': stateBefore.session?.sessionToken || '',
223
+ 'X-Request-Source': 'authStore.refreshSession'
224
+ },
225
+ credentials: 'include'
226
+ });
227
+ if (!refreshResponse.ok) {
228
+ const refreshError = await refreshResponse.json().catch(() => ({ message: 'Token refresh failed' }));
229
+ throw new Error(refreshError.message || 'Token refresh failed');
230
+ }
231
+ // TODO: Get updated session from Redis after successful refresh (temp disabled for client build)
232
+ // For now, assume refresh was successful
233
+ // TODO: Update session with new tokens from Redis (temp disabled)
234
+ // For now, assume session tokens are already updated by the refresh endpoint
235
+ logger_1.authLogger.info('[AuthStore] Token refresh successful via direct /api/session/refresh');
236
+ }
237
+ catch (error) {
238
+ const errorMessage = error instanceof Error ? error.message : 'Token refresh failed';
239
+ logger_1.authLogger.error('[AuthStore] Token refresh failed:', error);
240
+ set({
241
+ tokenError: errorMessage,
242
+ isAuthenticated: false,
243
+ });
244
+ // If refresh fails, sign out
245
+ await get().signOut();
246
+ }
247
+ finally {
248
+ set({ isRefreshingToken: false });
249
+ }
250
+ },
251
+ // ===============================
252
+ // AUTHENTICATION ACTIONS
253
+ // ===============================
254
+ signIn: async (credentials) => {
255
+ set({ isLoading: true, error: null });
256
+ try {
257
+ // Use NextAuth signIn - this will trigger our auth callbacks
258
+ const { signIn } = await Promise.resolve().then(() => __importStar(require('next-auth/react')));
259
+ const result = await signIn('credentials', {
260
+ ...credentials,
261
+ redirect: false,
262
+ });
263
+ if (result?.ok) {
264
+ logger_1.authLogger.info('[AuthStore] Sign in successful');
265
+ return true;
266
+ }
267
+ else {
268
+ const errorMessage = result?.error || 'Sign in failed';
269
+ set({ error: errorMessage, isLoading: false });
270
+ return false;
271
+ }
272
+ }
273
+ catch (error) {
274
+ const errorMessage = error instanceof Error ? error.message : 'Sign in failed';
275
+ logger_1.authLogger.error('[AuthStore] Sign in error:', error);
276
+ set({ error: errorMessage, isLoading: false });
277
+ return false;
278
+ }
279
+ },
280
+ signOut: async () => {
281
+ logger_1.authLogger.info('[AuthStore] Starting sign out process');
282
+ // Clear local state immediately
283
+ get().clearSession();
284
+ // Clear cached data
285
+ set({
286
+ userStats: null,
287
+ clients: null,
288
+ roles: null,
289
+ userStatsLastFetch: null,
290
+ clientsLastFetch: null,
291
+ rolesLastFetch: null,
292
+ });
293
+ try {
294
+ // Use NextAuth signOut
295
+ await (0, react_1.signOut)({ redirect: false });
296
+ logger_1.authLogger.info('[AuthStore] Sign out completed');
297
+ }
298
+ catch (error) {
299
+ logger_1.authLogger.error('[AuthStore] Sign out error:', error);
300
+ // Even if signOut fails, we've cleared local state
301
+ }
302
+ },
303
+ forceLogoutAndRedirect: async (reason) => {
304
+ const state = get();
305
+ // AGGRESSIVE LOGGING TO TRACK EXECUTION
306
+ console.error('🚨 FORCE LOGOUT INITIATED 🚨', {
307
+ reason,
308
+ userId: state.user?.id,
309
+ sessionToken: state.session?.sessionToken,
310
+ timestamp: new Date().toISOString()
311
+ });
312
+ logger_1.authLogger.error('[AuthStore] Force logout initiated', {
313
+ reason,
314
+ userId: state.user?.id,
315
+ sessionToken: state.session?.sessionToken
316
+ });
317
+ try {
318
+ // TODO: Step 1: Mark session as force-invalidated in Redis (temp disabled for client build)
319
+ // Step 2: Clear local state immediately
320
+ get().clearSession();
321
+ // Step 3: Clear all cached data
322
+ set({
323
+ userStats: null,
324
+ clients: null,
325
+ roles: null,
326
+ userStatsLastFetch: null,
327
+ clientsLastFetch: null,
328
+ rolesLastFetch: null,
329
+ });
330
+ // Step 4: Clear session cache (temp disabled for client build)
331
+ // TODO: Re-enable when session-cache module is available
332
+ // const { SessionCache } = await import('@/lib/session-cache');
333
+ // SessionCache.clearCache();
334
+ // SessionCache.clearServerSessionData();
335
+ // Step 5: AGGRESSIVELY clear NextAuth cookies immediately before signOut (app-slug prefixed)
336
+ if (typeof document !== 'undefined') {
337
+ const cookiesToClear = [
338
+ (0, app_slug_1.getSessionCookieName)(),
339
+ (0, app_slug_1.getSecureSessionCookieName)(),
340
+ (0, app_slug_1.getCsrfCookieName)(),
341
+ (0, app_slug_1.getSecureCsrfCookieName)()
342
+ ];
343
+ logger_1.authLogger.info('[AuthStore] Aggressively clearing NextAuth cookies immediately');
344
+ cookiesToClear.forEach(cookieName => {
345
+ try {
346
+ const expiredDate = 'Thu, 01 Jan 1970 00:00:00 GMT';
347
+ const clearPatterns = [
348
+ `${cookieName}=; expires=${expiredDate}; path=/`,
349
+ `${cookieName}=; expires=${expiredDate}; path=/; domain=${window.location.hostname}`,
350
+ `${cookieName}=; expires=${expiredDate}; path=/; domain=.${window.location.hostname}`,
351
+ ];
352
+ if (window.location.protocol === 'https:') {
353
+ clearPatterns.push(`${cookieName}=; expires=${expiredDate}; path=/; secure`, `${cookieName}=; expires=${expiredDate}; path=/; domain=${window.location.hostname}; secure`, `${cookieName}=; expires=${expiredDate}; path=/; domain=.${window.location.hostname}; secure`);
354
+ }
355
+ clearPatterns.forEach(pattern => {
356
+ document.cookie = pattern;
357
+ });
358
+ }
359
+ catch (cookieError) {
360
+ logger_1.authLogger.warn(`Failed to clear cookie ${cookieName}:`, cookieError);
361
+ }
362
+ });
363
+ }
364
+ // Step 6: Force NextAuth signOut (this should clear the session cookie)
365
+ const { signOut } = await Promise.resolve().then(() => __importStar(require('next-auth/react')));
366
+ await signOut({ redirect: false });
367
+ logger_1.authLogger.info('[AuthStore] Force logout completed, redirecting to login');
368
+ // Step 7: Longer delay to ensure everything is processed
369
+ await new Promise(resolve => setTimeout(resolve, 1000));
370
+ // Step 8: Force redirect with cache busting - use window.location.href for immediate effect
371
+ const loginUrl = `/account-auth/login?error=SessionExpired&reason=${reason}&t=${Date.now()}`;
372
+ logger_1.authLogger.info('[AuthStore] Redirecting to login:', loginUrl);
373
+ // Double redirect approach to ensure it works
374
+ window.location.replace(loginUrl);
375
+ window.location.href = loginUrl;
376
+ }
377
+ catch (error) {
378
+ logger_1.authLogger.error('[AuthStore] Error during force logout:', error);
379
+ // Fallback: still redirect even if cleanup fails
380
+ const loginUrl = `/account-auth/login?error=ForceLogoutError&reason=${reason}&t=${Date.now()}`;
381
+ window.location.replace(loginUrl);
382
+ }
383
+ },
384
+ // ===============================
385
+ // API CALLING WITH AUTO TOKEN REFRESH
386
+ // ===============================
387
+ // Single centralized refresh method with coordinated server-side coordination
388
+ refreshTokens: async () => {
389
+ const state = get();
390
+ // If already refreshing, wait for it to complete
391
+ if (state.isRefreshingToken) {
392
+ logger_1.authLogger.info('[AuthStore] Client-side token refresh already in progress, waiting for completion');
393
+ // Wait for the refresh to complete by polling the flag
394
+ const maxWaitMs = 15000; // 15 seconds max wait
395
+ const startTime = Date.now();
396
+ while (get().isRefreshingToken && (Date.now() - startTime) < maxWaitMs) {
397
+ await new Promise(resolve => setTimeout(resolve, 100));
398
+ }
399
+ // Check if refresh was successful by verifying we still have a valid session
400
+ const updatedState = get();
401
+ if (updatedState.isRefreshingToken) {
402
+ logger_1.authLogger.warn('[AuthStore] Client-side token refresh timed out');
403
+ throw new Error('Token refresh timed out - another refresh operation is stuck');
404
+ }
405
+ if (!updatedState.isAuthenticated || !updatedState.session) {
406
+ logger_1.authLogger.error('[AuthStore] Token refresh failed - session invalid after refresh');
407
+ throw new Error('Token refresh failed - session invalid after refresh');
408
+ }
409
+ logger_1.authLogger.info('[AuthStore] Token refresh completed by another caller');
410
+ return;
411
+ }
412
+ // Set refreshing flag for client-side coordination
413
+ set({ isRefreshingToken: true });
414
+ logger_1.authLogger.info('[AuthStore] Starting coordinated token refresh');
415
+ try {
416
+ // COORDINATED REFRESH: The server now handles distributed locking
417
+ // We just need to make the refresh call and let the server coordinate
418
+ // Resolve session token for header
419
+ const resolveSessionTokenForHeader = async () => {
420
+ let token = get().session?.sessionToken;
421
+ if (token)
422
+ return token;
423
+ try {
424
+ const { getSession } = await Promise.resolve().then(() => __importStar(require('next-auth/react')));
425
+ for (let attempt = 1; attempt <= 3 && !token; attempt++) {
426
+ const s = await getSession();
427
+ token = s?.sessionToken;
428
+ if (!token) {
429
+ await new Promise(r => setTimeout(r, 150));
430
+ }
431
+ }
432
+ }
433
+ catch {
434
+ logger_1.authLogger.warn('[AuthStore] Failed to resolve session token from NextAuth during refresh');
435
+ }
436
+ return token;
437
+ };
438
+ const sessionTokenHeader = await resolveSessionTokenForHeader();
439
+ const refreshResponse = await fetch('/api/auth/refresh', {
440
+ method: 'POST',
441
+ headers: {
442
+ 'Content-Type': 'application/json',
443
+ 'X-Client-Refresh': 'true',
444
+ 'X-Request-Source': 'authStore',
445
+ ...(sessionTokenHeader ? { 'X-Session-Token': sessionTokenHeader } : {})
446
+ },
447
+ credentials: 'include'
448
+ });
449
+ if (!refreshResponse.ok) {
450
+ if (refreshResponse.status === 429 || refreshResponse.status === 409) {
451
+ const retryAfter = refreshResponse.headers.get('Retry-After');
452
+ const waitMs = retryAfter ? parseInt(retryAfter) * 1000 : 2000;
453
+ logger_1.authLogger.info('[AuthStore] Server coordinated refresh in progress, waiting and retrying', {
454
+ status: refreshResponse.status,
455
+ retryAfterMs: waitMs
456
+ });
457
+ await new Promise(resolve => setTimeout(resolve, Math.min(waitMs, 5000)));
458
+ await get().rehydrateSessionAfterRefresh();
459
+ logger_1.authLogger.info('[AuthStore] Successfully coordinated with server-side refresh');
460
+ return;
461
+ }
462
+ const refreshError = await refreshResponse.json().catch(() => ({ message: 'Token refresh failed' }));
463
+ logger_1.authLogger.error('[AuthStore] Token refresh failed:', refreshError);
464
+ set({
465
+ session: null,
466
+ accessToken: null,
467
+ refreshToken: null,
468
+ isAuthenticated: false,
469
+ user: null
470
+ });
471
+ await get().forceLogoutAndRedirect('TokenRefreshFailed');
472
+ throw new Error(`Token refresh failed: ${refreshError.message}`);
473
+ }
474
+ logger_1.authLogger.info('[AuthStore] Coordinated token refresh successful on backend, rehydrating session');
475
+ await get().rehydrateSessionAfterRefresh();
476
+ }
477
+ catch (error) {
478
+ logger_1.authLogger.error('[AuthStore] Coordinated token refresh exception:', error);
479
+ const errorMessage = error instanceof Error ? error.message : String(error);
480
+ const isCoordinationError = errorMessage.includes('coordination') ||
481
+ errorMessage.includes('timeout') ||
482
+ errorMessage.includes('another refresh');
483
+ if (!isCoordinationError) {
484
+ set({
485
+ session: null,
486
+ accessToken: null,
487
+ refreshToken: null,
488
+ isAuthenticated: false,
489
+ user: null
490
+ });
491
+ await get().forceLogoutAndRedirect('TokenRefreshException');
492
+ }
493
+ throw error;
494
+ }
495
+ finally {
496
+ set({ isRefreshingToken: false });
497
+ }
498
+ },
499
+ // Atomic session rehydration after token refresh
500
+ // This ensures the authStore gets the fresh tokens from the session store
501
+ rehydrateSessionAfterRefresh: async () => {
502
+ const maxRetries = 3;
503
+ const retryDelay = 500; // 500ms between retries
504
+ logger_1.authLogger.info('[AuthStore] Starting session rehydration after token refresh');
505
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
506
+ try {
507
+ // Force NextAuth to reload the session from the session store
508
+ // This triggers the JWT callback which reads fresh data from Redis
509
+ const { getSession } = await Promise.resolve().then(() => __importStar(require('next-auth/react')));
510
+ logger_1.authLogger.debug(`[AuthStore] Rehydration attempt ${attempt}/${maxRetries}`);
511
+ // Force session refresh - this calls NextAuth's session endpoint
512
+ // which triggers JWT callback to read fresh tokens from Redis
513
+ const freshSession = await getSession();
514
+ if (!freshSession) {
515
+ throw new Error('No session returned from NextAuth after refresh');
516
+ }
517
+ if (!freshSession.accessToken) {
518
+ throw new Error('Fresh session missing access token');
519
+ }
520
+ // Verify the token is actually fresh (not expired)
521
+ try {
522
+ const { jwtDecode } = await Promise.resolve().then(() => __importStar(require('@/lib/jwt-decode')));
523
+ const decoded = jwtDecode(freshSession.accessToken);
524
+ if (!decoded?.exp) {
525
+ throw new Error('Fresh token missing expiration claim');
526
+ }
527
+ const tokenExpiry = decoded.exp * 1000;
528
+ const now = Date.now();
529
+ const timeUntilExpiry = tokenExpiry - now;
530
+ // Token should be fresh (not expiring in next 5 minutes)
531
+ if (timeUntilExpiry < (5 * 60 * 1000)) {
532
+ throw new Error(`Fresh token still expires soon: ${timeUntilExpiry}ms`);
533
+ }
534
+ logger_1.authLogger.info('[AuthStore] Session rehydration successful', {
535
+ attempt,
536
+ tokenExpiresAt: new Date(tokenExpiry).toISOString(),
537
+ timeUntilExpiry: `${Math.round(timeUntilExpiry / 1000)}s`,
538
+ subject: decoded.sub
539
+ });
540
+ // Update the authStore with the fresh session atomically
541
+ get().setSession(freshSession);
542
+ logger_1.authLogger.info('[AuthStore] AuthStore updated with fresh tokens');
543
+ return; // Success!
544
+ }
545
+ catch (tokenError) {
546
+ throw new Error(`Token validation failed: ${tokenError instanceof Error ? tokenError.message : String(tokenError)}`);
547
+ }
548
+ }
549
+ catch (error) {
550
+ const errorMessage = error instanceof Error ? error.message : String(error);
551
+ logger_1.authLogger.warn(`[AuthStore] Rehydration attempt ${attempt} failed:`, errorMessage);
552
+ // If this is the last attempt, throw the error
553
+ if (attempt === maxRetries) {
554
+ logger_1.authLogger.error('[AuthStore] Session rehydration failed after all retries');
555
+ throw new Error(`Session rehydration failed after ${maxRetries} attempts: ${errorMessage}`);
556
+ }
557
+ // Wait before retrying
558
+ logger_1.authLogger.debug(`[AuthStore] Waiting ${retryDelay}ms before retry ${attempt + 1}`);
559
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
560
+ }
561
+ }
562
+ },
563
+ apiCall: async (url, options = {}, maxRetries = 3) => {
564
+ // UNIVERSAL RETRY WRAPPER: Handle 503 retries at the store level
565
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
566
+ try {
567
+ return await get().makeApiCall(url, options, attempt);
568
+ }
569
+ catch (error) {
570
+ const isRetryableError = error.isRetryable || error.message?.includes('Token refresh in progress');
571
+ if (isRetryableError && attempt < maxRetries) {
572
+ const retryAfter = error.retryAfter || 1;
573
+ logger_1.authLogger.info(`[AuthStore] API call attempt ${attempt} failed with retryable error, retrying in ${retryAfter}s`, {
574
+ url,
575
+ error: error.message,
576
+ attempt,
577
+ maxRetries
578
+ });
579
+ await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
580
+ continue;
581
+ }
582
+ // Not retryable or max attempts reached
583
+ throw error;
584
+ }
585
+ }
586
+ // This should never be reached but TypeScript needs it
587
+ throw new Error('API call failed after all retry attempts');
588
+ },
589
+ makeApiCall: async (url, options = {}, attempt = 1) => {
590
+ const state = get();
591
+ // COORDINATED AUTH: Check authentication and refresh coordination
592
+ if (!state.isAuthenticated || !state.session?.sessionToken || !state.user?.id) {
593
+ throw new Error('Not authenticated');
594
+ }
595
+ // COORDINATED REFRESH: Check if token refresh is in progress
596
+ if (state.isRefreshingToken) {
597
+ logger_1.authLogger.info('[AuthStore] API call detected refresh in progress, waiting for completion', { url, attempt });
598
+ // Wait for refresh to complete before making API call
599
+ const maxWaitMs = 10000; // 10 seconds max wait
600
+ const startTime = Date.now();
601
+ while (get().isRefreshingToken && (Date.now() - startTime) < maxWaitMs) {
602
+ await new Promise(resolve => setTimeout(resolve, 100));
603
+ }
604
+ const updatedState = get();
605
+ if (updatedState.isRefreshingToken) {
606
+ logger_1.authLogger.warn('[AuthStore] API call timed out waiting for refresh', { url, attempt });
607
+ throw new Error('Request failed - token refresh in progress');
608
+ }
609
+ if (!updatedState.isAuthenticated || !updatedState.session) {
610
+ logger_1.authLogger.error('[AuthStore] Session lost during refresh wait', { url, attempt });
611
+ throw new Error('Authentication lost during token refresh');
612
+ }
613
+ logger_1.authLogger.info('[AuthStore] API call proceeding after refresh completion', { url, attempt });
614
+ }
615
+ // COORDINATED API CALL: Make API call using NextAuth session cookies
616
+ // Server-side handlers will coordinate any needed token refresh
617
+ const response = await fetch(url, {
618
+ ...options,
619
+ credentials: 'include', // Ensure session cookies are sent
620
+ headers: {
621
+ 'Content-Type': 'application/json',
622
+ 'X-Client-Call': 'true', // Indicate this is a client-initiated call
623
+ 'X-Request-Source': 'authStore',
624
+ ...options.headers,
625
+ },
626
+ });
627
+ if (!response.ok) {
628
+ // COORDINATED ERROR HANDLING: Handle various coordination scenarios
629
+ if (response.status === 401) {
630
+ logger_1.authLogger.info('[AuthStore] Received 401, attempting coordinated refresh and retry', { url, attempt });
631
+ // If not currently refreshing and we haven't retried yet, attempt refresh then retry once
632
+ if (!get().isRefreshingToken && (attempt ?? 1) < 2) {
633
+ try {
634
+ await get().refreshTokens();
635
+ logger_1.authLogger.info('[AuthStore] Refresh complete, retrying API call once', { url, nextAttempt: (attempt ?? 1) + 1 });
636
+ return await get().makeApiCall(url, options, (attempt ?? 1) + 1);
637
+ }
638
+ catch (refreshError) {
639
+ logger_1.authLogger.error('[AuthStore] Refresh attempt failed after 401', refreshError);
640
+ // fall through to force logout below
641
+ }
642
+ }
643
+ else if (get().isRefreshingToken) {
644
+ logger_1.authLogger.info('[AuthStore] 401 received during in-progress refresh - treating as coordination issue');
645
+ }
646
+ // If we reach here, refresh was not attempted or failed; force logout
647
+ await get().forceLogoutAndRedirect('ApiCall401');
648
+ throw new Error('Authentication failed');
649
+ }
650
+ // Handle server coordination responses
651
+ if (response.status === 503) {
652
+ const retryAfter = response.headers.get('Retry-After');
653
+ logger_1.authLogger.info('[AuthStore] Received 503 (service unavailable), likely token refresh in progress', {
654
+ url,
655
+ retryAfter
656
+ });
657
+ // This is a retryable condition - let the calling code handle retries
658
+ const error = new Error('Token refresh in progress');
659
+ error.retryAfter = parseInt(retryAfter || '1', 10);
660
+ error.isRetryable = true;
661
+ throw error;
662
+ }
663
+ if (response.status === 429) {
664
+ const retryAfter = response.headers.get('Retry-After');
665
+ logger_1.authLogger.info('[AuthStore] Received 429 (rate limit/coordination), server busy', {
666
+ url,
667
+ retryAfter
668
+ });
669
+ throw new Error(`Server busy - retry after ${retryAfter || '1'} second(s)`);
670
+ }
671
+ if (response.status === 409) {
672
+ // Distinguish business conflict (e.g., duplicate email/username) from refresh coordination
673
+ let body = null;
674
+ try {
675
+ body = await response.clone().json();
676
+ }
677
+ catch { }
678
+ const code = body?.error?.code || body?.code;
679
+ const message = body?.error?.message || body?.message;
680
+ const msgLower = typeof message === 'string' ? message.toLowerCase() : '';
681
+ // PayEz-standard conflicts we want to surface to the UI
682
+ if (code === 'RESOURCE_CONFLICT' ||
683
+ code === 'USERNAME_ALREADY_EXISTS' ||
684
+ code === 'EMAIL_ALREADY_EXISTS' ||
685
+ (msgLower.includes('duplicate') || msgLower.includes('already taken'))) {
686
+ const err = new Error(message || 'Resource conflict');
687
+ err.status = 409;
688
+ err.data = body;
689
+ throw err;
690
+ }
691
+ // Otherwise treat as coordination conflict (e.g., refresh lock)
692
+ logger_1.authLogger.info('[AuthStore] Received 409 (conflict) without recognizable business code; treating as refresh coordination issue', { url });
693
+ const err = new Error('Request conflict - token refresh in progress on server');
694
+ err.status = 409;
695
+ err.data = body;
696
+ throw err;
697
+ }
698
+ // Handle other HTTP errors: throw an error with details
699
+ const errorData = await response.json().catch(() => ({}));
700
+ const errorMessage = errorData.message || errorData.error || response.statusText || 'Request failed';
701
+ const error = new Error(errorMessage);
702
+ error.status = response.status;
703
+ error.data = errorData;
704
+ throw error;
705
+ }
706
+ return await response.json();
707
+ },
708
+ // ===============================
709
+ // DATA FETCHING ACTIONS
710
+ // ===============================
711
+ fetchUserStats: async (force = false) => {
712
+ const state = get();
713
+ const now = Date.now();
714
+ // Check cache unless forced
715
+ if (!force && state.userStats && state.userStatsLastFetch) {
716
+ const age = now - state.userStatsLastFetch;
717
+ if (age < CACHE_DURATION.USER_STATS) {
718
+ logger_1.authLogger.debug('[AuthStore] Using cached user stats');
719
+ return;
720
+ }
721
+ }
722
+ set({ isLoadingUserStats: true });
723
+ try {
724
+ const data = await get().apiCall('/api/activity/user-stats');
725
+ set({
726
+ userStats: data.data || data,
727
+ userStatsLastFetch: now,
728
+ isLoadingUserStats: false,
729
+ });
730
+ logger_1.authLogger.debug('[AuthStore] User stats fetched successfully');
731
+ }
732
+ catch (error) {
733
+ const errorMessage = error instanceof Error ? error.message : String(error);
734
+ // Don't log errors if we're already signed out (forced logout scenario)
735
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
736
+ logger_1.authLogger.error('[AuthStore] Failed to fetch user stats:', error);
737
+ }
738
+ set({ isLoadingUserStats: false });
739
+ // Don't throw if we're in a forced logout scenario
740
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
741
+ throw error;
742
+ }
743
+ }
744
+ },
745
+ fetchClients: async (force = false) => {
746
+ const state = get();
747
+ const now = Date.now();
748
+ // Check cache unless forced
749
+ if (!force && state.clients && state.clientsLastFetch) {
750
+ const age = now - state.clientsLastFetch;
751
+ if (age < CACHE_DURATION.CLIENTS) {
752
+ logger_1.authLogger.debug('[AuthStore] Using cached clients');
753
+ return;
754
+ }
755
+ }
756
+ set({ isLoadingClients: true });
757
+ try {
758
+ const data = await get().apiCall('/api/admin/clients');
759
+ // API returns pure array - no envelope
760
+ set({
761
+ clients: Array.isArray(data) ? data : [],
762
+ clientsLastFetch: now,
763
+ isLoadingClients: false,
764
+ });
765
+ logger_1.authLogger.debug('[AuthStore] Clients fetched successfully');
766
+ }
767
+ catch (error) {
768
+ const errorMessage = error instanceof Error ? error.message : String(error);
769
+ // Don't log errors if we're already signed out (forced logout scenario)
770
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
771
+ logger_1.authLogger.error('[AuthStore] Failed to fetch clients:', error);
772
+ }
773
+ set({ isLoadingClients: false });
774
+ // Don't throw if we're in a forced logout scenario
775
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
776
+ throw error;
777
+ }
778
+ }
779
+ },
780
+ fetchRoles: async (force = false) => {
781
+ const state = get();
782
+ const now = Date.now();
783
+ // Check cache unless forced
784
+ if (!force && state.roles && state.rolesLastFetch) {
785
+ const age = now - state.rolesLastFetch;
786
+ if (age < CACHE_DURATION.ROLES) {
787
+ logger_1.authLogger.debug('[AuthStore] Using cached roles');
788
+ return;
789
+ }
790
+ }
791
+ set({ isLoadingRoles: true });
792
+ try {
793
+ const data = await get().apiCall('/api/admin/roles');
794
+ // API returns pure array - no envelope
795
+ set({
796
+ roles: Array.isArray(data) ? data : [],
797
+ rolesLastFetch: now,
798
+ isLoadingRoles: false,
799
+ });
800
+ logger_1.authLogger.debug('[AuthStore] Roles fetched successfully');
801
+ }
802
+ catch (error) {
803
+ const errorMessage = error instanceof Error ? error.message : String(error);
804
+ // Don't log errors if we're already signed out (forced logout scenario)
805
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
806
+ logger_1.authLogger.error('[AuthStore] Failed to fetch roles:', error);
807
+ }
808
+ set({ isLoadingRoles: false });
809
+ // Don't throw if we're in a forced logout scenario
810
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
811
+ throw error;
812
+ }
813
+ }
814
+ },
815
+ fetchUsers: async (params = {}, force = false) => {
816
+ const state = get();
817
+ const now = Date.now();
818
+ // Check cache unless forced (users data changes frequently, shorter cache)
819
+ if (!force && state.users && state.usersLastFetch) {
820
+ const age = now - state.usersLastFetch;
821
+ if (age < CACHE_DURATION.USERS) {
822
+ logger_1.authLogger.debug('[AuthStore] Using cached users');
823
+ return;
824
+ }
825
+ }
826
+ set({ isLoadingUsers: true });
827
+ try {
828
+ // Always use POST for /api/admin/users with appropriate payload
829
+ const url = `/api/admin/users`;
830
+ // Build proper UserGridRequest with all required fields
831
+ const gridParams = {
832
+ page_number: params.page || 1,
833
+ page_size: params.pageSize || 25,
834
+ search: "",
835
+ search_field: "",
836
+ sort_field: "",
837
+ sort_order: "",
838
+ status: null,
839
+ merchant_id: "",
840
+ client_assignment_status: null
841
+ };
842
+ const options = { method: 'POST', body: JSON.stringify(gridParams) };
843
+ const data = await get().apiCall(url, options);
844
+ // API returns pure array - no envelope
845
+ const users = Array.isArray(data) ? data : [];
846
+ set({
847
+ users: Array.isArray(users) ? users : [],
848
+ usersLastFetch: now,
849
+ isLoadingUsers: false,
850
+ });
851
+ logger_1.authLogger.debug('[AuthStore] Users fetched successfully');
852
+ }
853
+ catch (error) {
854
+ const errorMessage = error instanceof Error ? error.message : String(error);
855
+ logger_1.authLogger.error('[AuthStore] Failed to fetch users:', error);
856
+ set({ isLoadingUsers: false });
857
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
858
+ throw error;
859
+ }
860
+ }
861
+ },
862
+ fetchUserDetails: async (userId, force = false) => {
863
+ const state = get();
864
+ const now = Date.now();
865
+ // Check cache unless forced
866
+ const cacheKey = `user_${userId}`;
867
+ const lastFetch = state.userDetails[`${cacheKey}_lastFetch`];
868
+ if (!force && state.userDetails[userId] && lastFetch) {
869
+ const age = now - lastFetch;
870
+ if (age < CACHE_DURATION.USER_DETAILS) {
871
+ logger_1.authLogger.debug(`[AuthStore] Using cached user details for ${userId}`);
872
+ return;
873
+ }
874
+ }
875
+ set({
876
+ isLoadingUserDetails: { ...state.isLoadingUserDetails, [userId]: true }
877
+ });
878
+ try {
879
+ const data = await get().apiCall(`/api/admin/users/${userId}`);
880
+ set({
881
+ userDetails: {
882
+ ...state.userDetails,
883
+ [userId]: data.data || data,
884
+ [`${cacheKey}_lastFetch`]: now
885
+ },
886
+ isLoadingUserDetails: { ...state.isLoadingUserDetails, [userId]: false }
887
+ });
888
+ logger_1.authLogger.debug(`[AuthStore] User details fetched for ${userId}`);
889
+ }
890
+ catch (error) {
891
+ const errorMessage = error instanceof Error ? error.message : String(error);
892
+ logger_1.authLogger.error(`[AuthStore] Failed to fetch user details for ${userId}:`, error);
893
+ set({
894
+ isLoadingUserDetails: { ...state.isLoadingUserDetails, [userId]: false }
895
+ });
896
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
897
+ throw error;
898
+ }
899
+ }
900
+ },
901
+ fetchUserClientAuthorizations: async (userId, force = false) => {
902
+ const state = get();
903
+ const now = Date.now();
904
+ const cacheKey = `auth_${userId}`;
905
+ // Check if already loading
906
+ if (state.isLoadingClientAuthorizations[userId]) {
907
+ return;
908
+ }
909
+ // Check cache unless forced
910
+ if (!force && state.clientAuthorizations[userId]) {
911
+ logger_1.authLogger.debug(`[AuthStore] Using cached client authorizations for ${userId}`);
912
+ return;
913
+ }
914
+ set({
915
+ isLoadingClientAuthorizations: { ...state.isLoadingClientAuthorizations, [userId]: true }
916
+ });
917
+ try {
918
+ const data = await get().apiCall(`/api/admin/users/client-authorizations?user_id=${userId}`);
919
+ // Handle various response formats
920
+ let authorizations = [];
921
+ if (data.data) {
922
+ authorizations = data.data.authorizations || data.data || [];
923
+ }
924
+ else {
925
+ authorizations = data.authorizations || data || [];
926
+ }
927
+ set({
928
+ clientAuthorizations: {
929
+ ...state.clientAuthorizations,
930
+ [userId]: Array.isArray(authorizations) ? authorizations : []
931
+ },
932
+ isLoadingClientAuthorizations: { ...state.isLoadingClientAuthorizations, [userId]: false }
933
+ });
934
+ logger_1.authLogger.debug(`[AuthStore] Client authorizations fetched for ${userId}`);
935
+ }
936
+ catch (error) {
937
+ const errorMessage = error instanceof Error ? error.message : String(error);
938
+ logger_1.authLogger.error(`[AuthStore] Failed to fetch client authorizations for ${userId}:`, error);
939
+ set({
940
+ clientAuthorizations: {
941
+ ...state.clientAuthorizations,
942
+ [userId]: []
943
+ },
944
+ isLoadingClientAuthorizations: { ...state.isLoadingClientAuthorizations, [userId]: false }
945
+ });
946
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
947
+ throw error;
948
+ }
949
+ }
950
+ },
951
+ fetchUserRoleAssignments: async (userId, force = false) => {
952
+ const state = get();
953
+ const cacheKey = `assignments_${userId}`;
954
+ if (state.isLoadingUserAssignments[userId]) {
955
+ return;
956
+ }
957
+ if (!force && state.userAssignments[userId]) {
958
+ logger_1.authLogger.debug(`[AuthStore] Using cached role assignments for ${userId}`);
959
+ return;
960
+ }
961
+ set({
962
+ isLoadingUserAssignments: { ...state.isLoadingUserAssignments, [userId]: true }
963
+ });
964
+ try {
965
+ const data = await get().apiCall(`/api/admin/users/role-assignments?user_id=${userId}`);
966
+ let assignments = [];
967
+ if (data.data) {
968
+ assignments = data.data.assignments || data.data || [];
969
+ }
970
+ else {
971
+ assignments = data.assignments || data || [];
972
+ }
973
+ set({
974
+ userAssignments: {
975
+ ...state.userAssignments,
976
+ [userId]: Array.isArray(assignments) ? assignments : []
977
+ },
978
+ isLoadingUserAssignments: { ...state.isLoadingUserAssignments, [userId]: false }
979
+ });
980
+ logger_1.authLogger.debug(`[AuthStore] Role assignments fetched for ${userId}`);
981
+ }
982
+ catch (error) {
983
+ const errorMessage = error instanceof Error ? error.message : String(error);
984
+ logger_1.authLogger.error(`[AuthStore] Failed to fetch role assignments for ${userId}:`, error);
985
+ set({
986
+ userAssignments: {
987
+ ...state.userAssignments,
988
+ [userId]: []
989
+ },
990
+ isLoadingUserAssignments: { ...state.isLoadingUserAssignments, [userId]: false }
991
+ });
992
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
993
+ throw error;
994
+ }
995
+ }
996
+ },
997
+ fetchRoleCategories: async (force = false) => {
998
+ const state = get();
999
+ const now = Date.now();
1000
+ // Check cache unless forced (role categories are fairly stable)
1001
+ if (!force && state.roleCategories && state.roleCategoriesLastFetch) {
1002
+ const age = now - state.roleCategoriesLastFetch;
1003
+ if (age < CACHE_DURATION.ROLE_CATEGORIES) {
1004
+ logger_1.authLogger.debug('[AuthStore] Using cached role categories');
1005
+ return;
1006
+ }
1007
+ }
1008
+ set({ isLoadingRoleCategories: true });
1009
+ try {
1010
+ const data = await get().apiCall('/api/admin/roles/categories');
1011
+ set({
1012
+ roleCategories: Array.isArray(data) ? data : (data.data || []),
1013
+ roleCategoriesLastFetch: now,
1014
+ isLoadingRoleCategories: false,
1015
+ });
1016
+ logger_1.authLogger.debug('[AuthStore] Role categories fetched successfully');
1017
+ }
1018
+ catch (error) {
1019
+ const errorMessage = error instanceof Error ? error.message : String(error);
1020
+ logger_1.authLogger.error('[AuthStore] Failed to fetch role categories:', error);
1021
+ set({ isLoadingRoleCategories: false });
1022
+ if (!errorMessage.includes('Session expired') && !errorMessage.includes('Authentication failed')) {
1023
+ throw error;
1024
+ }
1025
+ }
1026
+ },
1027
+ // ===============================
1028
+ // CRUD OPERATIONS
1029
+ // ===============================
1030
+ createUser: async (userData) => {
1031
+ try {
1032
+ const result = await get().apiCall('/api/admin/users', {
1033
+ method: 'POST',
1034
+ body: JSON.stringify(userData)
1035
+ });
1036
+ // Invalidate users cache to force refresh
1037
+ set({ usersLastFetch: null });
1038
+ logger_1.authLogger.info('[AuthStore] User created successfully');
1039
+ return result;
1040
+ }
1041
+ catch (error) {
1042
+ logger_1.authLogger.error('[AuthStore] Failed to create user:', error);
1043
+ throw error;
1044
+ }
1045
+ },
1046
+ updateUser: async (userId, updates) => {
1047
+ try {
1048
+ const result = await get().apiCall(`/api/admin/users/${userId}`, {
1049
+ method: 'PUT',
1050
+ body: JSON.stringify(updates)
1051
+ });
1052
+ // Invalidate relevant caches
1053
+ const state = get();
1054
+ set({
1055
+ usersLastFetch: null,
1056
+ userDetails: {
1057
+ ...state.userDetails,
1058
+ [userId]: undefined // Force refresh of this user's details
1059
+ }
1060
+ });
1061
+ logger_1.authLogger.info(`[AuthStore] User ${userId} updated successfully`);
1062
+ return result;
1063
+ }
1064
+ catch (error) {
1065
+ logger_1.authLogger.error(`[AuthStore] Failed to update user ${userId}:`, error);
1066
+ throw error;
1067
+ }
1068
+ },
1069
+ deleteUser: async (userId) => {
1070
+ try {
1071
+ await get().apiCall(`/api/admin/users/${userId}`, {
1072
+ method: 'DELETE'
1073
+ });
1074
+ // Clean up all related data for this user
1075
+ const state = get();
1076
+ const newUserDetails = { ...state.userDetails };
1077
+ const newUserAssignments = { ...state.userAssignments };
1078
+ const newClientAuthorizations = { ...state.clientAuthorizations };
1079
+ delete newUserDetails[userId];
1080
+ delete newUserAssignments[userId];
1081
+ delete newClientAuthorizations[userId];
1082
+ set({
1083
+ usersLastFetch: null, // Force refresh
1084
+ userDetails: newUserDetails,
1085
+ userAssignments: newUserAssignments,
1086
+ clientAuthorizations: newClientAuthorizations
1087
+ });
1088
+ logger_1.authLogger.info(`[AuthStore] User ${userId} deleted successfully`);
1089
+ }
1090
+ catch (error) {
1091
+ logger_1.authLogger.error(`[AuthStore] Failed to delete user ${userId}:`, error);
1092
+ throw error;
1093
+ }
1094
+ },
1095
+ createRole: async (roleData) => {
1096
+ try {
1097
+ const result = await get().apiCall('/api/admin/roles', {
1098
+ method: 'POST',
1099
+ body: JSON.stringify(roleData)
1100
+ });
1101
+ // Invalidate roles cache
1102
+ set({ rolesLastFetch: null });
1103
+ logger_1.authLogger.info('[AuthStore] Role created successfully');
1104
+ return result;
1105
+ }
1106
+ catch (error) {
1107
+ logger_1.authLogger.error('[AuthStore] Failed to create role:', error);
1108
+ throw error;
1109
+ }
1110
+ },
1111
+ updateRole: async (roleId, updates) => {
1112
+ try {
1113
+ const result = await get().apiCall(`/api/admin/roles/${roleId}`, {
1114
+ method: 'PUT',
1115
+ body: JSON.stringify(updates)
1116
+ });
1117
+ set({ rolesLastFetch: null });
1118
+ logger_1.authLogger.info(`[AuthStore] Role ${roleId} updated successfully`);
1119
+ return result;
1120
+ }
1121
+ catch (error) {
1122
+ logger_1.authLogger.error(`[AuthStore] Failed to update role ${roleId}:`, error);
1123
+ throw error;
1124
+ }
1125
+ },
1126
+ deleteRole: async (roleId) => {
1127
+ try {
1128
+ await get().apiCall(`/api/admin/roles/${roleId}`, {
1129
+ method: 'DELETE'
1130
+ });
1131
+ set({ rolesLastFetch: null });
1132
+ logger_1.authLogger.info(`[AuthStore] Role ${roleId} deleted successfully`);
1133
+ }
1134
+ catch (error) {
1135
+ logger_1.authLogger.error(`[AuthStore] Failed to delete role ${roleId}:`, error);
1136
+ throw error;
1137
+ }
1138
+ },
1139
+ assignUserToRole: async (userId, roleId) => {
1140
+ try {
1141
+ await get().apiCall('/api/admin/users/assign-role', {
1142
+ method: 'POST',
1143
+ body: JSON.stringify({ userId, roleId })
1144
+ });
1145
+ // Invalidate user assignments cache
1146
+ const state = get();
1147
+ set({
1148
+ userAssignments: {
1149
+ ...state.userAssignments,
1150
+ [userId]: undefined // Force refresh
1151
+ },
1152
+ usersLastFetch: null // Also refresh users list
1153
+ });
1154
+ logger_1.authLogger.info(`[AuthStore] User ${userId} assigned to role ${roleId}`);
1155
+ }
1156
+ catch (error) {
1157
+ logger_1.authLogger.error(`[AuthStore] Failed to assign user ${userId} to role ${roleId}:`, error);
1158
+ throw error;
1159
+ }
1160
+ },
1161
+ removeUserFromRole: async (userId, roleId) => {
1162
+ try {
1163
+ await get().apiCall('/api/admin/users/remove-role', {
1164
+ method: 'POST',
1165
+ body: JSON.stringify({ userId, roleId })
1166
+ });
1167
+ // Invalidate user assignments cache
1168
+ const state = get();
1169
+ set({
1170
+ userAssignments: {
1171
+ ...state.userAssignments,
1172
+ [userId]: undefined // Force refresh
1173
+ },
1174
+ usersLastFetch: null // Also refresh users list
1175
+ });
1176
+ logger_1.authLogger.info(`[AuthStore] User ${userId} removed from role ${roleId}`);
1177
+ }
1178
+ catch (error) {
1179
+ logger_1.authLogger.error(`[AuthStore] Failed to remove user ${userId} from role ${roleId}:`, error);
1180
+ throw error;
1181
+ }
1182
+ },
1183
+ assignUserToClient: async (userId, clientId) => {
1184
+ try {
1185
+ await get().apiCall('/api/admin/users/assign-client', {
1186
+ method: 'POST',
1187
+ body: JSON.stringify({ userId, clientId })
1188
+ });
1189
+ // Invalidate user client authorizations cache
1190
+ const state = get();
1191
+ set({
1192
+ clientAuthorizations: {
1193
+ ...state.clientAuthorizations,
1194
+ [userId]: undefined // Force refresh
1195
+ },
1196
+ usersLastFetch: null
1197
+ });
1198
+ logger_1.authLogger.info(`[AuthStore] User ${userId} assigned to client ${clientId}`);
1199
+ }
1200
+ catch (error) {
1201
+ logger_1.authLogger.error(`[AuthStore] Failed to assign user ${userId} to client ${clientId}:`, error);
1202
+ throw error;
1203
+ }
1204
+ },
1205
+ removeUserFromClient: async (userId, clientId) => {
1206
+ try {
1207
+ await get().apiCall('/api/admin/users/remove-client', {
1208
+ method: 'POST',
1209
+ body: JSON.stringify({ userId, clientId })
1210
+ });
1211
+ // Invalidate user client authorizations cache
1212
+ const state = get();
1213
+ set({
1214
+ clientAuthorizations: {
1215
+ ...state.clientAuthorizations,
1216
+ [userId]: undefined // Force refresh
1217
+ },
1218
+ usersLastFetch: null
1219
+ });
1220
+ logger_1.authLogger.info(`[AuthStore] User ${userId} removed from client ${clientId}`);
1221
+ }
1222
+ catch (error) {
1223
+ logger_1.authLogger.error(`[AuthStore] Failed to remove user ${userId} from client ${clientId}:`, error);
1224
+ throw error;
1225
+ }
1226
+ },
1227
+ // ===============================
1228
+ // UTILITY ACTIONS
1229
+ // ===============================
1230
+ hasRole: (role) => {
1231
+ const state = get();
1232
+ return state.user?.roles?.includes(role) || false;
1233
+ },
1234
+ hasAnyRole: (roles) => {
1235
+ const state = get();
1236
+ if (!state.user?.roles)
1237
+ return false;
1238
+ return roles.some(role => state.user.roles.includes(role));
1239
+ },
1240
+ hasAllRoles: (roles) => {
1241
+ const state = get();
1242
+ if (!state.user?.roles)
1243
+ return false;
1244
+ return roles.every(role => state.user.roles.includes(role));
1245
+ },
1246
+ isFullyAuthenticated: () => {
1247
+ const state = get();
1248
+ return state.isAuthenticated &&
1249
+ (!state.user?.requiresTwoFactor || state.user.twoFactorSessionVerified);
1250
+ },
1251
+ // ===============================
1252
+ // ERROR MANAGEMENT
1253
+ // ===============================
1254
+ setError: (error) => {
1255
+ set({ error });
1256
+ },
1257
+ clearError: () => {
1258
+ set({ error: null, tokenError: null });
1259
+ },
1260
+ // ===============================
1261
+ // ADMIN USER STATE MANAGEMENT ACTIONS
1262
+ // ===============================
1263
+ approveUser: async (userId, reason) => {
1264
+ try {
1265
+ await get().apiCall('/api/Admin/users/toggle-approval', {
1266
+ method: 'POST',
1267
+ body: JSON.stringify({
1268
+ user_id: parseInt(userId),
1269
+ is_approved: true,
1270
+ reason: reason || 'Administrative approval'
1271
+ })
1272
+ });
1273
+ logger_1.authLogger.info('[AuthStore] User approved:', { userId, reason });
1274
+ // The backend should broadcast the SignalR event, but we could also
1275
+ // optimistically update local state here if needed
1276
+ }
1277
+ catch (error) {
1278
+ logger_1.authLogger.error('[AuthStore] Failed to approve user:', error);
1279
+ throw error;
1280
+ }
1281
+ },
1282
+ disapproveUser: async (userId, reason) => {
1283
+ try {
1284
+ await get().apiCall('/api/Admin/users/toggle-approval', {
1285
+ method: 'POST',
1286
+ body: JSON.stringify({
1287
+ user_id: parseInt(userId),
1288
+ is_approved: false,
1289
+ reason: reason || 'Administrative disapproval'
1290
+ })
1291
+ });
1292
+ logger_1.authLogger.info('[AuthStore] User disapproved:', { userId, reason });
1293
+ }
1294
+ catch (error) {
1295
+ logger_1.authLogger.error('[AuthStore] Failed to disapprove user:', error);
1296
+ throw error;
1297
+ }
1298
+ },
1299
+ pauseUser: async (userId, reason) => {
1300
+ try {
1301
+ await get().apiCall('/api/Admin/users/pause', {
1302
+ method: 'POST',
1303
+ body: JSON.stringify({
1304
+ user_id: parseInt(userId),
1305
+ reason: reason || 'Administrative suspension'
1306
+ })
1307
+ });
1308
+ logger_1.authLogger.info('[AuthStore] User paused:', { userId, reason });
1309
+ }
1310
+ catch (error) {
1311
+ logger_1.authLogger.error('[AuthStore] Failed to pause user:', error);
1312
+ throw error;
1313
+ }
1314
+ },
1315
+ resumeUser: async (userId) => {
1316
+ try {
1317
+ await get().apiCall('/api/Admin/users/resume', {
1318
+ method: 'POST',
1319
+ body: JSON.stringify({
1320
+ user_id: parseInt(userId)
1321
+ })
1322
+ });
1323
+ logger_1.authLogger.info('[AuthStore] User resumed:', { userId });
1324
+ }
1325
+ catch (error) {
1326
+ logger_1.authLogger.error('[AuthStore] Failed to resume user:', error);
1327
+ throw error;
1328
+ }
1329
+ },
1330
+ haltUser: async (userId, reason) => {
1331
+ try {
1332
+ await get().apiCall('/api/Admin/users/halt', {
1333
+ method: 'POST',
1334
+ body: JSON.stringify({
1335
+ user_id: parseInt(userId),
1336
+ reason: reason || 'Security lockout'
1337
+ })
1338
+ });
1339
+ logger_1.authLogger.info('[AuthStore] User halted:', { userId, reason });
1340
+ }
1341
+ catch (error) {
1342
+ logger_1.authLogger.error('[AuthStore] Failed to halt user:', error);
1343
+ throw error;
1344
+ }
1345
+ },
1346
+ unlockUser: async (userId) => {
1347
+ try {
1348
+ await get().apiCall('/api/Admin/users/unlock', {
1349
+ method: 'POST',
1350
+ body: JSON.stringify({
1351
+ user_id: parseInt(userId)
1352
+ })
1353
+ });
1354
+ logger_1.authLogger.info('[AuthStore] User unlocked:', { userId });
1355
+ }
1356
+ catch (error) {
1357
+ logger_1.authLogger.error('[AuthStore] Failed to unlock user:', error);
1358
+ throw error;
1359
+ }
1360
+ },
1361
+ // ===============================
1362
+ // USER STATE UTILITY FUNCTIONS
1363
+ // ===============================
1364
+ canUserAccess: () => {
1365
+ const state = get();
1366
+ if (!state.user)
1367
+ return false;
1368
+ // State priority: LOCKOUT > SUSPENSION > APPROVAL
1369
+ if (state.user.lockoutEnabled) {
1370
+ // Check if lockout has expired
1371
+ if (state.user.lockoutEnd && new Date() > state.user.lockoutEnd) {
1372
+ return true; // Lockout expired
1373
+ }
1374
+ return false; // Still locked
1375
+ }
1376
+ if (state.user.isSuspended) {
1377
+ return false; // Suspended
1378
+ }
1379
+ return state.user.isApproved; // Must be approved
1380
+ },
1381
+ getUserStateDisplay: () => {
1382
+ const state = get();
1383
+ if (!state.user)
1384
+ return 'Unknown';
1385
+ // State priority: LOCKOUT > SUSPENSION > APPROVAL
1386
+ if (state.user.lockoutEnabled) {
1387
+ if (state.user.lockoutEnd && new Date() > state.user.lockoutEnd) {
1388
+ return 'Lockout Expired';
1389
+ }
1390
+ return 'Locked Out';
1391
+ }
1392
+ if (state.user.isSuspended) {
1393
+ return 'Suspended';
1394
+ }
1395
+ return state.user.isApproved ? 'Approved' : 'Unapproved';
1396
+ },
1397
+ isUserLocked: () => {
1398
+ const state = get();
1399
+ if (!state.user)
1400
+ return false;
1401
+ if (!state.user.lockoutEnabled)
1402
+ return false;
1403
+ // Check if lockout has expired
1404
+ if (state.user.lockoutEnd && new Date() > state.user.lockoutEnd) {
1405
+ return false; // Lockout expired
1406
+ }
1407
+ return true; // Still locked
1408
+ },
1409
+ // ===============================
1410
+ // REAL-TIME SIGNALR MANAGEMENT
1411
+ // ===============================
1412
+ initializeSignalR: async () => {
1413
+ const state = get();
1414
+ // Don't initialize if already connected or not authenticated
1415
+ if (state.signalrConnection || !state.isAuthenticated || !state.accessToken) {
1416
+ logger_1.authLogger.debug('[AuthStore] Skipping SignalR - already connected or not authenticated');
1417
+ return;
1418
+ }
1419
+ // Check if user has admin role for ActivityHub access
1420
+ const hasAdminRole = state.user?.roles?.includes('payez_admin') || false;
1421
+ if (!hasAdminRole) {
1422
+ logger_1.authLogger.info('[AuthStore] Skipping SignalR - user lacks admin role for ActivityHub');
1423
+ return;
1424
+ }
1425
+ logger_1.authLogger.info('[AuthStore] Initializing SignalR connection');
1426
+ try {
1427
+ // Use the existing signalRActivityService instead of creating a duplicate connection
1428
+ // to the same health hub - this avoids connection errors flooding the console
1429
+ // Start the health service if not already running (with timeout)
1430
+ const idpBaseUrl = process.env.IDP_URL;
1431
+ if (!idpBaseUrl) {
1432
+ throw new Error('[IDP_URL] FATAL: IDP_URL environment variable is REQUIRED.');
1433
+ }
1434
+ const startPromise = signalrActivityService_1.signalRActivityService.start(idpBaseUrl);
1435
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('SignalR start timeout')), 5000));
1436
+ await Promise.race([startPromise, timeoutPromise]);
1437
+ // Subscribe to health status changes from the dedicated health service
1438
+ const unsubscribe = signalrActivityService_1.signalRActivityService.subscribe((status) => {
1439
+ // Map health service status to our connection state
1440
+ const connectionState = status.isHealthy ?
1441
+ signalr_1.HubConnectionState.Connected :
1442
+ status.message.includes('reconnecting') ?
1443
+ signalr_1.HubConnectionState.Reconnecting :
1444
+ signalr_1.HubConnectionState.Disconnected;
1445
+ logger_1.authLogger.info('[AuthStore] Health status update:', {
1446
+ isHealthy: status.isHealthy,
1447
+ message: status.message,
1448
+ connectionState
1449
+ });
1450
+ set({
1451
+ signalrConnectionState: connectionState,
1452
+ isConnectedToSignalR: status.isHealthy
1453
+ });
1454
+ });
1455
+ // Store the unsubscribe function for cleanup
1456
+ set({
1457
+ signalrConnection: {
1458
+ // Minimal connection-like interface that does nothing on stop()
1459
+ // This allows existing code to still call connection.stop() without errors
1460
+ stop: async () => {
1461
+ unsubscribe();
1462
+ logger_1.authLogger.info('[AuthStore] Unsubscribed from health service');
1463
+ }
1464
+ },
1465
+ signalrConnectionState: signalrActivityService_1.signalRActivityService.getCurrentStatus().isHealthy ?
1466
+ signalr_1.HubConnectionState.Connected : signalr_1.HubConnectionState.Disconnected,
1467
+ isConnectedToSignalR: signalrActivityService_1.signalRActivityService.getCurrentStatus().isHealthy
1468
+ });
1469
+ logger_1.authLogger.info('[AuthStore] SignalR connection established via health service');
1470
+ }
1471
+ catch (error) {
1472
+ // Don't let SignalR errors propagate up and potentially break login flow
1473
+ const errorMessage = error instanceof Error ? error.message : String(error);
1474
+ // Only log as error if it's not a timeout or expected connection failure
1475
+ if (errorMessage.includes('timeout') || errorMessage.includes('unavailable')) {
1476
+ logger_1.authLogger.info('[AuthStore] SignalR connection not available (expected in some environments):', errorMessage);
1477
+ }
1478
+ else {
1479
+ logger_1.authLogger.error('[AuthStore] SignalR connection failed:', error);
1480
+ }
1481
+ set({
1482
+ signalrConnectionState: signalr_1.HubConnectionState.Disconnected,
1483
+ isConnectedToSignalR: false
1484
+ });
1485
+ }
1486
+ },
1487
+ disconnectSignalR: async () => {
1488
+ const state = get();
1489
+ if (state.signalrConnection) {
1490
+ logger_1.authLogger.info('[AuthStore] Disconnecting SignalR');
1491
+ try {
1492
+ await state.signalrConnection.stop();
1493
+ }
1494
+ catch (error) {
1495
+ logger_1.authLogger.error('[AuthStore] Error disconnecting SignalR:', error);
1496
+ }
1497
+ set({
1498
+ signalrConnection: null,
1499
+ signalrConnectionState: signalr_1.HubConnectionState.Disconnected,
1500
+ isConnectedToSignalR: false
1501
+ });
1502
+ }
1503
+ },
1504
+ handleUserStateChanged: (data) => {
1505
+ // This method was designed for UserStateChanged events that don't exist in current backend
1506
+ // Keeping for future reference or if we implement proper user state change events
1507
+ const state = get();
1508
+ // If this event is about the current user, update their state
1509
+ if (data.userId === state.user?.id && state.session) {
1510
+ logger_1.authLogger.info('[AuthStore] Updating current user state from SignalR:', data);
1511
+ // ... user state update logic would go here
1512
+ }
1513
+ },
1514
+ }), {
1515
+ name: 'auth-store',
1516
+ }));
1517
+ // ===============================
1518
+ // STORE INITIALIZATION HELPER
1519
+ // ===============================
1520
+ /**
1521
+ * Initialize the auth store with a session (typically called in layout)
1522
+ */
1523
+ const initializeAuthStore = (session) => {
1524
+ const store = exports.useAuthStore.getState();
1525
+ if (!store.isInitialized) {
1526
+ logger_1.authLogger.debug('[AuthStore] Initializing with session:', { hasSession: !!session });
1527
+ store.setSession(session);
1528
+ }
1529
+ };
1530
+ exports.initializeAuthStore = initializeAuthStore;
1531
+ exports.default = exports.useAuthStore;