@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
package/src/api/index.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @payez/next-mvp API Module Exports
|
|
3
|
-
*
|
|
4
|
-
* Provides enhanced API route handlers with automatic token management
|
|
5
|
-
*
|
|
6
|
-
* @version 2.0.0
|
|
7
|
-
* @since auth-ready-v2
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export {
|
|
11
|
-
createAuthHandler,
|
|
12
|
-
type AuthContext,
|
|
13
|
-
type AuthHandlerOptions,
|
|
14
|
-
type HandlerFunction
|
|
15
|
-
} from './auth-handler';
|
|
16
|
-
|
|
17
|
-
// Default export for convenience
|
|
1
|
+
/**
|
|
2
|
+
* @payez/next-mvp API Module Exports
|
|
3
|
+
*
|
|
4
|
+
* Provides enhanced API route handlers with automatic token management
|
|
5
|
+
*
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
* @since auth-ready-v2
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
createAuthHandler,
|
|
12
|
+
type AuthContext,
|
|
13
|
+
type AuthHandlerOptions,
|
|
14
|
+
type HandlerFunction
|
|
15
|
+
} from './auth-handler';
|
|
16
|
+
|
|
17
|
+
// Default export for convenience
|
|
18
18
|
export { default } from './auth-handler';
|
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { getSession as getBetterAuthSession } from '../../server/auth';
|
|
3
|
-
import { getSession } from '../../lib/session-store';
|
|
4
|
-
|
|
5
|
-
interface ChangePasswordRequest {
|
|
6
|
-
current_password: string;
|
|
7
|
-
new_password: string;
|
|
8
|
-
confirm_password: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
import { nanoid } from 'nanoid';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* PAY-1596: the failure code comes from the IdP's REAL envelope { success, error: { code, message, details } } - the handler
|
|
15
|
-
* used to read a top-level `error_code` the IdP never sends, so every refusal was CHANGE_PASSWORD_FAILED and "this account has
|
|
16
|
-
* no password" / "wrong current password" could not be told apart. Allowlisted: nothing but these codes ever leaves here.
|
|
17
|
-
* - error.code NO_PASSWORD_SET / INVALID_CURRENT_PASSWORD (IdP PR 412) / VALIDATION_ERROR
|
|
18
|
-
* - the pre-412 wrong-current shape: VALIDATION_ERROR + details.value[] { code: InvalidCurrentPassword, field_name: currentPassword }
|
|
19
|
-
* - a legacy top-level error_code, for older IdPs
|
|
20
|
-
*/
|
|
21
|
-
const CHANGE_PASSWORD_CODES = new Set(['NO_PASSWORD_SET', 'INVALID_CURRENT_PASSWORD', 'VALIDATION_ERROR']);
|
|
22
|
-
|
|
23
|
-
function changePasswordFailureCode(body: any): string {
|
|
24
|
-
const err = body && typeof body.error === 'object' && body.error ? body.error : null;
|
|
25
|
-
const code: string = typeof err?.code === 'string' ? err.code : typeof body?.error_code === 'string' ? body.error_code : '';
|
|
26
|
-
if (code === 'VALIDATION_ERROR' && Array.isArray(err?.details?.value)) {
|
|
27
|
-
const wrongCurrent = err.details.value.some(
|
|
28
|
-
(e: any) => e && typeof e === 'object' && (String(e.field_name ?? e.fieldName ?? '').toLowerCase() === 'currentpassword' || String(e.code ?? '').toLowerCase() === 'invalidcurrentpassword'),
|
|
29
|
-
);
|
|
30
|
-
if (wrongCurrent) return 'INVALID_CURRENT_PASSWORD';
|
|
31
|
-
}
|
|
32
|
-
return CHANGE_PASSWORD_CODES.has(code) ? code : 'CHANGE_PASSWORD_FAILED';
|
|
33
|
-
}
|
|
34
|
-
import { projectPasswordPolicyDetails } from '../../lib/safe-error-response';
|
|
35
|
-
|
|
36
|
-
// ...
|
|
37
|
-
|
|
38
|
-
export async function POST(req: NextRequest) {
|
|
39
|
-
const requestId = nanoid();
|
|
40
|
-
try {
|
|
41
|
-
// Get session from Better Auth
|
|
42
|
-
const betterAuthSession = await getBetterAuthSession(req);
|
|
43
|
-
const sessionToken = betterAuthSession?.session?.token as string | undefined;
|
|
44
|
-
if (!betterAuthSession || typeof sessionToken !== 'string') {
|
|
45
|
-
return NextResponse.json({ success: false, message: 'Unauthorized' }, { status: 401 });
|
|
46
|
-
}
|
|
47
|
-
const sessionData = await getSession(sessionToken);
|
|
48
|
-
// NOTE: Field is idpAccessToken (not accessToken) per normalized naming convention
|
|
49
|
-
if (!sessionData?.idpAccessToken) {
|
|
50
|
-
return NextResponse.json(
|
|
51
|
-
{
|
|
52
|
-
success: false,
|
|
53
|
-
message: 'Authentication required - no access token available',
|
|
54
|
-
error_code: 'UNAUTHORIZED',
|
|
55
|
-
request_id: requestId,
|
|
56
|
-
},
|
|
57
|
-
{ status: 401 }
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const body = await req.json();
|
|
62
|
-
const { current_password, new_password, confirm_password } = body as ChangePasswordRequest;
|
|
63
|
-
|
|
64
|
-
// Validate input
|
|
65
|
-
if (!current_password || !new_password || !confirm_password) {
|
|
66
|
-
return NextResponse.json(
|
|
67
|
-
{
|
|
68
|
-
success: false,
|
|
69
|
-
message: 'Current password, new password, and confirmation are required',
|
|
70
|
-
error_code: 'VALIDATION_ERROR',
|
|
71
|
-
request_id: requestId,
|
|
72
|
-
},
|
|
73
|
-
{ status: 400 }
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (new_password !== confirm_password) {
|
|
78
|
-
return NextResponse.json(
|
|
79
|
-
{
|
|
80
|
-
success: false,
|
|
81
|
-
message: 'New password and confirmation do not match',
|
|
82
|
-
error_code: 'VALIDATION_ERROR',
|
|
83
|
-
request_id: requestId,
|
|
84
|
-
},
|
|
85
|
-
{ status: 400 }
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Get IDP base URL from environment
|
|
90
|
-
const idpBaseUrl = process.env.IDP_URL;
|
|
91
|
-
|
|
92
|
-
if (!idpBaseUrl) {
|
|
93
|
-
console.error('[CHANGE_PASSWORD] IDP_URL not configured');
|
|
94
|
-
return NextResponse.json(
|
|
95
|
-
{
|
|
96
|
-
success: false,
|
|
97
|
-
message: 'Service configuration error',
|
|
98
|
-
error_code: 'CONFIGURATION_ERROR',
|
|
99
|
-
request_id: requestId,
|
|
100
|
-
},
|
|
101
|
-
{ status: 500 }
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Proxy request to IDP
|
|
106
|
-
const idpUrl = `${idpBaseUrl}/api/Account/change-password`;
|
|
107
|
-
const idpResponse = await fetch(idpUrl, {
|
|
108
|
-
method: 'POST',
|
|
109
|
-
headers: {
|
|
110
|
-
'Content-Type': 'application/json',
|
|
111
|
-
'Authorization': `Bearer ${sessionData.idpAccessToken}`,
|
|
112
|
-
'x-request-id': requestId,
|
|
113
|
-
},
|
|
114
|
-
body: JSON.stringify({
|
|
115
|
-
current_password,
|
|
116
|
-
new_password,
|
|
117
|
-
confirm_password,
|
|
118
|
-
}),
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const responseData = await idpResponse.json().catch(() => ({}));
|
|
122
|
-
|
|
123
|
-
if (!idpResponse.ok) {
|
|
124
|
-
// Extract error message from IDP response
|
|
125
|
-
let errorMessage = 'Failed to change password';
|
|
126
|
-
|
|
127
|
-
const failureCode = changePasswordFailureCode(responseData);
|
|
128
|
-
// The real envelope nests details under `error`; older IdPs put them at the top level.
|
|
129
|
-
const idpDetails = (responseData.error && typeof responseData.error === 'object' ? responseData.error.details : undefined) ?? responseData.details;
|
|
130
|
-
if (responseData.message) {
|
|
131
|
-
errorMessage = responseData.message;
|
|
132
|
-
} else if (idpDetails?.value && Array.isArray(idpDetails.value) && idpDetails.value.length > 0) {
|
|
133
|
-
errorMessage = idpDetails.value[0].message || errorMessage;
|
|
134
|
-
} else if (idpDetails?.message) {
|
|
135
|
-
errorMessage = idpDetails.message;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return NextResponse.json(
|
|
139
|
-
{
|
|
140
|
-
success: false,
|
|
141
|
-
message: errorMessage,
|
|
142
|
-
error_code: failureCode,
|
|
143
|
-
request_id: requestId,
|
|
144
|
-
// PAY-1438: only the password-policy messages the security pages display, never the raw envelope.
|
|
145
|
-
details: projectPasswordPolicyDetails(idpDetails),
|
|
146
|
-
},
|
|
147
|
-
{ status: idpResponse.status }
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return NextResponse.json({
|
|
152
|
-
success: true,
|
|
153
|
-
message: responseData.message || 'Password changed successfully',
|
|
154
|
-
request_id: requestId,
|
|
155
|
-
});
|
|
156
|
-
} catch (error) {
|
|
157
|
-
const requestId = req.headers.get('x-request-id') ?? crypto.randomUUID();
|
|
158
|
-
// PAY-1438: the exception text stays in the log, keyed by the request_id the caller receives.
|
|
159
|
-
console.error('[CHANGE_PASSWORD] Error:', { request_id: requestId, error });
|
|
160
|
-
return NextResponse.json(
|
|
161
|
-
{
|
|
162
|
-
success: false,
|
|
163
|
-
message: 'Failed to change password',
|
|
164
|
-
error_code: 'INTERNAL_ERROR',
|
|
165
|
-
request_id: requestId,
|
|
166
|
-
},
|
|
167
|
-
{ status: 500 }
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getSession as getBetterAuthSession } from '../../server/auth';
|
|
3
|
+
import { getSession } from '../../lib/session-store';
|
|
4
|
+
|
|
5
|
+
interface ChangePasswordRequest {
|
|
6
|
+
current_password: string;
|
|
7
|
+
new_password: string;
|
|
8
|
+
confirm_password: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
import { nanoid } from 'nanoid';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* PAY-1596: the failure code comes from the IdP's REAL envelope { success, error: { code, message, details } } - the handler
|
|
15
|
+
* used to read a top-level `error_code` the IdP never sends, so every refusal was CHANGE_PASSWORD_FAILED and "this account has
|
|
16
|
+
* no password" / "wrong current password" could not be told apart. Allowlisted: nothing but these codes ever leaves here.
|
|
17
|
+
* - error.code NO_PASSWORD_SET / INVALID_CURRENT_PASSWORD (IdP PR 412) / VALIDATION_ERROR
|
|
18
|
+
* - the pre-412 wrong-current shape: VALIDATION_ERROR + details.value[] { code: InvalidCurrentPassword, field_name: currentPassword }
|
|
19
|
+
* - a legacy top-level error_code, for older IdPs
|
|
20
|
+
*/
|
|
21
|
+
const CHANGE_PASSWORD_CODES = new Set(['NO_PASSWORD_SET', 'INVALID_CURRENT_PASSWORD', 'VALIDATION_ERROR']);
|
|
22
|
+
|
|
23
|
+
function changePasswordFailureCode(body: any): string {
|
|
24
|
+
const err = body && typeof body.error === 'object' && body.error ? body.error : null;
|
|
25
|
+
const code: string = typeof err?.code === 'string' ? err.code : typeof body?.error_code === 'string' ? body.error_code : '';
|
|
26
|
+
if (code === 'VALIDATION_ERROR' && Array.isArray(err?.details?.value)) {
|
|
27
|
+
const wrongCurrent = err.details.value.some(
|
|
28
|
+
(e: any) => e && typeof e === 'object' && (String(e.field_name ?? e.fieldName ?? '').toLowerCase() === 'currentpassword' || String(e.code ?? '').toLowerCase() === 'invalidcurrentpassword'),
|
|
29
|
+
);
|
|
30
|
+
if (wrongCurrent) return 'INVALID_CURRENT_PASSWORD';
|
|
31
|
+
}
|
|
32
|
+
return CHANGE_PASSWORD_CODES.has(code) ? code : 'CHANGE_PASSWORD_FAILED';
|
|
33
|
+
}
|
|
34
|
+
import { projectPasswordPolicyDetails } from '../../lib/safe-error-response';
|
|
35
|
+
|
|
36
|
+
// ...
|
|
37
|
+
|
|
38
|
+
export async function POST(req: NextRequest) {
|
|
39
|
+
const requestId = nanoid();
|
|
40
|
+
try {
|
|
41
|
+
// Get session from Better Auth
|
|
42
|
+
const betterAuthSession = await getBetterAuthSession(req);
|
|
43
|
+
const sessionToken = betterAuthSession?.session?.token as string | undefined;
|
|
44
|
+
if (!betterAuthSession || typeof sessionToken !== 'string') {
|
|
45
|
+
return NextResponse.json({ success: false, message: 'Unauthorized' }, { status: 401 });
|
|
46
|
+
}
|
|
47
|
+
const sessionData = await getSession(sessionToken);
|
|
48
|
+
// NOTE: Field is idpAccessToken (not accessToken) per normalized naming convention
|
|
49
|
+
if (!sessionData?.idpAccessToken) {
|
|
50
|
+
return NextResponse.json(
|
|
51
|
+
{
|
|
52
|
+
success: false,
|
|
53
|
+
message: 'Authentication required - no access token available',
|
|
54
|
+
error_code: 'UNAUTHORIZED',
|
|
55
|
+
request_id: requestId,
|
|
56
|
+
},
|
|
57
|
+
{ status: 401 }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const body = await req.json();
|
|
62
|
+
const { current_password, new_password, confirm_password } = body as ChangePasswordRequest;
|
|
63
|
+
|
|
64
|
+
// Validate input
|
|
65
|
+
if (!current_password || !new_password || !confirm_password) {
|
|
66
|
+
return NextResponse.json(
|
|
67
|
+
{
|
|
68
|
+
success: false,
|
|
69
|
+
message: 'Current password, new password, and confirmation are required',
|
|
70
|
+
error_code: 'VALIDATION_ERROR',
|
|
71
|
+
request_id: requestId,
|
|
72
|
+
},
|
|
73
|
+
{ status: 400 }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (new_password !== confirm_password) {
|
|
78
|
+
return NextResponse.json(
|
|
79
|
+
{
|
|
80
|
+
success: false,
|
|
81
|
+
message: 'New password and confirmation do not match',
|
|
82
|
+
error_code: 'VALIDATION_ERROR',
|
|
83
|
+
request_id: requestId,
|
|
84
|
+
},
|
|
85
|
+
{ status: 400 }
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get IDP base URL from environment
|
|
90
|
+
const idpBaseUrl = process.env.IDP_URL;
|
|
91
|
+
|
|
92
|
+
if (!idpBaseUrl) {
|
|
93
|
+
console.error('[CHANGE_PASSWORD] IDP_URL not configured');
|
|
94
|
+
return NextResponse.json(
|
|
95
|
+
{
|
|
96
|
+
success: false,
|
|
97
|
+
message: 'Service configuration error',
|
|
98
|
+
error_code: 'CONFIGURATION_ERROR',
|
|
99
|
+
request_id: requestId,
|
|
100
|
+
},
|
|
101
|
+
{ status: 500 }
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Proxy request to IDP
|
|
106
|
+
const idpUrl = `${idpBaseUrl}/api/Account/change-password`;
|
|
107
|
+
const idpResponse = await fetch(idpUrl, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: {
|
|
110
|
+
'Content-Type': 'application/json',
|
|
111
|
+
'Authorization': `Bearer ${sessionData.idpAccessToken}`,
|
|
112
|
+
'x-request-id': requestId,
|
|
113
|
+
},
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
current_password,
|
|
116
|
+
new_password,
|
|
117
|
+
confirm_password,
|
|
118
|
+
}),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const responseData = await idpResponse.json().catch(() => ({}));
|
|
122
|
+
|
|
123
|
+
if (!idpResponse.ok) {
|
|
124
|
+
// Extract error message from IDP response
|
|
125
|
+
let errorMessage = 'Failed to change password';
|
|
126
|
+
|
|
127
|
+
const failureCode = changePasswordFailureCode(responseData);
|
|
128
|
+
// The real envelope nests details under `error`; older IdPs put them at the top level.
|
|
129
|
+
const idpDetails = (responseData.error && typeof responseData.error === 'object' ? responseData.error.details : undefined) ?? responseData.details;
|
|
130
|
+
if (responseData.message) {
|
|
131
|
+
errorMessage = responseData.message;
|
|
132
|
+
} else if (idpDetails?.value && Array.isArray(idpDetails.value) && idpDetails.value.length > 0) {
|
|
133
|
+
errorMessage = idpDetails.value[0].message || errorMessage;
|
|
134
|
+
} else if (idpDetails?.message) {
|
|
135
|
+
errorMessage = idpDetails.message;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return NextResponse.json(
|
|
139
|
+
{
|
|
140
|
+
success: false,
|
|
141
|
+
message: errorMessage,
|
|
142
|
+
error_code: failureCode,
|
|
143
|
+
request_id: requestId,
|
|
144
|
+
// PAY-1438: only the password-policy messages the security pages display, never the raw envelope.
|
|
145
|
+
details: projectPasswordPolicyDetails(idpDetails),
|
|
146
|
+
},
|
|
147
|
+
{ status: idpResponse.status }
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return NextResponse.json({
|
|
152
|
+
success: true,
|
|
153
|
+
message: responseData.message || 'Password changed successfully',
|
|
154
|
+
request_id: requestId,
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
const requestId = req.headers.get('x-request-id') ?? crypto.randomUUID();
|
|
158
|
+
// PAY-1438: the exception text stays in the log, keyed by the request_id the caller receives.
|
|
159
|
+
console.error('[CHANGE_PASSWORD] Error:', { request_id: requestId, error });
|
|
160
|
+
return NextResponse.json(
|
|
161
|
+
{
|
|
162
|
+
success: false,
|
|
163
|
+
message: 'Failed to change password',
|
|
164
|
+
error_code: 'INTERNAL_ERROR',
|
|
165
|
+
request_id: requestId,
|
|
166
|
+
},
|
|
167
|
+
{ status: 500 }
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { idpFetchJSON } from '../../lib/idp-fetch';
|
|
3
|
-
import { ENV_CONFIG } from '../../config/env';
|
|
4
|
-
import { projectIdpError } from '../../lib/safe-error-response';
|
|
5
|
-
|
|
6
|
-
// IDP masked-info is POST and uses capital 'A' in /api/Account
|
|
7
|
-
export async function POST(req: NextRequest) {
|
|
8
|
-
const url = `${ENV_CONFIG.IDP_URL}/api/Account/masked-info`;
|
|
9
|
-
|
|
10
|
-
// Forward request body if present; IDP often accepts empty object
|
|
11
|
-
let body = '{}';
|
|
12
|
-
try {
|
|
13
|
-
const raw = await req.text();
|
|
14
|
-
if (raw && raw.trim().length > 0) body = raw;
|
|
15
|
-
} catch {}
|
|
16
|
-
|
|
17
|
-
const result = await idpFetchJSON(req, url, {
|
|
18
|
-
method: 'POST',
|
|
19
|
-
headers: { 'Content-Type': 'application/json' },
|
|
20
|
-
body
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
if (!result.ok) {
|
|
24
|
-
return NextResponse.json(
|
|
25
|
-
{
|
|
26
|
-
success: false,
|
|
27
|
-
message: 'Upstream error',
|
|
28
|
-
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: projectIdpError(result.json) },
|
|
29
|
-
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
30
|
-
},
|
|
31
|
-
{ status: result.status }
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const bodyJson: any = result.json;
|
|
36
|
-
// Unwrap if IDP returns envelope { success, data }
|
|
37
|
-
if (bodyJson && typeof bodyJson === 'object' && 'success' in bodyJson && 'data' in bodyJson) {
|
|
38
|
-
if ((bodyJson as any).success === true) {
|
|
39
|
-
return NextResponse.json((bodyJson as any).data, { status: 200 });
|
|
40
|
-
}
|
|
41
|
-
return NextResponse.json(bodyJson, { status: 200 });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Passthrough otherwise
|
|
45
|
-
return NextResponse.json(bodyJson ?? {}, { status: 200 });
|
|
46
|
-
}
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { idpFetchJSON } from '../../lib/idp-fetch';
|
|
3
|
+
import { ENV_CONFIG } from '../../config/env';
|
|
4
|
+
import { projectIdpError } from '../../lib/safe-error-response';
|
|
5
|
+
|
|
6
|
+
// IDP masked-info is POST and uses capital 'A' in /api/Account
|
|
7
|
+
export async function POST(req: NextRequest) {
|
|
8
|
+
const url = `${ENV_CONFIG.IDP_URL}/api/Account/masked-info`;
|
|
9
|
+
|
|
10
|
+
// Forward request body if present; IDP often accepts empty object
|
|
11
|
+
let body = '{}';
|
|
12
|
+
try {
|
|
13
|
+
const raw = await req.text();
|
|
14
|
+
if (raw && raw.trim().length > 0) body = raw;
|
|
15
|
+
} catch {}
|
|
16
|
+
|
|
17
|
+
const result = await idpFetchJSON(req, url, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: { 'Content-Type': 'application/json' },
|
|
20
|
+
body
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!result.ok) {
|
|
24
|
+
return NextResponse.json(
|
|
25
|
+
{
|
|
26
|
+
success: false,
|
|
27
|
+
message: 'Upstream error',
|
|
28
|
+
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: projectIdpError(result.json) },
|
|
29
|
+
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
30
|
+
},
|
|
31
|
+
{ status: result.status }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const bodyJson: any = result.json;
|
|
36
|
+
// Unwrap if IDP returns envelope { success, data }
|
|
37
|
+
if (bodyJson && typeof bodyJson === 'object' && 'success' in bodyJson && 'data' in bodyJson) {
|
|
38
|
+
if ((bodyJson as any).success === true) {
|
|
39
|
+
return NextResponse.json((bodyJson as any).data, { status: 200 });
|
|
40
|
+
}
|
|
41
|
+
return NextResponse.json(bodyJson, { status: 200 });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Passthrough otherwise
|
|
45
|
+
return NextResponse.json(bodyJson ?? {}, { status: 200 });
|
|
46
|
+
}
|