@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,118 +1,118 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useEffect, useState } from 'react';
|
|
4
|
-
import { usePasswordValidation } from '../../hooks/usePasswordValidation';
|
|
5
|
-
import { passwordRulesText } from '../../lib/password-rules';
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
onSubmit: (password: string, confirmPassword: string) => void;
|
|
9
|
-
loading: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function SetPasswordStep({ onSubmit, loading }: Props) {
|
|
13
|
-
const [password, setPassword] = useState('');
|
|
14
|
-
const [confirmPassword, setConfirmPassword] = useState('');
|
|
15
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
16
|
-
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
17
|
-
|
|
18
|
-
const passwordsMatch = password === confirmPassword && password !== '';
|
|
19
|
-
|
|
20
|
-
// PAY-1591: ADVISORY only. It shows the rules and what is missing; it never disables submit, because the validator
|
|
21
|
-
// may be down and the IdP is the authority (a dead validator must not trap a user who already spent their code).
|
|
22
|
-
const { setPassword: validateAsYouType, policy, failedRequirements } = usePasswordValidation();
|
|
23
|
-
// The policy only comes back with a validation, so ask once on mount with a one-character probe (it never leaves the
|
|
24
|
-
// policy question; the answer is what the rules line is built from). Until it lands the line says the rules are coming.
|
|
25
|
-
useEffect(() => { validateAsYouType('a'); }, [validateAsYouType]);
|
|
26
|
-
const missing = failedRequirements.filter((r) => !/^(Unable to validate|Password validation)/i.test(r));
|
|
27
|
-
|
|
28
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
29
|
-
e.preventDefault();
|
|
30
|
-
if (passwordsMatch) {
|
|
31
|
-
onSubmit(password, confirmPassword);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div className="bg-white border border-gray-300 rounded p-8">
|
|
37
|
-
<h2 className="text-xl font-semibold text-gray-900 mb-2">Set New Password</h2>
|
|
38
|
-
<p className="text-sm text-gray-600 mb-2">
|
|
39
|
-
Choose a strong password for your account.
|
|
40
|
-
</p>
|
|
41
|
-
<p data-testid="password-rules" className="text-sm text-gray-700 mb-6">
|
|
42
|
-
{passwordRulesText(policy)}
|
|
43
|
-
</p>
|
|
44
|
-
|
|
45
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
46
|
-
<div>
|
|
47
|
-
<label htmlFor="password" className="block text-sm font-medium text-gray-900 mb-1">
|
|
48
|
-
New Password
|
|
49
|
-
</label>
|
|
50
|
-
<div className="relative">
|
|
51
|
-
<input
|
|
52
|
-
type={showPassword ? 'text' : 'password'}
|
|
53
|
-
id="password"
|
|
54
|
-
value={password}
|
|
55
|
-
onChange={(e) => { setPassword(e.target.value); validateAsYouType(e.target.value); }}
|
|
56
|
-
required
|
|
57
|
-
disabled={loading}
|
|
58
|
-
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 pr-10"
|
|
59
|
-
/>
|
|
60
|
-
<button
|
|
61
|
-
type="button"
|
|
62
|
-
onClick={() => setShowPassword(!showPassword)}
|
|
63
|
-
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-600 hover:text-gray-900"
|
|
64
|
-
>
|
|
65
|
-
{showPassword ? '👁️' : '👁️🗨️'}
|
|
66
|
-
</button>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<div>
|
|
71
|
-
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-900 mb-1">
|
|
72
|
-
Confirm New Password
|
|
73
|
-
</label>
|
|
74
|
-
<div className="relative">
|
|
75
|
-
<input
|
|
76
|
-
type={showConfirmPassword ? 'text' : 'password'}
|
|
77
|
-
id="confirmPassword"
|
|
78
|
-
value={confirmPassword}
|
|
79
|
-
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
80
|
-
required
|
|
81
|
-
disabled={loading}
|
|
82
|
-
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 pr-10"
|
|
83
|
-
/>
|
|
84
|
-
<button
|
|
85
|
-
type="button"
|
|
86
|
-
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
|
87
|
-
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-600 hover:text-gray-900"
|
|
88
|
-
>
|
|
89
|
-
{showConfirmPassword ? '👁️' : '👁️🗨️'}
|
|
90
|
-
</button>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
{password && missing.length > 0 && (
|
|
95
|
-
<ul data-testid="password-missing" className="text-sm text-gray-700 list-disc pl-5">
|
|
96
|
-
{missing.map((r) => (
|
|
97
|
-
<li key={r}>{r}</li>
|
|
98
|
-
))}
|
|
99
|
-
</ul>
|
|
100
|
-
)}
|
|
101
|
-
|
|
102
|
-
{!passwordsMatch && confirmPassword && (
|
|
103
|
-
<div className="bg-red-50 border border-red-200 rounded p-3">
|
|
104
|
-
<p className="text-red-700 text-sm">Passwords do not match</p>
|
|
105
|
-
</div>
|
|
106
|
-
)}
|
|
107
|
-
|
|
108
|
-
<button
|
|
109
|
-
type="submit"
|
|
110
|
-
disabled={!passwordsMatch || loading}
|
|
111
|
-
className="w-full py-2 px-4 border border-gray-300 rounded text-gray-900 font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
112
|
-
>
|
|
113
|
-
{loading ? 'Resetting Password...' : 'Reset Password'}
|
|
114
|
-
</button>
|
|
115
|
-
</form>
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { usePasswordValidation } from '../../hooks/usePasswordValidation';
|
|
5
|
+
import { passwordRulesText } from '../../lib/password-rules';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
onSubmit: (password: string, confirmPassword: string) => void;
|
|
9
|
+
loading: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function SetPasswordStep({ onSubmit, loading }: Props) {
|
|
13
|
+
const [password, setPassword] = useState('');
|
|
14
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
15
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
16
|
+
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
17
|
+
|
|
18
|
+
const passwordsMatch = password === confirmPassword && password !== '';
|
|
19
|
+
|
|
20
|
+
// PAY-1591: ADVISORY only. It shows the rules and what is missing; it never disables submit, because the validator
|
|
21
|
+
// may be down and the IdP is the authority (a dead validator must not trap a user who already spent their code).
|
|
22
|
+
const { setPassword: validateAsYouType, policy, failedRequirements } = usePasswordValidation();
|
|
23
|
+
// The policy only comes back with a validation, so ask once on mount with a one-character probe (it never leaves the
|
|
24
|
+
// policy question; the answer is what the rules line is built from). Until it lands the line says the rules are coming.
|
|
25
|
+
useEffect(() => { validateAsYouType('a'); }, [validateAsYouType]);
|
|
26
|
+
const missing = failedRequirements.filter((r) => !/^(Unable to validate|Password validation)/i.test(r));
|
|
27
|
+
|
|
28
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
if (passwordsMatch) {
|
|
31
|
+
onSubmit(password, confirmPassword);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="bg-white border border-gray-300 rounded p-8">
|
|
37
|
+
<h2 className="text-xl font-semibold text-gray-900 mb-2">Set New Password</h2>
|
|
38
|
+
<p className="text-sm text-gray-600 mb-2">
|
|
39
|
+
Choose a strong password for your account.
|
|
40
|
+
</p>
|
|
41
|
+
<p data-testid="password-rules" className="text-sm text-gray-700 mb-6">
|
|
42
|
+
{passwordRulesText(policy)}
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
46
|
+
<div>
|
|
47
|
+
<label htmlFor="password" className="block text-sm font-medium text-gray-900 mb-1">
|
|
48
|
+
New Password
|
|
49
|
+
</label>
|
|
50
|
+
<div className="relative">
|
|
51
|
+
<input
|
|
52
|
+
type={showPassword ? 'text' : 'password'}
|
|
53
|
+
id="password"
|
|
54
|
+
value={password}
|
|
55
|
+
onChange={(e) => { setPassword(e.target.value); validateAsYouType(e.target.value); }}
|
|
56
|
+
required
|
|
57
|
+
disabled={loading}
|
|
58
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 pr-10"
|
|
59
|
+
/>
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
63
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-600 hover:text-gray-900"
|
|
64
|
+
>
|
|
65
|
+
{showPassword ? '👁️' : '👁️🗨️'}
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div>
|
|
71
|
+
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-900 mb-1">
|
|
72
|
+
Confirm New Password
|
|
73
|
+
</label>
|
|
74
|
+
<div className="relative">
|
|
75
|
+
<input
|
|
76
|
+
type={showConfirmPassword ? 'text' : 'password'}
|
|
77
|
+
id="confirmPassword"
|
|
78
|
+
value={confirmPassword}
|
|
79
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
80
|
+
required
|
|
81
|
+
disabled={loading}
|
|
82
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 pr-10"
|
|
83
|
+
/>
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
|
87
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-600 hover:text-gray-900"
|
|
88
|
+
>
|
|
89
|
+
{showConfirmPassword ? '👁️' : '👁️🗨️'}
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{password && missing.length > 0 && (
|
|
95
|
+
<ul data-testid="password-missing" className="text-sm text-gray-700 list-disc pl-5">
|
|
96
|
+
{missing.map((r) => (
|
|
97
|
+
<li key={r}>{r}</li>
|
|
98
|
+
))}
|
|
99
|
+
</ul>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
{!passwordsMatch && confirmPassword && (
|
|
103
|
+
<div className="bg-red-50 border border-red-200 rounded p-3">
|
|
104
|
+
<p className="text-red-700 text-sm">Passwords do not match</p>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
<button
|
|
109
|
+
type="submit"
|
|
110
|
+
disabled={!passwordsMatch || loading}
|
|
111
|
+
className="w-full py-2 px-4 border border-gray-300 rounded text-gray-900 font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
112
|
+
>
|
|
113
|
+
{loading ? 'Resetting Password...' : 'Reset Password'}
|
|
114
|
+
</button>
|
|
115
|
+
</form>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState, useEffect } from 'react';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
code: string;
|
|
7
|
-
setCode: (code: string) => void;
|
|
8
|
-
onSubmit: () => void;
|
|
9
|
-
onResend: () => void;
|
|
10
|
-
loading: boolean;
|
|
11
|
-
maskedDestination?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function VerifyCodeStep({
|
|
15
|
-
code,
|
|
16
|
-
setCode,
|
|
17
|
-
onSubmit,
|
|
18
|
-
onResend,
|
|
19
|
-
loading,
|
|
20
|
-
maskedDestination
|
|
21
|
-
}: Props) {
|
|
22
|
-
const [resendCooldown, setResendCooldown] = useState(0);
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (resendCooldown > 0) {
|
|
26
|
-
const timer = setTimeout(() => setResendCooldown(resendCooldown - 1), 1000);
|
|
27
|
-
return () => clearTimeout(timer);
|
|
28
|
-
}
|
|
29
|
-
}, [resendCooldown]);
|
|
30
|
-
|
|
31
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
32
|
-
e.preventDefault();
|
|
33
|
-
onSubmit();
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const handleResend = () => {
|
|
37
|
-
onResend();
|
|
38
|
-
setResendCooldown(30);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<div className="bg-white border border-gray-300 rounded p-8">
|
|
43
|
-
<h2 className="text-xl font-semibold text-gray-900 mb-2">Enter Verification Code</h2>
|
|
44
|
-
{maskedDestination && (
|
|
45
|
-
<p className="text-sm text-gray-600 mb-6">
|
|
46
|
-
Enter the 6-digit code sent to <span className="font-medium">{maskedDestination}</span>
|
|
47
|
-
</p>
|
|
48
|
-
)}
|
|
49
|
-
|
|
50
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
51
|
-
<div>
|
|
52
|
-
<label htmlFor="code" className="block text-sm font-medium text-gray-900 mb-1">
|
|
53
|
-
Verification Code
|
|
54
|
-
</label>
|
|
55
|
-
<input
|
|
56
|
-
type="text"
|
|
57
|
-
id="code"
|
|
58
|
-
value={code}
|
|
59
|
-
onChange={(e) => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
|
|
60
|
-
required
|
|
61
|
-
disabled={loading}
|
|
62
|
-
placeholder="000000"
|
|
63
|
-
maxLength={6}
|
|
64
|
-
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 text-center text-2xl tracking-widest font-mono"
|
|
65
|
-
/>
|
|
66
|
-
<p className="text-xs text-gray-600 mt-1">Code expires in 5 minutes</p>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<button
|
|
70
|
-
type="submit"
|
|
71
|
-
disabled={loading || code.length !== 6}
|
|
72
|
-
className="w-full py-2 px-4 border border-gray-300 rounded text-gray-900 font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
73
|
-
>
|
|
74
|
-
{loading ? 'Verifying...' : 'Verify Code'}
|
|
75
|
-
</button>
|
|
76
|
-
|
|
77
|
-
<div className="text-center">
|
|
78
|
-
<button
|
|
79
|
-
type="button"
|
|
80
|
-
onClick={handleResend}
|
|
81
|
-
disabled={loading || resendCooldown > 0}
|
|
82
|
-
className="text-sm text-gray-900 hover:underline font-medium disabled:opacity-50 disabled:cursor-not-allowed disabled:no-underline"
|
|
83
|
-
>
|
|
84
|
-
{resendCooldown > 0 ? `Resend Code (${resendCooldown}s)` : 'Resend Code'}
|
|
85
|
-
</button>
|
|
86
|
-
</div>
|
|
87
|
-
</form>
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
code: string;
|
|
7
|
+
setCode: (code: string) => void;
|
|
8
|
+
onSubmit: () => void;
|
|
9
|
+
onResend: () => void;
|
|
10
|
+
loading: boolean;
|
|
11
|
+
maskedDestination?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function VerifyCodeStep({
|
|
15
|
+
code,
|
|
16
|
+
setCode,
|
|
17
|
+
onSubmit,
|
|
18
|
+
onResend,
|
|
19
|
+
loading,
|
|
20
|
+
maskedDestination
|
|
21
|
+
}: Props) {
|
|
22
|
+
const [resendCooldown, setResendCooldown] = useState(0);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (resendCooldown > 0) {
|
|
26
|
+
const timer = setTimeout(() => setResendCooldown(resendCooldown - 1), 1000);
|
|
27
|
+
return () => clearTimeout(timer);
|
|
28
|
+
}
|
|
29
|
+
}, [resendCooldown]);
|
|
30
|
+
|
|
31
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
onSubmit();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleResend = () => {
|
|
37
|
+
onResend();
|
|
38
|
+
setResendCooldown(30);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="bg-white border border-gray-300 rounded p-8">
|
|
43
|
+
<h2 className="text-xl font-semibold text-gray-900 mb-2">Enter Verification Code</h2>
|
|
44
|
+
{maskedDestination && (
|
|
45
|
+
<p className="text-sm text-gray-600 mb-6">
|
|
46
|
+
Enter the 6-digit code sent to <span className="font-medium">{maskedDestination}</span>
|
|
47
|
+
</p>
|
|
48
|
+
)}
|
|
49
|
+
|
|
50
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
51
|
+
<div>
|
|
52
|
+
<label htmlFor="code" className="block text-sm font-medium text-gray-900 mb-1">
|
|
53
|
+
Verification Code
|
|
54
|
+
</label>
|
|
55
|
+
<input
|
|
56
|
+
type="text"
|
|
57
|
+
id="code"
|
|
58
|
+
value={code}
|
|
59
|
+
onChange={(e) => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
|
|
60
|
+
required
|
|
61
|
+
disabled={loading}
|
|
62
|
+
placeholder="000000"
|
|
63
|
+
maxLength={6}
|
|
64
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 text-center text-2xl tracking-widest font-mono"
|
|
65
|
+
/>
|
|
66
|
+
<p className="text-xs text-gray-600 mt-1">Code expires in 5 minutes</p>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<button
|
|
70
|
+
type="submit"
|
|
71
|
+
disabled={loading || code.length !== 6}
|
|
72
|
+
className="w-full py-2 px-4 border border-gray-300 rounded text-gray-900 font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
73
|
+
>
|
|
74
|
+
{loading ? 'Verifying...' : 'Verify Code'}
|
|
75
|
+
</button>
|
|
76
|
+
|
|
77
|
+
<div className="text-center">
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
onClick={handleResend}
|
|
81
|
+
disabled={loading || resendCooldown > 0}
|
|
82
|
+
className="text-sm text-gray-900 hover:underline font-medium disabled:opacity-50 disabled:cursor-not-allowed disabled:no-underline"
|
|
83
|
+
>
|
|
84
|
+
{resendCooldown > 0 ? `Resend Code (${resendCooldown}s)` : 'Resend Code'}
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
87
|
+
</form>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|