@payez/next-mvp 4.8.1 → 4.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/POST-INSTALL.md +408 -408
- package/README.md +859 -859
- package/dist/api-handlers/admin/audit.js +13 -21
- package/dist/api-handlers/admin/sessions.js +59 -45
- package/dist/api-handlers/admin/stats.js +1 -1
- package/dist/api-handlers/admin/users.js +15 -23
- package/dist/api-handlers/admin/vibe-data.d.ts +5 -0
- package/dist/api-handlers/admin/vibe-data.js +5 -0
- package/dist/components/account/MobileNavDrawer.js +18 -18
- package/dist/components/account/UserAvatarMenu.js +1 -1
- package/dist/components/admin/AlertSettingsTab.js +40 -40
- package/dist/components/admin/LoggingSettingsTab.js +28 -28
- package/dist/lib/session-store.js +21 -21
- package/dist/lib/startup-init.js +19 -19
- package/dist/pages/login/page.js +28 -28
- package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
- package/dist/vibe/client.d.ts +1 -1
- package/dist/vibe/client.js +2 -1
- package/dist/vibe/filters.d.ts +36 -0
- package/dist/vibe/filters.js +47 -0
- package/dist/vibe/generic.js +2 -1
- package/dist/vibe/sessions.js +28 -3
- package/package.json +2 -2
- package/scripts/check-publish-eol.mjs +33 -15
- package/src/api/auth-handler.ts +550 -550
- package/src/api/index.ts +17 -17
- package/src/api-handlers/account/change-password.ts +170 -170
- package/src/api-handlers/account/masked-info.ts +46 -46
- package/src/api-handlers/account/profile.ts +99 -99
- package/src/api-handlers/account/recovery/forward.ts +117 -117
- package/src/api-handlers/account/recovery/initiate.ts +59 -59
- package/src/api-handlers/account/recovery/send-code.ts +21 -21
- package/src/api-handlers/account/recovery/verify-code.ts +20 -20
- package/src/api-handlers/account/reset-password.ts +87 -87
- package/src/api-handlers/account/send-code.ts +70 -70
- package/src/api-handlers/account/update-phone.ts +84 -84
- package/src/api-handlers/account/validate-password.ts +139 -139
- package/src/api-handlers/account/verify-email.ts +134 -134
- package/src/api-handlers/account/verify-sms.ts +134 -134
- package/src/api-handlers/admin/analytics.ts +452 -452
- package/src/api-handlers/admin/audit.ts +223 -232
- package/src/api-handlers/admin/index.ts +63 -63
- package/src/api-handlers/admin/redis-sessions.ts +259 -259
- package/src/api-handlers/admin/sessions.ts +341 -327
- package/src/api-handlers/admin/site-logs.ts +388 -388
- package/src/api-handlers/admin/stats.ts +260 -260
- package/src/api-handlers/admin/users.ts +240 -251
- package/src/api-handlers/admin/vibe-data.ts +338 -333
- package/src/api-handlers/anon/preferences.ts +123 -123
- package/src/api-handlers/auth/credential-login-email-code.ts +92 -92
- package/src/api-handlers/auth/credential-login.ts +948 -948
- package/src/api-handlers/auth/jwks.ts +31 -31
- package/src/api-handlers/auth/login.ts +252 -252
- package/src/api-handlers/auth/passkey-signin.ts +116 -116
- package/src/api-handlers/auth/refresh.ts +829 -829
- package/src/api-handlers/auth/signout.ts +201 -201
- package/src/api-handlers/auth/status.ts +23 -23
- package/src/api-handlers/auth/update-session.ts +144 -144
- package/src/api-handlers/auth/validate.ts +45 -45
- package/src/api-handlers/auth/verify-code.ts +116 -116
- package/src/api-handlers/mfa/backup-code-verify.ts +170 -170
- package/src/api-handlers/mfa/backup-codes.ts +42 -42
- package/src/api-handlers/mfa/methods.ts +47 -47
- package/src/api-handlers/mfa/totp-confirm.ts +43 -43
- package/src/api-handlers/mfa/totp-enroll.ts +43 -43
- package/src/api-handlers/mfa/totp-verify.ts +172 -172
- package/src/api-handlers/oauth/resume.ts +182 -182
- package/src/api-handlers/onboarding/send-email-code.ts +18 -18
- package/src/api-handlers/onboarding/set-password.ts +21 -21
- package/src/api-handlers/onboarding/verify-code.ts +20 -20
- package/src/api-handlers/onboarding/verify-welcome-email.ts +17 -17
- package/src/api-handlers/passkeys/list.ts +32 -32
- package/src/api-handlers/passkeys/manage.ts +55 -55
- package/src/api-handlers/passkeys/register-options.ts +43 -43
- package/src/api-handlers/passkeys/register-verify.ts +42 -42
- package/src/api-handlers/session/refresh-viability.ts +56 -56
- package/src/api-handlers/session/viability.ts +238 -238
- package/src/api-handlers/test/force-expire.ts +167 -167
- package/src/auth/auth-decision.ts +292 -292
- package/src/auth/better-auth.ts +1688 -1688
- package/src/auth/idp-oauth.ts +310 -310
- package/src/auth/password-reset-bridge.ts +98 -98
- package/src/auth/revocation-fields.ts +16 -16
- package/src/auth/route-config.ts +236 -236
- package/src/auth/safe-callback.ts +199 -199
- package/src/auth/session-cookie-clear.ts +30 -30
- package/src/auth/session-reset-link.ts +159 -159
- package/src/auth/session-response-sanitizer.ts +151 -151
- package/src/auth/types/auth-types.ts +555 -555
- package/src/auth/types/index.ts +7 -7
- package/src/auth/unauthenticated-routes.ts +2 -2
- package/src/auth/utils/idp-client.ts +364 -364
- package/src/auth/utils/index.ts +6 -6
- package/src/auth/utils/token-utils.ts +243 -243
- package/src/client/AuthContext.tsx +192 -192
- package/src/client/better-auth-client.ts +70 -70
- package/src/client/fetch-with-auth.ts +47 -47
- package/src/client/fetchWithSession.ts +21 -21
- package/src/client/index.ts +13 -13
- package/src/client/useAnonSession.ts +131 -131
- package/src/components/SessionSync.tsx +138 -138
- package/src/components/SignalRHealthCheck.tsx +131 -131
- package/src/components/account/MobileNavDrawer.tsx +305 -305
- package/src/components/account/UserAvatarMenu.tsx +277 -277
- package/src/components/account/index.ts +13 -13
- package/src/components/admin/AlertSettingsTab.tsx +728 -728
- package/src/components/admin/AnalyticsTab.tsx +703 -703
- package/src/components/admin/DataBrowserTab.tsx +508 -508
- package/src/components/admin/LoggingSettingsTab.tsx +665 -665
- package/src/components/admin/SessionsTab.tsx +421 -421
- package/src/components/admin/StatsTab.tsx +379 -379
- package/src/components/admin/VibeAdminContext.tsx +87 -87
- package/src/components/admin/VibeAdminLayout.tsx +187 -187
- package/src/components/admin/index.ts +59 -59
- package/src/components/auth/CredentialSignInForm.tsx +240 -240
- package/src/components/auth/FederatedAuthSection.tsx +129 -129
- package/src/components/auth/ModeAwareLoginPage.tsx +146 -146
- package/src/components/auth/ModeAwareSignupPage.tsx +200 -200
- package/src/components/auth/PasswordResetBridge.tsx +30 -30
- package/src/components/auth/TraditionalAuthSection.tsx +103 -103
- package/src/components/recovery/CompleteStep.tsx +36 -36
- package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
- package/src/components/recovery/RequestedStep.tsx +30 -30
- package/src/components/recovery/SelectMethodStep.tsx +73 -73
- package/src/components/recovery/SetPasswordStep.tsx +118 -118
- package/src/components/recovery/VerifyCodeStep.tsx +90 -90
- package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -160
- package/src/components/reserved/ReservedStatusBox.tsx +118 -118
- package/src/components/ui/BetaBadge.tsx +58 -58
- package/src/components/ui/Footer.tsx +93 -93
- package/src/config/env.ts +55 -55
- package/src/config/logger.ts +62 -62
- package/src/config/logging-config.ts +82 -82
- package/src/config/unauthenticated-routes.ts +19 -19
- package/src/config/vibe-log-transport.ts +264 -264
- package/src/edge/internal-api-url.ts +65 -65
- package/src/edge/middleware.ts +42 -42
- package/src/hooks/useAuth.ts +117 -117
- package/src/hooks/useAuthSettings.ts +109 -109
- package/src/hooks/useAvailableProviders.ts +143 -143
- package/src/hooks/usePasswordValidation.ts +127 -127
- package/src/hooks/useProfile.ts +82 -82
- package/src/hooks/usePublicAuthSettings.ts +171 -171
- package/src/hooks/useSessionExpiration.ts +101 -101
- package/src/hooks/useViabilitySession.ts +337 -337
- package/src/index.ts +64 -64
- package/src/lib/anon-session.ts +213 -213
- package/src/lib/api-handler.ts +643 -643
- package/src/lib/app-slug.ts +228 -228
- package/src/lib/authorize-resume.ts +172 -172
- package/src/lib/bearer-fetch.ts +31 -31
- package/src/lib/client-key.ts +88 -88
- package/src/lib/code-step-copy.ts +83 -83
- package/src/lib/credential-sign-in-client.ts +101 -101
- package/src/lib/demo-mode.ts +18 -18
- package/src/lib/device-id.ts +67 -67
- package/src/lib/ensure-fresh-access-token.ts +509 -509
- package/src/lib/geolocation.ts +265 -265
- package/src/lib/idp-client-config.ts +1005 -1005
- package/src/lib/idp-fetch.ts +169 -169
- package/src/lib/idp-url-guard.ts +37 -37
- package/src/lib/internal-api.ts +171 -171
- package/src/lib/jwt-decode-client.ts +45 -45
- package/src/lib/jwt-decode.ts +82 -82
- package/src/lib/lock-window.ts +49 -49
- package/src/lib/login-error-message.ts +41 -41
- package/src/lib/never-in-response.ts +64 -64
- package/src/lib/nextauth-secret.ts +168 -168
- package/src/lib/onboarding-proxy.ts +57 -57
- package/src/lib/password-rules.ts +50 -50
- package/src/lib/rate-limit-service.ts +9 -9
- package/src/lib/redis.ts +105 -105
- package/src/lib/refresh-token-validator.ts +64 -64
- package/src/lib/roles.ts +177 -177
- package/src/lib/safe-error-response.ts +149 -149
- package/src/lib/secret-validation.ts +8 -8
- package/src/lib/session-cookie-shape.ts +185 -185
- package/src/lib/session-store.ts +1092 -1092
- package/src/lib/session.ts +34 -34
- package/src/lib/single-flight.ts +64 -64
- package/src/lib/site-logger.ts +245 -245
- package/src/lib/standardized-client-api.ts +901 -901
- package/src/lib/startup-init.ts +276 -276
- package/src/lib/test-aware-get-token.ts +41 -41
- package/src/lib/token-expiry.ts +40 -40
- package/src/lib/token-lifecycle.ts +546 -546
- package/src/lib/token-log-id.ts +69 -69
- package/src/lib/twofa-rejection.ts +91 -91
- package/src/lib/types/api-responses.ts +336 -336
- package/src/lib/user-agent-parser.ts +252 -252
- package/src/logging/api/admin-analytics.ts +51 -51
- package/src/logging/api/audit-log.ts +53 -53
- package/src/logging/components/AdminAnalyticsLayout.tsx +49 -49
- package/src/logging/components/AuditLogViewer.tsx +125 -125
- package/src/logging/components/ErrorMetricsCard.tsx +98 -98
- package/src/logging/components/HealthMetricsCard.tsx +70 -70
- package/src/logging/hooks/useAdminAnalytics.ts +22 -22
- package/src/logging/hooks/useAuditLog.ts +24 -24
- package/src/logging/hooks/useErrorMetrics.ts +40 -40
- package/src/logging/hooks/useHealthMetrics.ts +44 -44
- package/src/logging/index.ts +18 -18
- package/src/logging/types/analytics.ts +81 -81
- package/src/logging/types/audit.ts +31 -31
- package/src/logging/types/index.ts +3 -3
- package/src/middleware/create-middleware.ts +1141 -1141
- package/src/middleware/rbac-check.ts +279 -279
- package/src/middleware/twofa-presets.ts +224 -224
- package/src/models/DecodedAccessToken.ts +16 -16
- package/src/models/SessionModel.ts +331 -331
- package/src/pages/admin-login/page.tsx +111 -111
- package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -527
- package/src/pages/admin-page-permissions/index.ts +7 -7
- package/src/pages/admin-roles/RolesAdminPage.tsx +533 -533
- package/src/pages/admin-roles/index.ts +9 -9
- package/src/pages/admin-roles/modals.tsx +469 -469
- package/src/pages/client-admin/ClientSiteAdminPage.tsx +382 -382
- package/src/pages/client-admin/index.ts +33 -33
- package/src/pages/coming-soon/page.tsx +83 -83
- package/src/pages/login/page.tsx +338 -338
- package/src/pages/onboarding/index.ts +11 -11
- package/src/pages/onboarding/page.tsx +212 -212
- package/src/pages/profile/EnhancedProfilePage.tsx +514 -514
- package/src/pages/profile/index.ts +9 -9
- package/src/pages/profile/page.tsx +166 -166
- package/src/pages/recovery/page.tsx +267 -267
- package/src/pages/roles/MyRolesPage.tsx +211 -211
- package/src/pages/roles/components.tsx +294 -294
- package/src/pages/roles/index.ts +17 -17
- package/src/pages/security/EnhancedSecurityPage.tsx +660 -660
- package/src/pages/security/index.ts +12 -12
- package/src/pages/security/page.tsx +507 -507
- package/src/pages/service-unavailable/page.tsx +63 -63
- package/src/pages/settings/EnhancedSettingsPage.tsx +642 -642
- package/src/pages/settings/index.ts +9 -9
- package/src/pages/settings/page.tsx +47 -47
- package/src/pages/showcase/ShowcasePage.tsx +532 -532
- package/src/pages/showcase/index.ts +13 -13
- package/src/pages/test-env/EmergencyLogoutPage.tsx +192 -192
- package/src/pages/test-env/JwtInspectPage.tsx +449 -449
- package/src/pages/test-env/RefreshTokenPage.tsx +178 -178
- package/src/pages/test-env/TestEnvPage.tsx +130 -130
- package/src/pages/test-env/guarded.tsx +81 -81
- package/src/pages/test-env/index.ts +77 -77
- package/src/pages/test-env/redact-credentials.ts +96 -96
- package/src/pages/verify-code/page.tsx +746 -746
- package/src/routes/account/index.ts +31 -31
- package/src/routes/account/masked-info.ts +36 -36
- package/src/routes/account/send-code.ts +39 -39
- package/src/routes/account/update-phone.ts +13 -13
- package/src/routes/account/verify-email.ts +40 -40
- package/src/routes/account/verify-sms.ts +40 -40
- package/src/routes/auth/credential-login-email-code.ts +6 -6
- package/src/routes/auth/credential-login.ts +6 -6
- package/src/routes/auth/index.ts +22 -22
- package/src/routes/auth/logout.ts +111 -111
- package/src/routes/auth/nextauth.ts +45 -45
- package/src/routes/auth/passkey-signin.ts +13 -13
- package/src/routes/auth/refresh.ts +77 -77
- package/src/routes/auth/session-reset.ts +37 -37
- package/src/routes/auth/session.ts +261 -261
- package/src/routes/auth/settings.ts +181 -181
- package/src/routes/auth/viability.ts +519 -519
- package/src/routes/index.ts +17 -17
- package/src/routes/mfa/backup-code-verify.ts +1 -1
- package/src/routes/mfa/backup-codes.ts +10 -10
- package/src/routes/mfa/methods.ts +22 -22
- package/src/routes/mfa/totp-confirm.ts +10 -10
- package/src/routes/mfa/totp-enroll.ts +10 -10
- package/src/routes/mfa/totp-verify.ts +14 -14
- package/src/routes/onboarding/index.ts +15 -15
- package/src/routes/onboarding/send-email-code.ts +2 -2
- package/src/routes/onboarding/set-password.ts +2 -2
- package/src/routes/onboarding/verify-code.ts +2 -2
- package/src/routes/onboarding/verify-welcome-email.ts +2 -2
- package/src/routes/passkeys/list.ts +10 -10
- package/src/routes/passkeys/manage.ts +23 -23
- package/src/routes/passkeys/register-options.ts +10 -10
- package/src/routes/passkeys/register-verify.ts +10 -10
- package/src/routes/session/index.ts +7 -7
- package/src/routes/session/refresh-viability.ts +17 -17
- package/src/server/auth-guard.ts +203 -203
- package/src/server/auth.ts +486 -486
- package/src/server/decode-session.ts +309 -309
- package/src/server/slim-middleware.ts +117 -117
- package/src/server/verified-session-token.ts +103 -103
- package/src/server/with-auth.ts +93 -93
- package/src/services/signalrActivityService.ts +258 -258
- package/src/stores/authStore.ts +1900 -1900
- package/src/templates/instrumentation.ts +60 -60
- package/src/theme/ThemeProvider.tsx +39 -39
- package/src/theme/default.ts +33 -33
- package/src/theme/index.ts +31 -31
- package/src/theme/types.ts +69 -69
- package/src/theme/useTheme.ts +57 -57
- package/src/theme/utils.ts +40 -40
- package/src/types/api.ts +12 -12
- package/src/types/auth.d.ts +23 -23
- package/src/types/auth.ts +48 -48
- package/src/types/logging.ts +11 -11
- package/src/types/recovery.ts +54 -54
- package/src/utils/api.ts +401 -401
- package/src/utils/circuitBreaker.ts +40 -40
- package/src/utils/error-message.ts +108 -108
- package/src/utils/layout/reservedSpace.ts +124 -124
- package/src/utils/logout.ts +30 -30
- package/src/vibe/client.ts +878 -877
- package/src/vibe/enterprise-auth.ts +208 -208
- package/src/vibe/errors.ts +209 -209
- package/src/vibe/filters.ts +52 -0
- package/src/vibe/generic.ts +340 -339
- package/src/vibe/hooks/index.ts +367 -367
- package/src/vibe/index.ts +133 -133
- package/src/vibe/sessions.ts +925 -899
- package/src/vibe/types.ts +577 -577
|
@@ -1,327 +1,341 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Admin Sessions API Handler
|
|
3
|
-
*
|
|
4
|
-
* Provides admin-level access to login sessions using service account credentials.
|
|
5
|
-
* Used by SessionsTab component.
|
|
6
|
-
*
|
|
7
|
-
* @version 1.0
|
|
8
|
-
* @requires Admin role (vibe_app_admin or payez_admin)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
12
|
-
import { getSession } from '../../server/auth';
|
|
13
|
-
import { getStartupIDPConfig } from '../../lib/startup-init';
|
|
14
|
-
import { resolveClientKey } from '../../lib/client-key';
|
|
15
|
-
import { ADMIN_ROLES, hasAnyRole } from '../../lib/roles';
|
|
16
|
-
import { randomUUID } from 'crypto';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
.
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
'
|
|
94
|
-
'X-Vibe-
|
|
95
|
-
'X-Vibe-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.
|
|
132
|
-
.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
*
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
],
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Admin Sessions API Handler
|
|
3
|
+
*
|
|
4
|
+
* Provides admin-level access to login sessions using service account credentials.
|
|
5
|
+
* Used by SessionsTab component.
|
|
6
|
+
*
|
|
7
|
+
* @version 1.0
|
|
8
|
+
* @requires Admin role (vibe_app_admin or payez_admin)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
12
|
+
import { getSession } from '../../server/auth';
|
|
13
|
+
import { getStartupIDPConfig } from '../../lib/startup-init';
|
|
14
|
+
import { resolveClientKey } from '../../lib/client-key';
|
|
15
|
+
import { ADMIN_ROLES, hasAnyRole } from '../../lib/roles';
|
|
16
|
+
import { randomUUID } from 'crypto';
|
|
17
|
+
import { allOf, contains, eq, orderByMap } from '../../vibe/filters';
|
|
18
|
+
|
|
19
|
+
interface VibeRequestOptions {
|
|
20
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
21
|
+
body?: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if the current user has admin role
|
|
26
|
+
*/
|
|
27
|
+
async function checkAdminRole(request: NextRequest): Promise<{ isAdmin: boolean; userId?: number; error?: NextResponse }> {
|
|
28
|
+
const session = await getSession(request) as any;
|
|
29
|
+
|
|
30
|
+
if (!session?.user) {
|
|
31
|
+
return {
|
|
32
|
+
isAdmin: false,
|
|
33
|
+
error: NextResponse.json(
|
|
34
|
+
{ success: false, error: 'Please sign in' },
|
|
35
|
+
{ status: 401 }
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const userRoles = (session.user?.roles as string[]) || [];
|
|
41
|
+
const hasAdminRole = ADMIN_ROLES.some(role => userRoles.includes(role));
|
|
42
|
+
|
|
43
|
+
if (!hasAdminRole) {
|
|
44
|
+
return {
|
|
45
|
+
isAdmin: false,
|
|
46
|
+
error: NextResponse.json(
|
|
47
|
+
{ success: false, error: 'Admin access required' },
|
|
48
|
+
{ status: 403 }
|
|
49
|
+
),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { isAdmin: true, userId: session.user?.id };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Make a service account request to Vibe (admin mode)
|
|
58
|
+
*/
|
|
59
|
+
async function vibeServiceRequest<T = unknown>(
|
|
60
|
+
endpoint: string,
|
|
61
|
+
options: VibeRequestOptions
|
|
62
|
+
): Promise<{ ok: boolean; status: number; data: T | null; error?: string; correlationId?: string }> {
|
|
63
|
+
// PAY-1429: `error` can reach the HTTP caller, so it never carries upstream Vibe text or
|
|
64
|
+
// exception text. Failures return a fixed message + correlationId; the same id is on the
|
|
65
|
+
// server log line, which carries status + bodyLength only (PAY-1424).
|
|
66
|
+
const idpUrl = process.env.NEXT_PUBLIC_IDP_URL || process.env.IDP_URL;
|
|
67
|
+
const clientId = process.env.VIBE_CLIENT_ID;
|
|
68
|
+
const signingKey = process.env.VIBE_HMAC_KEY;
|
|
69
|
+
|
|
70
|
+
if (!idpUrl || !clientId || !signingKey) {
|
|
71
|
+
return { ok: false, status: 500, data: null, error: 'Vibe not configured' };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
75
|
+
const stringToSign = `${timestamp}|${options.method}|${endpoint}`;
|
|
76
|
+
|
|
77
|
+
const crypto = await import('crypto');
|
|
78
|
+
const signature = crypto
|
|
79
|
+
.createHmac('sha256', Buffer.from(signingKey, 'base64'))
|
|
80
|
+
.update(stringToSign)
|
|
81
|
+
.digest('base64');
|
|
82
|
+
|
|
83
|
+
const proxyUrl = `${idpUrl}/api/vibe/proxy`;
|
|
84
|
+
|
|
85
|
+
// Get the client slug from startup config for multi-client admin support
|
|
86
|
+
const idpConfig = getStartupIDPConfig();
|
|
87
|
+
const idpClientId = resolveClientKey(idpConfig, 'admin/sessions').key;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch(proxyUrl, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: {
|
|
93
|
+
'Content-Type': 'application/json',
|
|
94
|
+
'X-Vibe-Client-Id': clientId,
|
|
95
|
+
'X-Vibe-Timestamp': String(timestamp),
|
|
96
|
+
'X-Vibe-Signature': signature,
|
|
97
|
+
...(idpClientId && { 'X-Client-Id': idpClientId }),
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
endpoint,
|
|
101
|
+
method: options.method,
|
|
102
|
+
data: options.body ?? null,
|
|
103
|
+
}),
|
|
104
|
+
cache: 'no-store',
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (res.status === 204) {
|
|
108
|
+
return { ok: true, status: 204, data: null };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
const errorText = await res.text();
|
|
113
|
+
const correlationId = randomUUID();
|
|
114
|
+
console.error(`[admin/sessions] Vibe request failed: ${options.method} ${endpoint} - ${res.status}`, { correlationId, bodyLength: errorText.length });
|
|
115
|
+
return { ok: false, status: res.status, data: null, error: `Vibe request failed (HTTP ${res.status})`, correlationId };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const body = await res.json();
|
|
119
|
+
return { ok: true, status: res.status, data: body };
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const correlationId = randomUUID();
|
|
122
|
+
console.error(`[admin/sessions] Vibe request exception: ${options.method} ${endpoint}`, { correlationId, error });
|
|
123
|
+
return { ok: false, status: 0, data: null, error: 'Vibe request could not be completed', correlationId };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Country code to flag emoji mapping
|
|
128
|
+
function getCountryFlag(countryCode: string): string {
|
|
129
|
+
if (!countryCode || countryCode.length !== 2) return '';
|
|
130
|
+
const codePoints = countryCode
|
|
131
|
+
.toUpperCase()
|
|
132
|
+
.split('')
|
|
133
|
+
.map(char => 127397 + char.charCodeAt(0));
|
|
134
|
+
return String.fromCodePoint(...codePoints);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface AdminSessionsHandlerConfig {
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* GET /api/admin/sessions - List sessions
|
|
142
|
+
* POST /api/admin/sessions - Stats, revoke actions
|
|
143
|
+
*/
|
|
144
|
+
export function createSessionsHandler(config: AdminSessionsHandlerConfig) {
|
|
145
|
+
return {
|
|
146
|
+
async GET(request: NextRequest) {
|
|
147
|
+
const adminCheck = await checkAdminRole(request);
|
|
148
|
+
if (adminCheck.error) return adminCheck.error;
|
|
149
|
+
|
|
150
|
+
const { searchParams } = new URL(request.url);
|
|
151
|
+
const status = searchParams.get('status');
|
|
152
|
+
const email = searchParams.get('email');
|
|
153
|
+
|
|
154
|
+
// Build query for login_sessions table
|
|
155
|
+
const queryBody: any = {
|
|
156
|
+
page: 1,
|
|
157
|
+
pageSize: 100,
|
|
158
|
+
// PAY-1810: orderBy is a { field: dir } map; a bare string is rejected or ignored by Core.
|
|
159
|
+
orderBy: orderByMap('created_at', 'desc'),
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// PAY-1810: Core filter forms only (vibe/filters.ts): compound uses `filters`, and substring is `contains` (`like` is not a Core
|
|
163
|
+
// operator and a flat unknown operator is DROPPED, which returned every session).
|
|
164
|
+
const filter = allOf([
|
|
165
|
+
status && status !== 'all' ? eq('status', status) : null,
|
|
166
|
+
email ? contains('email', email) : null,
|
|
167
|
+
]);
|
|
168
|
+
if (filter) queryBody.filter = filter;
|
|
169
|
+
|
|
170
|
+
const result = await vibeServiceRequest<any>(
|
|
171
|
+
'/v1/collections/vibe_app/tables/login_sessions/query',
|
|
172
|
+
{ method: 'POST', body: queryBody }
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (!result.ok) {
|
|
176
|
+
return NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Extract sessions from response
|
|
180
|
+
const rawSessions = result.data?.data || result.data?.documents || [];
|
|
181
|
+
const sessions = rawSessions.map((s: any) => ({
|
|
182
|
+
id: s.id || s.document_id,
|
|
183
|
+
idp_user_id: s.idp_user_id || s.user_id,
|
|
184
|
+
email: s.email || '',
|
|
185
|
+
name: s.name || s.display_name || '',
|
|
186
|
+
status: s.status || 'active',
|
|
187
|
+
ip_address: s.ip_address || s.ip,
|
|
188
|
+
city: s.city,
|
|
189
|
+
region: s.region,
|
|
190
|
+
country_code: s.country_code || s.country,
|
|
191
|
+
device_type: s.device_type || s.device,
|
|
192
|
+
browser: s.browser,
|
|
193
|
+
os: s.os,
|
|
194
|
+
created_at: s.created_at,
|
|
195
|
+
last_activity: s.last_activity || s.updated_at,
|
|
196
|
+
revoked_at: s.revoked_at,
|
|
197
|
+
revoked_by: s.revoked_by,
|
|
198
|
+
country_flag: getCountryFlag(s.country_code || s.country || ''),
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
return NextResponse.json({ sessions });
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
async POST(request: NextRequest) {
|
|
205
|
+
const adminCheck = await checkAdminRole(request);
|
|
206
|
+
if (adminCheck.error) return adminCheck.error;
|
|
207
|
+
|
|
208
|
+
const body = await request.json();
|
|
209
|
+
const { action, sessionId, userId } = body;
|
|
210
|
+
|
|
211
|
+
if (action === 'stats') {
|
|
212
|
+
// Get all sessions for stats calculation
|
|
213
|
+
const result = await vibeServiceRequest<any>(
|
|
214
|
+
'/v1/collections/vibe_app/tables/login_sessions/query',
|
|
215
|
+
{ method: 'POST', body: { page: 1, pageSize: 1000 } }
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (!result.ok) {
|
|
219
|
+
return NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const sessions = result.data?.data || result.data?.documents || [];
|
|
223
|
+
|
|
224
|
+
// Calculate stats
|
|
225
|
+
const now = new Date();
|
|
226
|
+
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
227
|
+
|
|
228
|
+
const stats = {
|
|
229
|
+
totalActive: sessions.filter((s: any) => s.status === 'active').length,
|
|
230
|
+
totalRevoked: sessions.filter((s: any) => s.status === 'revoked').length,
|
|
231
|
+
uniqueUsers: new Set(sessions.map((s: any) => s.idp_user_id || s.user_id)).size,
|
|
232
|
+
recentLogins: sessions.filter((s: any) => new Date(s.created_at) > oneDayAgo).length,
|
|
233
|
+
byCountryWithFlags: {} as Record<string, { count: number; flag: string }>,
|
|
234
|
+
byDevice: {} as Record<string, number>,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Count by country
|
|
238
|
+
sessions.forEach((s: any) => {
|
|
239
|
+
const country = s.country_code || s.country || 'Unknown';
|
|
240
|
+
if (!stats.byCountryWithFlags[country]) {
|
|
241
|
+
stats.byCountryWithFlags[country] = { count: 0, flag: getCountryFlag(country) };
|
|
242
|
+
}
|
|
243
|
+
stats.byCountryWithFlags[country].count++;
|
|
244
|
+
|
|
245
|
+
const device = s.device_type || s.device || 'Unknown';
|
|
246
|
+
stats.byDevice[device] = (stats.byDevice[device] || 0) + 1;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return NextResponse.json({ stats });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (action === 'revoke' && sessionId) {
|
|
253
|
+
// Revoke single session
|
|
254
|
+
const result = await vibeServiceRequest<any>(
|
|
255
|
+
`/v1/collections/vibe_app/tables/login_sessions/${sessionId}`,
|
|
256
|
+
{
|
|
257
|
+
// PAY-1810 (NightHawk 75394): PATCH, not PUT. A PUT to a Vibe document REPLACES it (Core UpdateAsync SET data = body), so this 3-field body
|
|
258
|
+
// erased idp_user_id / session_token_hash / the rest of the row: the revoked row then vanished from the revocation check and the user list.
|
|
259
|
+
method: 'PATCH',
|
|
260
|
+
body: {
|
|
261
|
+
status: 'revoked',
|
|
262
|
+
revoked_at: new Date().toISOString(),
|
|
263
|
+
revoked_by: `admin:${adminCheck.userId}`,
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (!result.ok) {
|
|
269
|
+
return NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return NextResponse.json({ success: true });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (action === 'revoke_all' && userId) {
|
|
276
|
+
// PAY-1810: one /query call returns ONE page (Core's default page size is 20), so a user with more active sessions than a page would keep
|
|
277
|
+
// the rest signed in. Revoked sessions leave the `status = active` filter, so re-reading page 1 walks the whole set. It stops when a read
|
|
278
|
+
// is empty (complete), when a round revokes nothing (no progress: never loops on a failing write), or after REVOKE_ALL_MAX_ROUNDS.
|
|
279
|
+
const REVOKE_ALL_PAGE_SIZE = 50;
|
|
280
|
+
const REVOKE_ALL_MAX_ROUNDS = 20;
|
|
281
|
+
let revokedCount = 0;
|
|
282
|
+
let complete = false;
|
|
283
|
+
|
|
284
|
+
for (let round = 0; round < REVOKE_ALL_MAX_ROUNDS; round++) {
|
|
285
|
+
const queryResult = await vibeServiceRequest<any>(
|
|
286
|
+
'/v1/collections/vibe_app/tables/login_sessions/query',
|
|
287
|
+
{
|
|
288
|
+
method: 'POST',
|
|
289
|
+
body: {
|
|
290
|
+
// PAY-1810: `conditions` is not a Core key (it read zero rows, so revoke_all revoked 0 and answered success); `filters` is.
|
|
291
|
+
filter: allOf([eq('idp_user_id', userId), eq('status', 'active')]),
|
|
292
|
+
page: 1,
|
|
293
|
+
pageSize: REVOKE_ALL_PAGE_SIZE,
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (!queryResult.ok) {
|
|
299
|
+
// The first read failing is a plain upstream error (as before). A later one leaves a partial result, reported as incomplete.
|
|
300
|
+
if (round === 0) {
|
|
301
|
+
return NextResponse.json({ error: queryResult.error, code: 'UPSTREAM_ERROR', correlationId: queryResult.correlationId }, { status: queryResult.status || 500 });
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const sessions = queryResult.data?.data || queryResult.data?.documents || [];
|
|
307
|
+
if (sessions.length === 0) {
|
|
308
|
+
complete = true;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let revokedThisRound = 0;
|
|
313
|
+
for (const session of sessions) {
|
|
314
|
+
const id = session.id || session.document_id;
|
|
315
|
+
const result = await vibeServiceRequest<any>(
|
|
316
|
+
`/v1/collections/vibe_app/tables/login_sessions/${id}`,
|
|
317
|
+
{
|
|
318
|
+
method: 'PATCH', // PAY-1810: PATCH merges {status, revoked_at, revoked_by}; PUT would replace the whole row (see the single revoke above)
|
|
319
|
+
body: {
|
|
320
|
+
status: 'revoked',
|
|
321
|
+
revoked_at: new Date().toISOString(),
|
|
322
|
+
revoked_by: `admin:${adminCheck.userId}`,
|
|
323
|
+
},
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
if (result.ok) {
|
|
327
|
+
revokedCount++;
|
|
328
|
+
revokedThisRound++;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (revokedThisRound === 0) break;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// success only when the whole set was revoked: a sign-out control must not say success when every write failed (Scout 75399).
|
|
335
|
+
return NextResponse.json({ success: complete, revokedCount, complete });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|