@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,139 +1,139 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
|
|
3
|
-
interface ValidatePasswordRequest {
|
|
4
|
-
password: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
interface ValidatePasswordResponse {
|
|
8
|
-
is_valid: boolean;
|
|
9
|
-
score: number;
|
|
10
|
-
failed_requirements: string[];
|
|
11
|
-
tip?: string;
|
|
12
|
-
policy?: {
|
|
13
|
-
min_length?: number;
|
|
14
|
-
require_uppercase?: boolean;
|
|
15
|
-
require_lowercase?: boolean;
|
|
16
|
-
require_digit?: boolean;
|
|
17
|
-
require_special?: boolean;
|
|
18
|
-
min_strength_score?: number;
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function POST(req: NextRequest) {
|
|
23
|
-
try {
|
|
24
|
-
const body = await req.json();
|
|
25
|
-
const { password } = body as ValidatePasswordRequest;
|
|
26
|
-
const requestId = req.headers.get('x-request-id') ?? crypto.randomUUID();
|
|
27
|
-
|
|
28
|
-
// Validate input
|
|
29
|
-
if (!password || typeof password !== 'string') {
|
|
30
|
-
return NextResponse.json(
|
|
31
|
-
{
|
|
32
|
-
is_valid: false,
|
|
33
|
-
score: 0,
|
|
34
|
-
failed_requirements: ['Password is required'],
|
|
35
|
-
} as ValidatePasswordResponse,
|
|
36
|
-
{
|
|
37
|
-
status: 200, // Return 200 even for validation errors to keep UI responsive
|
|
38
|
-
headers: { 'Cache-Control': 'no-store' },
|
|
39
|
-
}
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Get IDP base URL and client ID from environment
|
|
44
|
-
const idpBaseUrl = process.env.IDP_URL;
|
|
45
|
-
|
|
46
|
-
const clientId = process.env.CLIENT_ID;
|
|
47
|
-
|
|
48
|
-
if (!idpBaseUrl) {
|
|
49
|
-
console.error('[VALIDATE_PASSWORD] IDP_URL not configured');
|
|
50
|
-
return NextResponse.json(
|
|
51
|
-
{
|
|
52
|
-
is_valid: false,
|
|
53
|
-
score: 0,
|
|
54
|
-
failed_requirements: ['Password validation service unavailable'],
|
|
55
|
-
} as ValidatePasswordResponse,
|
|
56
|
-
{
|
|
57
|
-
status: 200,
|
|
58
|
-
headers: { 'Cache-Control': 'no-store' },
|
|
59
|
-
}
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Proxy request to IDP
|
|
64
|
-
const idpUrl = `${idpBaseUrl}/api/Account/validate-password`;
|
|
65
|
-
const payload = {
|
|
66
|
-
password,
|
|
67
|
-
client_id: clientId,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const idpResponse = await fetch(idpUrl, {
|
|
71
|
-
method: 'POST',
|
|
72
|
-
headers: {
|
|
73
|
-
'Content-Type': 'application/json',
|
|
74
|
-
'x-request-id': requestId,
|
|
75
|
-
},
|
|
76
|
-
body: JSON.stringify(payload),
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const responseData = await idpResponse.json().catch(() => ({}));
|
|
80
|
-
|
|
81
|
-
if (!idpResponse.ok) {
|
|
82
|
-
// PAY-1424: shape only, never the envelope contents (this endpoint carries a password).
|
|
83
|
-
console.error('[VALIDATE_PASSWORD] IDP error:', {
|
|
84
|
-
status: idpResponse.status,
|
|
85
|
-
bodyKeys: responseData && typeof responseData === 'object' ? Object.keys(responseData) : undefined,
|
|
86
|
-
errorKeys: (responseData as any)?.error && typeof (responseData as any)?.error === 'object' ? Object.keys((responseData as any)?.error) : undefined,
|
|
87
|
-
});
|
|
88
|
-
return NextResponse.json(
|
|
89
|
-
{
|
|
90
|
-
is_valid: false,
|
|
91
|
-
score: 0,
|
|
92
|
-
failed_requirements: ['Password validation failed'],
|
|
93
|
-
} as ValidatePasswordResponse,
|
|
94
|
-
{
|
|
95
|
-
status: 200,
|
|
96
|
-
headers: { 'Cache-Control': 'no-store' },
|
|
97
|
-
}
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* 283211 - UNWRAP THE IDP ENVELOPE. Approved by BAPert 2026-08-30.
|
|
103
|
-
*
|
|
104
|
-
* The IDP answers this endpoint with an ENVELOPE - { success, data: { is_valid, score,
|
|
105
|
-
* failed_requirements, policy } } - and this line used to hand that straight back. The
|
|
106
|
-
* `as ValidatePasswordResponse` is a COMPILE-TIME ASSERTION AND PERFORMS NO RUNTIME
|
|
107
|
-
* TRANSFORMATION, so nothing ever reshaped it and the type told us it was fine.
|
|
108
|
-
*
|
|
109
|
-
* The consumer is usePasswordValidation, which reads `data.is_valid` at the top level and
|
|
110
|
-
* calls setIsValid with it. Against an envelope that is `undefined`, so isValid never
|
|
111
|
-
* became true; and EVERY error path in this file returns a FLAT { is_valid: false }. So the
|
|
112
|
-
* success shape was unreadable and the failure shape was readable-and-false: setIsValid(true)
|
|
113
|
-
* was UNREACHABLE, and the Change Password button's `canSubmit` could never enable.
|
|
114
|
-
*
|
|
115
|
-
* `responseData?.data ?? responseData` is defensive in both directions BY DESIGN: it unwraps
|
|
116
|
-
* the envelope we get today and passes a flat body through unchanged, so it does not break
|
|
117
|
-
* if the IDP is later made consistent with this file's own declared response type.
|
|
118
|
-
*/
|
|
119
|
-
const unwrapped = (responseData as any)?.data ?? responseData;
|
|
120
|
-
|
|
121
|
-
return NextResponse.json(unwrapped as ValidatePasswordResponse, {
|
|
122
|
-
status: 200,
|
|
123
|
-
headers: { 'Cache-Control': 'no-store' },
|
|
124
|
-
});
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.error('[VALIDATE_PASSWORD] Error:', error);
|
|
127
|
-
return NextResponse.json(
|
|
128
|
-
{
|
|
129
|
-
is_valid: false,
|
|
130
|
-
score: 0,
|
|
131
|
-
failed_requirements: ['Password validation failed'],
|
|
132
|
-
} as ValidatePasswordResponse,
|
|
133
|
-
{
|
|
134
|
-
status: 200,
|
|
135
|
-
headers: { 'Cache-Control': 'no-store' },
|
|
136
|
-
}
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface ValidatePasswordRequest {
|
|
4
|
+
password: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface ValidatePasswordResponse {
|
|
8
|
+
is_valid: boolean;
|
|
9
|
+
score: number;
|
|
10
|
+
failed_requirements: string[];
|
|
11
|
+
tip?: string;
|
|
12
|
+
policy?: {
|
|
13
|
+
min_length?: number;
|
|
14
|
+
require_uppercase?: boolean;
|
|
15
|
+
require_lowercase?: boolean;
|
|
16
|
+
require_digit?: boolean;
|
|
17
|
+
require_special?: boolean;
|
|
18
|
+
min_strength_score?: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function POST(req: NextRequest) {
|
|
23
|
+
try {
|
|
24
|
+
const body = await req.json();
|
|
25
|
+
const { password } = body as ValidatePasswordRequest;
|
|
26
|
+
const requestId = req.headers.get('x-request-id') ?? crypto.randomUUID();
|
|
27
|
+
|
|
28
|
+
// Validate input
|
|
29
|
+
if (!password || typeof password !== 'string') {
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{
|
|
32
|
+
is_valid: false,
|
|
33
|
+
score: 0,
|
|
34
|
+
failed_requirements: ['Password is required'],
|
|
35
|
+
} as ValidatePasswordResponse,
|
|
36
|
+
{
|
|
37
|
+
status: 200, // Return 200 even for validation errors to keep UI responsive
|
|
38
|
+
headers: { 'Cache-Control': 'no-store' },
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Get IDP base URL and client ID from environment
|
|
44
|
+
const idpBaseUrl = process.env.IDP_URL;
|
|
45
|
+
|
|
46
|
+
const clientId = process.env.CLIENT_ID;
|
|
47
|
+
|
|
48
|
+
if (!idpBaseUrl) {
|
|
49
|
+
console.error('[VALIDATE_PASSWORD] IDP_URL not configured');
|
|
50
|
+
return NextResponse.json(
|
|
51
|
+
{
|
|
52
|
+
is_valid: false,
|
|
53
|
+
score: 0,
|
|
54
|
+
failed_requirements: ['Password validation service unavailable'],
|
|
55
|
+
} as ValidatePasswordResponse,
|
|
56
|
+
{
|
|
57
|
+
status: 200,
|
|
58
|
+
headers: { 'Cache-Control': 'no-store' },
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Proxy request to IDP
|
|
64
|
+
const idpUrl = `${idpBaseUrl}/api/Account/validate-password`;
|
|
65
|
+
const payload = {
|
|
66
|
+
password,
|
|
67
|
+
client_id: clientId,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const idpResponse = await fetch(idpUrl, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
'x-request-id': requestId,
|
|
75
|
+
},
|
|
76
|
+
body: JSON.stringify(payload),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const responseData = await idpResponse.json().catch(() => ({}));
|
|
80
|
+
|
|
81
|
+
if (!idpResponse.ok) {
|
|
82
|
+
// PAY-1424: shape only, never the envelope contents (this endpoint carries a password).
|
|
83
|
+
console.error('[VALIDATE_PASSWORD] IDP error:', {
|
|
84
|
+
status: idpResponse.status,
|
|
85
|
+
bodyKeys: responseData && typeof responseData === 'object' ? Object.keys(responseData) : undefined,
|
|
86
|
+
errorKeys: (responseData as any)?.error && typeof (responseData as any)?.error === 'object' ? Object.keys((responseData as any)?.error) : undefined,
|
|
87
|
+
});
|
|
88
|
+
return NextResponse.json(
|
|
89
|
+
{
|
|
90
|
+
is_valid: false,
|
|
91
|
+
score: 0,
|
|
92
|
+
failed_requirements: ['Password validation failed'],
|
|
93
|
+
} as ValidatePasswordResponse,
|
|
94
|
+
{
|
|
95
|
+
status: 200,
|
|
96
|
+
headers: { 'Cache-Control': 'no-store' },
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 283211 - UNWRAP THE IDP ENVELOPE. Approved by BAPert 2026-08-30.
|
|
103
|
+
*
|
|
104
|
+
* The IDP answers this endpoint with an ENVELOPE - { success, data: { is_valid, score,
|
|
105
|
+
* failed_requirements, policy } } - and this line used to hand that straight back. The
|
|
106
|
+
* `as ValidatePasswordResponse` is a COMPILE-TIME ASSERTION AND PERFORMS NO RUNTIME
|
|
107
|
+
* TRANSFORMATION, so nothing ever reshaped it and the type told us it was fine.
|
|
108
|
+
*
|
|
109
|
+
* The consumer is usePasswordValidation, which reads `data.is_valid` at the top level and
|
|
110
|
+
* calls setIsValid with it. Against an envelope that is `undefined`, so isValid never
|
|
111
|
+
* became true; and EVERY error path in this file returns a FLAT { is_valid: false }. So the
|
|
112
|
+
* success shape was unreadable and the failure shape was readable-and-false: setIsValid(true)
|
|
113
|
+
* was UNREACHABLE, and the Change Password button's `canSubmit` could never enable.
|
|
114
|
+
*
|
|
115
|
+
* `responseData?.data ?? responseData` is defensive in both directions BY DESIGN: it unwraps
|
|
116
|
+
* the envelope we get today and passes a flat body through unchanged, so it does not break
|
|
117
|
+
* if the IDP is later made consistent with this file's own declared response type.
|
|
118
|
+
*/
|
|
119
|
+
const unwrapped = (responseData as any)?.data ?? responseData;
|
|
120
|
+
|
|
121
|
+
return NextResponse.json(unwrapped as ValidatePasswordResponse, {
|
|
122
|
+
status: 200,
|
|
123
|
+
headers: { 'Cache-Control': 'no-store' },
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('[VALIDATE_PASSWORD] Error:', error);
|
|
127
|
+
return NextResponse.json(
|
|
128
|
+
{
|
|
129
|
+
is_valid: false,
|
|
130
|
+
score: 0,
|
|
131
|
+
failed_requirements: ['Password validation failed'],
|
|
132
|
+
} as ValidatePasswordResponse,
|
|
133
|
+
{
|
|
134
|
+
status: 200,
|
|
135
|
+
headers: { 'Cache-Control': 'no-store' },
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Verify Email 2FA Code Handler
|
|
3
|
-
*
|
|
4
|
-
* Verifies the 2FA email verification code and completes the 2FA flow.
|
|
5
|
-
* Updates the session with new tokens upon successful verification.
|
|
6
|
-
*
|
|
7
|
-
* @package @payez/next-mvp
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
11
|
-
import { idpFetchJSON } from '../../lib/idp-fetch';
|
|
12
|
-
import { ENV_CONFIG } from '../../config/env';
|
|
13
|
-
import { getTokenTestAware } from '../../lib/test-aware-get-token';
|
|
14
|
-
import { transitionTo2FASession } from '../../lib/session-store';
|
|
15
|
-
import { jwtDecode } from '../../lib/jwt-decode';
|
|
16
|
-
import { twoFactorVerifyFailure } from '../../lib/two-factor-verify-failure';
|
|
17
|
-
|
|
18
|
-
export async function POST(req: NextRequest) {
|
|
19
|
-
try {
|
|
20
|
-
// Parse request body
|
|
21
|
-
const body = await req.json();
|
|
22
|
-
const verificationCode = body.verificationCode || body.verification_code;
|
|
23
|
-
|
|
24
|
-
if (!verificationCode) {
|
|
25
|
-
return NextResponse.json(
|
|
26
|
-
{
|
|
27
|
-
success: false,
|
|
28
|
-
error: 'Verification code is required',
|
|
29
|
-
code: 'INVALID_REQUEST',
|
|
30
|
-
},
|
|
31
|
-
{ status: 400 }
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Call IDP using idpFetchJSON which auto-injects Bearer token from Redis session
|
|
36
|
-
const result = await idpFetchJSON(req, `${ENV_CONFIG.IDP_URL}/api/ExternalAuth/twofa/email/verify`, {
|
|
37
|
-
method: 'POST',
|
|
38
|
-
headers: { 'Content-Type': 'application/json' },
|
|
39
|
-
body: JSON.stringify({ verification_code: verificationCode }),
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (!result.ok) {
|
|
43
|
-
// PAY-1443: never relay the IDP's error text - it arrives as a C# anonymous-object
|
|
44
|
-
// ToString ("{ message = ... }") on the deployed IDP. Classify it, answer a fixed sentence.
|
|
45
|
-
return twoFactorVerifyFailure(result, 'email');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Unwrap IDP envelope
|
|
49
|
-
const unwrappedData = result.json?.data || result.json;
|
|
50
|
-
|
|
51
|
-
// If we have new tokens, update the session to complete 2FA
|
|
52
|
-
// Reports whether the session ACTUALLY transitioned. Must not be assumed
|
|
53
|
-
// from a 2xx: the IDP can verify the code while we fail to transition
|
|
54
|
-
// (no tokens returned, or no resolvable session token).
|
|
55
|
-
let sessionTransitioned = false;
|
|
56
|
-
|
|
57
|
-
if (unwrappedData.access_token && unwrappedData.refresh_token) {
|
|
58
|
-
// Get session token from NextAuth
|
|
59
|
-
// Support both field names: sessionToken (auth.ts JWT) and redisSessionId (legacy)
|
|
60
|
-
const token = await getTokenTestAware(req);
|
|
61
|
-
const sessionToken = (token?.sessionToken || token?.redisSessionId) as string | undefined;
|
|
62
|
-
|
|
63
|
-
if (sessionToken) {
|
|
64
|
-
console.log('[VERIFY_EMAIL] Updating session with new tokens');
|
|
65
|
-
|
|
66
|
-
// Decode access token to get actual expiration
|
|
67
|
-
let accessTokenExpires = Date.now() + (15 * 60 * 1000); // Default: 15 minutes
|
|
68
|
-
try {
|
|
69
|
-
const decoded = jwtDecode<{ exp?: number }>(unwrappedData.access_token);
|
|
70
|
-
if (decoded?.exp) {
|
|
71
|
-
accessTokenExpires = decoded.exp * 1000; // Convert to milliseconds
|
|
72
|
-
}
|
|
73
|
-
} catch (err) {
|
|
74
|
-
console.warn('[VERIFY_EMAIL] Could not decode access token, using default expiration');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Decode refresh token to get actual expiration (optional)
|
|
78
|
-
let refreshTokenExpires = Date.now() + (3 * 24 * 60 * 60 * 1000); // Default: 3 days
|
|
79
|
-
try {
|
|
80
|
-
const decoded = jwtDecode<{ exp?: number }>(unwrappedData.refresh_token);
|
|
81
|
-
if (decoded?.exp) {
|
|
82
|
-
refreshTokenExpires = decoded.exp * 1000;
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
// Refresh token may not have exp claim, use default
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Update session with new tokens and mark 2FA complete
|
|
89
|
-
await transitionTo2FASession(
|
|
90
|
-
sessionToken,
|
|
91
|
-
{
|
|
92
|
-
accessToken: unwrappedData.access_token,
|
|
93
|
-
refreshToken: unwrappedData.refresh_token,
|
|
94
|
-
accessTokenExpires,
|
|
95
|
-
refreshTokenExpires
|
|
96
|
-
},
|
|
97
|
-
'email' // Store 2FA method for refresh token flow
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
sessionTransitioned = true;
|
|
101
|
-
|
|
102
|
-
console.log('[VERIFY_EMAIL] Session updated successfully', {
|
|
103
|
-
accessTokenExpires: new Date(accessTokenExpires).toISOString(),
|
|
104
|
-
refreshTokenExpires: new Date(refreshTokenExpires).toISOString()
|
|
105
|
-
});
|
|
106
|
-
} else {
|
|
107
|
-
console.warn('[VERIFY_EMAIL] IDP verified the code but no session token was resolvable — session NOT transitioned');
|
|
108
|
-
}
|
|
109
|
-
} else {
|
|
110
|
-
console.warn('[VERIFY_EMAIL] IDP returned 2xx without tokens — session NOT transitioned');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Return simplified success response (don't expose tokens to client)
|
|
114
|
-
return NextResponse.json({
|
|
115
|
-
success: true,
|
|
116
|
-
verificationSuccessful: true,
|
|
117
|
-
// NOT a copy of `success` — reports what actually happened. A client that
|
|
118
|
-
// trusted a hardcoded true would show the user as MFA-verified while the
|
|
119
|
-
// session still says otherwise.
|
|
120
|
-
twoFactorSessionVerified: sessionTransitioned,
|
|
121
|
-
message: unwrappedData.message || 'Email code verified successfully'
|
|
122
|
-
}, { status: 200 });
|
|
123
|
-
} catch (error) {
|
|
124
|
-
console.error('[VERIFY_EMAIL] Error:', error);
|
|
125
|
-
return NextResponse.json(
|
|
126
|
-
{
|
|
127
|
-
success: false,
|
|
128
|
-
error: 'Failed to verify code',
|
|
129
|
-
code: 'INTERNAL_ERROR',
|
|
130
|
-
},
|
|
131
|
-
{ status: 500 }
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Verify Email 2FA Code Handler
|
|
3
|
+
*
|
|
4
|
+
* Verifies the 2FA email verification code and completes the 2FA flow.
|
|
5
|
+
* Updates the session with new tokens upon successful verification.
|
|
6
|
+
*
|
|
7
|
+
* @package @payez/next-mvp
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
11
|
+
import { idpFetchJSON } from '../../lib/idp-fetch';
|
|
12
|
+
import { ENV_CONFIG } from '../../config/env';
|
|
13
|
+
import { getTokenTestAware } from '../../lib/test-aware-get-token';
|
|
14
|
+
import { transitionTo2FASession } from '../../lib/session-store';
|
|
15
|
+
import { jwtDecode } from '../../lib/jwt-decode';
|
|
16
|
+
import { twoFactorVerifyFailure } from '../../lib/two-factor-verify-failure';
|
|
17
|
+
|
|
18
|
+
export async function POST(req: NextRequest) {
|
|
19
|
+
try {
|
|
20
|
+
// Parse request body
|
|
21
|
+
const body = await req.json();
|
|
22
|
+
const verificationCode = body.verificationCode || body.verification_code;
|
|
23
|
+
|
|
24
|
+
if (!verificationCode) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{
|
|
27
|
+
success: false,
|
|
28
|
+
error: 'Verification code is required',
|
|
29
|
+
code: 'INVALID_REQUEST',
|
|
30
|
+
},
|
|
31
|
+
{ status: 400 }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Call IDP using idpFetchJSON which auto-injects Bearer token from Redis session
|
|
36
|
+
const result = await idpFetchJSON(req, `${ENV_CONFIG.IDP_URL}/api/ExternalAuth/twofa/email/verify`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify({ verification_code: verificationCode }),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!result.ok) {
|
|
43
|
+
// PAY-1443: never relay the IDP's error text - it arrives as a C# anonymous-object
|
|
44
|
+
// ToString ("{ message = ... }") on the deployed IDP. Classify it, answer a fixed sentence.
|
|
45
|
+
return twoFactorVerifyFailure(result, 'email');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Unwrap IDP envelope
|
|
49
|
+
const unwrappedData = result.json?.data || result.json;
|
|
50
|
+
|
|
51
|
+
// If we have new tokens, update the session to complete 2FA
|
|
52
|
+
// Reports whether the session ACTUALLY transitioned. Must not be assumed
|
|
53
|
+
// from a 2xx: the IDP can verify the code while we fail to transition
|
|
54
|
+
// (no tokens returned, or no resolvable session token).
|
|
55
|
+
let sessionTransitioned = false;
|
|
56
|
+
|
|
57
|
+
if (unwrappedData.access_token && unwrappedData.refresh_token) {
|
|
58
|
+
// Get session token from NextAuth
|
|
59
|
+
// Support both field names: sessionToken (auth.ts JWT) and redisSessionId (legacy)
|
|
60
|
+
const token = await getTokenTestAware(req);
|
|
61
|
+
const sessionToken = (token?.sessionToken || token?.redisSessionId) as string | undefined;
|
|
62
|
+
|
|
63
|
+
if (sessionToken) {
|
|
64
|
+
console.log('[VERIFY_EMAIL] Updating session with new tokens');
|
|
65
|
+
|
|
66
|
+
// Decode access token to get actual expiration
|
|
67
|
+
let accessTokenExpires = Date.now() + (15 * 60 * 1000); // Default: 15 minutes
|
|
68
|
+
try {
|
|
69
|
+
const decoded = jwtDecode<{ exp?: number }>(unwrappedData.access_token);
|
|
70
|
+
if (decoded?.exp) {
|
|
71
|
+
accessTokenExpires = decoded.exp * 1000; // Convert to milliseconds
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.warn('[VERIFY_EMAIL] Could not decode access token, using default expiration');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Decode refresh token to get actual expiration (optional)
|
|
78
|
+
let refreshTokenExpires = Date.now() + (3 * 24 * 60 * 60 * 1000); // Default: 3 days
|
|
79
|
+
try {
|
|
80
|
+
const decoded = jwtDecode<{ exp?: number }>(unwrappedData.refresh_token);
|
|
81
|
+
if (decoded?.exp) {
|
|
82
|
+
refreshTokenExpires = decoded.exp * 1000;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// Refresh token may not have exp claim, use default
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Update session with new tokens and mark 2FA complete
|
|
89
|
+
await transitionTo2FASession(
|
|
90
|
+
sessionToken,
|
|
91
|
+
{
|
|
92
|
+
accessToken: unwrappedData.access_token,
|
|
93
|
+
refreshToken: unwrappedData.refresh_token,
|
|
94
|
+
accessTokenExpires,
|
|
95
|
+
refreshTokenExpires
|
|
96
|
+
},
|
|
97
|
+
'email' // Store 2FA method for refresh token flow
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
sessionTransitioned = true;
|
|
101
|
+
|
|
102
|
+
console.log('[VERIFY_EMAIL] Session updated successfully', {
|
|
103
|
+
accessTokenExpires: new Date(accessTokenExpires).toISOString(),
|
|
104
|
+
refreshTokenExpires: new Date(refreshTokenExpires).toISOString()
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
console.warn('[VERIFY_EMAIL] IDP verified the code but no session token was resolvable — session NOT transitioned');
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
console.warn('[VERIFY_EMAIL] IDP returned 2xx without tokens — session NOT transitioned');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Return simplified success response (don't expose tokens to client)
|
|
114
|
+
return NextResponse.json({
|
|
115
|
+
success: true,
|
|
116
|
+
verificationSuccessful: true,
|
|
117
|
+
// NOT a copy of `success` — reports what actually happened. A client that
|
|
118
|
+
// trusted a hardcoded true would show the user as MFA-verified while the
|
|
119
|
+
// session still says otherwise.
|
|
120
|
+
twoFactorSessionVerified: sessionTransitioned,
|
|
121
|
+
message: unwrappedData.message || 'Email code verified successfully'
|
|
122
|
+
}, { status: 200 });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('[VERIFY_EMAIL] Error:', error);
|
|
125
|
+
return NextResponse.json(
|
|
126
|
+
{
|
|
127
|
+
success: false,
|
|
128
|
+
error: 'Failed to verify code',
|
|
129
|
+
code: 'INTERNAL_ERROR',
|
|
130
|
+
},
|
|
131
|
+
{ status: 500 }
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|