@payez/next-mvp 4.8.1 → 4.8.2
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/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/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 +232 -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 +327 -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 +251 -251
- package/src/api-handlers/admin/vibe-data.ts +333 -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 +877 -877
- package/src/vibe/enterprise-auth.ts +208 -208
- package/src/vibe/errors.ts +209 -209
- package/src/vibe/generic.ts +339 -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,123 +1,123 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anonymous Session Preferences API Handler
|
|
3
|
-
*
|
|
4
|
-
* GET /api/anon/preferences - Get current preferences
|
|
5
|
-
* POST /api/anon/preferences - Update preferences
|
|
6
|
-
*
|
|
7
|
-
* Sets a cookie to track anonymous visitors and stores preferences in Redis.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
11
|
-
import { cookies } from 'next/headers';
|
|
12
|
-
import {
|
|
13
|
-
getOrCreateAnonSession,
|
|
14
|
-
updateAnonPreferences,
|
|
15
|
-
ANON_COOKIE_NAME,
|
|
16
|
-
AnonSessionPreferences,
|
|
17
|
-
} from '../../lib/anon-session';
|
|
18
|
-
|
|
19
|
-
const COOKIE_MAX_AGE = 90 * 24 * 60 * 60; // 90 days in seconds
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* GET handler - retrieves anonymous session preferences
|
|
23
|
-
*/
|
|
24
|
-
export async function GET(request: NextRequest) {
|
|
25
|
-
try {
|
|
26
|
-
const cookieStore = await cookies();
|
|
27
|
-
let anonId = cookieStore.get(ANON_COOKIE_NAME)?.value;
|
|
28
|
-
|
|
29
|
-
// Get or create session
|
|
30
|
-
const session = await getOrCreateAnonSession(anonId);
|
|
31
|
-
|
|
32
|
-
// Build response
|
|
33
|
-
const response = NextResponse.json({
|
|
34
|
-
success: true,
|
|
35
|
-
data: {
|
|
36
|
-
id: session.id,
|
|
37
|
-
preferences: session.preferences,
|
|
38
|
-
metrics: session.metrics,
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Set cookie if it's a new session
|
|
43
|
-
if (!anonId || anonId !== session.id) {
|
|
44
|
-
response.cookies.set(ANON_COOKIE_NAME, session.id, {
|
|
45
|
-
httpOnly: true,
|
|
46
|
-
secure: process.env.NODE_ENV === 'production',
|
|
47
|
-
sameSite: 'lax',
|
|
48
|
-
maxAge: COOKIE_MAX_AGE,
|
|
49
|
-
path: '/',
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return response;
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.error('[ANON-PREFERENCES] GET error:', error);
|
|
56
|
-
return NextResponse.json(
|
|
57
|
-
{ success: false, error: 'Failed to get preferences' },
|
|
58
|
-
{ status: 500 }
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* POST handler - updates anonymous session preferences
|
|
65
|
-
*/
|
|
66
|
-
export async function POST(request: NextRequest) {
|
|
67
|
-
try {
|
|
68
|
-
const cookieStore = await cookies();
|
|
69
|
-
let anonId = cookieStore.get(ANON_COOKIE_NAME)?.value;
|
|
70
|
-
|
|
71
|
-
// Get or create session first
|
|
72
|
-
const session = await getOrCreateAnonSession(anonId);
|
|
73
|
-
anonId = session.id;
|
|
74
|
-
|
|
75
|
-
// Parse body for preferences to update
|
|
76
|
-
const body = await request.json();
|
|
77
|
-
const preferences: Partial<AnonSessionPreferences> = body.preferences || body;
|
|
78
|
-
|
|
79
|
-
// Validate preferences
|
|
80
|
-
if (typeof preferences !== 'object') {
|
|
81
|
-
return NextResponse.json(
|
|
82
|
-
{ success: false, error: 'Invalid preferences format' },
|
|
83
|
-
{ status: 400 }
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Update preferences
|
|
88
|
-
const updatedSession = await updateAnonPreferences(anonId, preferences);
|
|
89
|
-
|
|
90
|
-
if (!updatedSession) {
|
|
91
|
-
return NextResponse.json(
|
|
92
|
-
{ success: false, error: 'Session not found' },
|
|
93
|
-
{ status: 404 }
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Build response
|
|
98
|
-
const response = NextResponse.json({
|
|
99
|
-
success: true,
|
|
100
|
-
data: {
|
|
101
|
-
id: updatedSession.id,
|
|
102
|
-
preferences: updatedSession.preferences,
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Ensure cookie is set
|
|
107
|
-
response.cookies.set(ANON_COOKIE_NAME, anonId, {
|
|
108
|
-
httpOnly: true,
|
|
109
|
-
secure: process.env.NODE_ENV === 'production',
|
|
110
|
-
sameSite: 'lax',
|
|
111
|
-
maxAge: COOKIE_MAX_AGE,
|
|
112
|
-
path: '/',
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return response;
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.error('[ANON-PREFERENCES] POST error:', error);
|
|
118
|
-
return NextResponse.json(
|
|
119
|
-
{ success: false, error: 'Failed to update preferences' },
|
|
120
|
-
{ status: 500 }
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous Session Preferences API Handler
|
|
3
|
+
*
|
|
4
|
+
* GET /api/anon/preferences - Get current preferences
|
|
5
|
+
* POST /api/anon/preferences - Update preferences
|
|
6
|
+
*
|
|
7
|
+
* Sets a cookie to track anonymous visitors and stores preferences in Redis.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
11
|
+
import { cookies } from 'next/headers';
|
|
12
|
+
import {
|
|
13
|
+
getOrCreateAnonSession,
|
|
14
|
+
updateAnonPreferences,
|
|
15
|
+
ANON_COOKIE_NAME,
|
|
16
|
+
AnonSessionPreferences,
|
|
17
|
+
} from '../../lib/anon-session';
|
|
18
|
+
|
|
19
|
+
const COOKIE_MAX_AGE = 90 * 24 * 60 * 60; // 90 days in seconds
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* GET handler - retrieves anonymous session preferences
|
|
23
|
+
*/
|
|
24
|
+
export async function GET(request: NextRequest) {
|
|
25
|
+
try {
|
|
26
|
+
const cookieStore = await cookies();
|
|
27
|
+
let anonId = cookieStore.get(ANON_COOKIE_NAME)?.value;
|
|
28
|
+
|
|
29
|
+
// Get or create session
|
|
30
|
+
const session = await getOrCreateAnonSession(anonId);
|
|
31
|
+
|
|
32
|
+
// Build response
|
|
33
|
+
const response = NextResponse.json({
|
|
34
|
+
success: true,
|
|
35
|
+
data: {
|
|
36
|
+
id: session.id,
|
|
37
|
+
preferences: session.preferences,
|
|
38
|
+
metrics: session.metrics,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Set cookie if it's a new session
|
|
43
|
+
if (!anonId || anonId !== session.id) {
|
|
44
|
+
response.cookies.set(ANON_COOKIE_NAME, session.id, {
|
|
45
|
+
httpOnly: true,
|
|
46
|
+
secure: process.env.NODE_ENV === 'production',
|
|
47
|
+
sameSite: 'lax',
|
|
48
|
+
maxAge: COOKIE_MAX_AGE,
|
|
49
|
+
path: '/',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return response;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('[ANON-PREFERENCES] GET error:', error);
|
|
56
|
+
return NextResponse.json(
|
|
57
|
+
{ success: false, error: 'Failed to get preferences' },
|
|
58
|
+
{ status: 500 }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* POST handler - updates anonymous session preferences
|
|
65
|
+
*/
|
|
66
|
+
export async function POST(request: NextRequest) {
|
|
67
|
+
try {
|
|
68
|
+
const cookieStore = await cookies();
|
|
69
|
+
let anonId = cookieStore.get(ANON_COOKIE_NAME)?.value;
|
|
70
|
+
|
|
71
|
+
// Get or create session first
|
|
72
|
+
const session = await getOrCreateAnonSession(anonId);
|
|
73
|
+
anonId = session.id;
|
|
74
|
+
|
|
75
|
+
// Parse body for preferences to update
|
|
76
|
+
const body = await request.json();
|
|
77
|
+
const preferences: Partial<AnonSessionPreferences> = body.preferences || body;
|
|
78
|
+
|
|
79
|
+
// Validate preferences
|
|
80
|
+
if (typeof preferences !== 'object') {
|
|
81
|
+
return NextResponse.json(
|
|
82
|
+
{ success: false, error: 'Invalid preferences format' },
|
|
83
|
+
{ status: 400 }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Update preferences
|
|
88
|
+
const updatedSession = await updateAnonPreferences(anonId, preferences);
|
|
89
|
+
|
|
90
|
+
if (!updatedSession) {
|
|
91
|
+
return NextResponse.json(
|
|
92
|
+
{ success: false, error: 'Session not found' },
|
|
93
|
+
{ status: 404 }
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Build response
|
|
98
|
+
const response = NextResponse.json({
|
|
99
|
+
success: true,
|
|
100
|
+
data: {
|
|
101
|
+
id: updatedSession.id,
|
|
102
|
+
preferences: updatedSession.preferences,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Ensure cookie is set
|
|
107
|
+
response.cookies.set(ANON_COOKIE_NAME, anonId, {
|
|
108
|
+
httpOnly: true,
|
|
109
|
+
secure: process.env.NODE_ENV === 'production',
|
|
110
|
+
sameSite: 'lax',
|
|
111
|
+
maxAge: COOKIE_MAX_AGE,
|
|
112
|
+
path: '/',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return response;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('[ANON-PREFERENCES] POST error:', error);
|
|
118
|
+
return NextResponse.json(
|
|
119
|
+
{ success: false, error: 'Failed to update preferences' },
|
|
120
|
+
{ status: 500 }
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { getIDPClientConfig } from '../../lib/idp-client-config';
|
|
3
|
-
import { resolveClientKey } from '../../lib/client-key';
|
|
4
|
-
import { bearerFetch } from '../../lib/bearer-fetch';
|
|
5
|
-
|
|
6
|
-
// `export const runtime = 'nodejs'` must be written LITERALLY in the app's route file (Next ignores a re-exported segment config).
|
|
7
|
-
|
|
8
|
-
/** One email-code request per this many seconds per browser (the IdP rate-limits too; this stops a double click and a loop). */
|
|
9
|
-
const EMAIL_CODE_COOLDOWN_S = 30;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* POST /api/auth/admin-login/email-code - PAY-2284: "email me a code instead".
|
|
13
|
-
*
|
|
14
|
-
* WHY THIS EXISTS. The sign-in door sends NO code at all to a user who holds an authenticator ("TOTP wins when present: there is
|
|
15
|
-
* nothing to send"). When that authenticator's secret cannot be decrypted on our side (dev-93, encryption key version 57 gone), the
|
|
16
|
-
* verify step answers "a problem on our side ... use another sign-in method" - and there was no other method on the screen and no
|
|
17
|
-
* code in the user's mailbox, because none had been requested. This route is the missing action: it asks the IdP to EMAIL a code,
|
|
18
|
-
* using the provisional bearer the door already parked in the httpOnly `adm2fa` cookie. The verify turn already tries
|
|
19
|
-
* twofa/email/verify after twofa/totp/verify, so the code is accepted in the same box.
|
|
20
|
-
*
|
|
21
|
-
* What it takes from the browser: NOTHING. No body, no client id, no address - the code goes to the account the provisional bearer
|
|
22
|
-
* belongs to (the IdP decides the recipient). What it answers: { success, codeRequested, sentVia:'email', devVerificationCode? }.
|
|
23
|
-
* `codeRequested` means "we asked", never "a mail arrived" (twofa/email/send returns 200 even when the SMTP send threw, 267561).
|
|
24
|
-
*/
|
|
25
|
-
export async function POST(req: NextRequest) {
|
|
26
|
-
const provisional = req.cookies.get('adm2fa')?.value;
|
|
27
|
-
if (!provisional) {
|
|
28
|
-
return NextResponse.json({ success: false, error: 'Your verification window expired. Sign in again.' }, { status: 401 });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const last = Number(req.cookies.get('adm2fa_emailreq')?.value);
|
|
32
|
-
const now = Math.floor(Date.now() / 1000);
|
|
33
|
-
if (Number.isFinite(last) && last > 0 && now - last < EMAIL_CODE_COOLDOWN_S) {
|
|
34
|
-
const wait = EMAIL_CODE_COOLDOWN_S - (now - last);
|
|
35
|
-
return NextResponse.json(
|
|
36
|
-
{ success: false, error: `We just asked for a code. Give it a moment (about ${wait} seconds) before asking again.`, retryAfter: wait },
|
|
37
|
-
{ status: 429 },
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let clientKey = '';
|
|
42
|
-
try {
|
|
43
|
-
const config = await getIDPClientConfig();
|
|
44
|
-
clientKey = resolveClientKey(config, 'graft/admin-login/email-code').key ?? '';
|
|
45
|
-
} catch (err) {
|
|
46
|
-
console.error('[ADMIN_LOGIN_EMAIL_CODE] IDP client config unavailable:', err instanceof Error ? err.message : String(err));
|
|
47
|
-
return NextResponse.json(
|
|
48
|
-
{ success: false, error: 'Sign-in is temporarily unavailable. This is a problem on our side. Please try again shortly.' },
|
|
49
|
-
{ status: 503 },
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let sendOk = false;
|
|
54
|
-
let devCode: string | undefined;
|
|
55
|
-
try {
|
|
56
|
-
const sendResp = await bearerFetch(`${process.env.IDP_URL}/api/ExternalAuth/twofa/email/send`, {
|
|
57
|
-
method: 'POST',
|
|
58
|
-
headers: {
|
|
59
|
-
'Content-Type': 'application/json',
|
|
60
|
-
Accept: 'application/json',
|
|
61
|
-
Authorization: `Bearer ${provisional}`,
|
|
62
|
-
'X-Client-Id': clientKey,
|
|
63
|
-
},
|
|
64
|
-
body: JSON.stringify({ client_id: clientKey }),
|
|
65
|
-
});
|
|
66
|
-
sendOk = sendResp.ok;
|
|
67
|
-
const text = await sendResp.text().catch(() => '');
|
|
68
|
-
if (!sendOk) console.error('[ADMIN_LOGIN_EMAIL_CODE] twofa/email/send failed', sendResp.status, text.slice(0, 200));
|
|
69
|
-
try {
|
|
70
|
-
const j = JSON.parse(text) as { dev_verification_code?: unknown; data?: { dev_verification_code?: unknown; result?: { dev_verification_code?: unknown } }; result?: { dev_verification_code?: unknown } } | null;
|
|
71
|
-
const echoed = j?.dev_verification_code ?? j?.data?.dev_verification_code ?? j?.data?.result?.dev_verification_code ?? j?.result?.dev_verification_code;
|
|
72
|
-
if (typeof echoed === 'string' && echoed) devCode = echoed; // present only when the server chose to echo (dev rigs); absence is the contract
|
|
73
|
-
} catch {
|
|
74
|
-
/* not JSON - nothing to echo */
|
|
75
|
-
}
|
|
76
|
-
} catch (err) {
|
|
77
|
-
console.error('[ADMIN_LOGIN_EMAIL_CODE] twofa/email/send fetch failed:', err instanceof Error ? err.message : String(err));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!sendOk) {
|
|
81
|
-
return NextResponse.json({ success: false, error: 'We could not ask for an email code just now - this is a problem on our side. Try again shortly.' }, { status: 502 });
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const res = NextResponse.json(
|
|
85
|
-
{ success: true, codeRequested: true, sentVia: 'email', ...(devCode ? { devVerificationCode: devCode } : {}) },
|
|
86
|
-
{ status: 200 },
|
|
87
|
-
);
|
|
88
|
-
const cookie = { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' as const, path: '/', maxAge: 600 };
|
|
89
|
-
res.cookies.set('adm2fa_lane', 'email', cookie); // the verify turn then tries totp -> email, exactly as for any non-SMS user
|
|
90
|
-
res.cookies.set('adm2fa_emailreq', String(now), { ...cookie, maxAge: EMAIL_CODE_COOLDOWN_S });
|
|
91
|
-
return res;
|
|
92
|
-
}
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getIDPClientConfig } from '../../lib/idp-client-config';
|
|
3
|
+
import { resolveClientKey } from '../../lib/client-key';
|
|
4
|
+
import { bearerFetch } from '../../lib/bearer-fetch';
|
|
5
|
+
|
|
6
|
+
// `export const runtime = 'nodejs'` must be written LITERALLY in the app's route file (Next ignores a re-exported segment config).
|
|
7
|
+
|
|
8
|
+
/** One email-code request per this many seconds per browser (the IdP rate-limits too; this stops a double click and a loop). */
|
|
9
|
+
const EMAIL_CODE_COOLDOWN_S = 30;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* POST /api/auth/admin-login/email-code - PAY-2284: "email me a code instead".
|
|
13
|
+
*
|
|
14
|
+
* WHY THIS EXISTS. The sign-in door sends NO code at all to a user who holds an authenticator ("TOTP wins when present: there is
|
|
15
|
+
* nothing to send"). When that authenticator's secret cannot be decrypted on our side (dev-93, encryption key version 57 gone), the
|
|
16
|
+
* verify step answers "a problem on our side ... use another sign-in method" - and there was no other method on the screen and no
|
|
17
|
+
* code in the user's mailbox, because none had been requested. This route is the missing action: it asks the IdP to EMAIL a code,
|
|
18
|
+
* using the provisional bearer the door already parked in the httpOnly `adm2fa` cookie. The verify turn already tries
|
|
19
|
+
* twofa/email/verify after twofa/totp/verify, so the code is accepted in the same box.
|
|
20
|
+
*
|
|
21
|
+
* What it takes from the browser: NOTHING. No body, no client id, no address - the code goes to the account the provisional bearer
|
|
22
|
+
* belongs to (the IdP decides the recipient). What it answers: { success, codeRequested, sentVia:'email', devVerificationCode? }.
|
|
23
|
+
* `codeRequested` means "we asked", never "a mail arrived" (twofa/email/send returns 200 even when the SMTP send threw, 267561).
|
|
24
|
+
*/
|
|
25
|
+
export async function POST(req: NextRequest) {
|
|
26
|
+
const provisional = req.cookies.get('adm2fa')?.value;
|
|
27
|
+
if (!provisional) {
|
|
28
|
+
return NextResponse.json({ success: false, error: 'Your verification window expired. Sign in again.' }, { status: 401 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const last = Number(req.cookies.get('adm2fa_emailreq')?.value);
|
|
32
|
+
const now = Math.floor(Date.now() / 1000);
|
|
33
|
+
if (Number.isFinite(last) && last > 0 && now - last < EMAIL_CODE_COOLDOWN_S) {
|
|
34
|
+
const wait = EMAIL_CODE_COOLDOWN_S - (now - last);
|
|
35
|
+
return NextResponse.json(
|
|
36
|
+
{ success: false, error: `We just asked for a code. Give it a moment (about ${wait} seconds) before asking again.`, retryAfter: wait },
|
|
37
|
+
{ status: 429 },
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let clientKey = '';
|
|
42
|
+
try {
|
|
43
|
+
const config = await getIDPClientConfig();
|
|
44
|
+
clientKey = resolveClientKey(config, 'graft/admin-login/email-code').key ?? '';
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error('[ADMIN_LOGIN_EMAIL_CODE] IDP client config unavailable:', err instanceof Error ? err.message : String(err));
|
|
47
|
+
return NextResponse.json(
|
|
48
|
+
{ success: false, error: 'Sign-in is temporarily unavailable. This is a problem on our side. Please try again shortly.' },
|
|
49
|
+
{ status: 503 },
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let sendOk = false;
|
|
54
|
+
let devCode: string | undefined;
|
|
55
|
+
try {
|
|
56
|
+
const sendResp = await bearerFetch(`${process.env.IDP_URL}/api/ExternalAuth/twofa/email/send`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: {
|
|
59
|
+
'Content-Type': 'application/json',
|
|
60
|
+
Accept: 'application/json',
|
|
61
|
+
Authorization: `Bearer ${provisional}`,
|
|
62
|
+
'X-Client-Id': clientKey,
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({ client_id: clientKey }),
|
|
65
|
+
});
|
|
66
|
+
sendOk = sendResp.ok;
|
|
67
|
+
const text = await sendResp.text().catch(() => '');
|
|
68
|
+
if (!sendOk) console.error('[ADMIN_LOGIN_EMAIL_CODE] twofa/email/send failed', sendResp.status, text.slice(0, 200));
|
|
69
|
+
try {
|
|
70
|
+
const j = JSON.parse(text) as { dev_verification_code?: unknown; data?: { dev_verification_code?: unknown; result?: { dev_verification_code?: unknown } }; result?: { dev_verification_code?: unknown } } | null;
|
|
71
|
+
const echoed = j?.dev_verification_code ?? j?.data?.dev_verification_code ?? j?.data?.result?.dev_verification_code ?? j?.result?.dev_verification_code;
|
|
72
|
+
if (typeof echoed === 'string' && echoed) devCode = echoed; // present only when the server chose to echo (dev rigs); absence is the contract
|
|
73
|
+
} catch {
|
|
74
|
+
/* not JSON - nothing to echo */
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error('[ADMIN_LOGIN_EMAIL_CODE] twofa/email/send fetch failed:', err instanceof Error ? err.message : String(err));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!sendOk) {
|
|
81
|
+
return NextResponse.json({ success: false, error: 'We could not ask for an email code just now - this is a problem on our side. Try again shortly.' }, { status: 502 });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const res = NextResponse.json(
|
|
85
|
+
{ success: true, codeRequested: true, sentVia: 'email', ...(devCode ? { devVerificationCode: devCode } : {}) },
|
|
86
|
+
{ status: 200 },
|
|
87
|
+
);
|
|
88
|
+
const cookie = { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' as const, path: '/', maxAge: 600 };
|
|
89
|
+
res.cookies.set('adm2fa_lane', 'email', cookie); // the verify turn then tries totp -> email, exactly as for any non-SMS user
|
|
90
|
+
res.cookies.set('adm2fa_emailreq', String(now), { ...cookie, maxAge: EMAIL_CODE_COOLDOWN_S });
|
|
91
|
+
return res;
|
|
92
|
+
}
|