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