@payez/next-mvp 4.8.0 → 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/middleware/create-middleware.js +6 -5
- 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/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 +1139 -1138
- 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/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,143 +1,143 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useAvailableProviders Hook
|
|
3
|
-
*
|
|
4
|
-
* Returns the list of OAuth providers configured for this client.
|
|
5
|
-
* Ensures UI only shows buttons for providers that are enabled in IDP.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use client';
|
|
9
|
-
|
|
10
|
-
import { useState, useEffect } from 'react';
|
|
11
|
-
import type { FederatedProvider } from '../types/auth';
|
|
12
|
-
|
|
13
|
-
// Map provider IDs to our FederatedProvider type
|
|
14
|
-
const PROVIDER_MAP: Record<string, FederatedProvider> = {
|
|
15
|
-
'google': 'google',
|
|
16
|
-
'apple': 'apple',
|
|
17
|
-
'facebook': 'facebook',
|
|
18
|
-
'github': 'github',
|
|
19
|
-
'azure-ad': 'microsoft',
|
|
20
|
-
'microsoft-entra-id': 'microsoft',
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// Providers we support in UI (excludes credentials)
|
|
24
|
-
const OAUTH_PROVIDERS = ['google', 'apple', 'facebook', 'github', 'azure-ad', 'microsoft-entra-id'];
|
|
25
|
-
|
|
26
|
-
export interface UseAvailableProvidersResult {
|
|
27
|
-
providers: FederatedProvider[];
|
|
28
|
-
isLoading: boolean;
|
|
29
|
-
error: Error | null;
|
|
30
|
-
rawProviders: Record<string, any> | null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Hook to get available federated OAuth providers.
|
|
35
|
-
*
|
|
36
|
-
* Returns only the providers that are enabled in IDP config.
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```tsx
|
|
40
|
-
* function SignupPage() {
|
|
41
|
-
* const { providers, isLoading } = useAvailableProviders();
|
|
42
|
-
*
|
|
43
|
-
* if (isLoading) return <Spinner />;
|
|
44
|
-
*
|
|
45
|
-
* return (
|
|
46
|
-
* <FederatedAuthSection
|
|
47
|
-
* providers={providers}
|
|
48
|
-
* onProviderClick={handleClick}
|
|
49
|
-
* />
|
|
50
|
-
* );
|
|
51
|
-
* }
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
export function useAvailableProviders(): UseAvailableProvidersResult {
|
|
55
|
-
const [providers, setProviders] = useState<FederatedProvider[]>([]);
|
|
56
|
-
const [rawProviders, setRawProviders] = useState<Record<string, any> | null>(null);
|
|
57
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
58
|
-
const [error, setError] = useState<Error | null>(null);
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
let mounted = true;
|
|
62
|
-
|
|
63
|
-
async function fetchProviders() {
|
|
64
|
-
try {
|
|
65
|
-
// PAY-2104: THIS USED TO BE A HARDCODED GOOGLE LITERAL.
|
|
66
|
-
//
|
|
67
|
-
// const result = { google: { id: 'google', name: 'Google' } };
|
|
68
|
-
//
|
|
69
|
-
// under a comment claiming providers were "loaded dynamically". It
|
|
70
|
-
// returned Google for EVERY client whatever the tenant had enabled -
|
|
71
|
-
// a config-shaped constant, so Apple/Facebook (PAY-2104) could never
|
|
72
|
-
// appear and a client that had DISABLED Google would still see its
|
|
73
|
-
// button. The real answer is served by /api/auth/settings (the same
|
|
74
|
-
// route routes/auth/settings.ts builds from the IDP's oauthProviders).
|
|
75
|
-
const response = await fetch('/api/auth/settings', { cache: 'no-store' });
|
|
76
|
-
|
|
77
|
-
if (!mounted) return;
|
|
78
|
-
|
|
79
|
-
if (!response.ok) {
|
|
80
|
-
// FAIL CLOSED. An unreadable policy is not an empty provider list
|
|
81
|
-
// that we may then pad with a guess - it is "we do not know". See
|
|
82
|
-
// the PAY-1243 rule: never fabricate a provider list.
|
|
83
|
-
throw new Error(`Failed to fetch auth settings: ${response.status}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const body = await response.json().catch(() => null);
|
|
87
|
-
const settings = body?.data ?? body;
|
|
88
|
-
if (body?.success === false || !settings) {
|
|
89
|
-
throw new Error(body?.error?.code ?? 'POLICY_UNAVAILABLE');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Keep the raw provider rows (id -> the settings row) for callers that
|
|
93
|
-
// want more than the mapped union type, exactly as before.
|
|
94
|
-
const enabledIds: string[] = Array.isArray(settings.enabledProviders)
|
|
95
|
-
? settings.enabledProviders.filter((p: unknown): p is string => typeof p === 'string')
|
|
96
|
-
: [];
|
|
97
|
-
const raw: Record<string, { id: string; name: string }> = {};
|
|
98
|
-
for (const id of enabledIds) raw[id] = { id, name: id };
|
|
99
|
-
|
|
100
|
-
if (!mounted) return;
|
|
101
|
-
setRawProviders(raw);
|
|
102
|
-
|
|
103
|
-
// Filter to OAuth providers only (exclude credentials) and map to our
|
|
104
|
-
// FederatedProvider union, in the IDP's order.
|
|
105
|
-
const oauthProviders = enabledIds
|
|
106
|
-
.filter(id => OAUTH_PROVIDERS.includes(id))
|
|
107
|
-
.map(id => PROVIDER_MAP[id])
|
|
108
|
-
.filter((p): p is FederatedProvider => p !== undefined);
|
|
109
|
-
|
|
110
|
-
setProviders(oauthProviders);
|
|
111
|
-
setError(null);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
if (!mounted) return;
|
|
114
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
115
|
-
setProviders([]);
|
|
116
|
-
setRawProviders(null); // never leave a stale list behind a failure
|
|
117
|
-
} finally {
|
|
118
|
-
if (mounted) {
|
|
119
|
-
setIsLoading(false);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
fetchProviders();
|
|
125
|
-
|
|
126
|
-
return () => {
|
|
127
|
-
mounted = false;
|
|
128
|
-
};
|
|
129
|
-
}, []);
|
|
130
|
-
|
|
131
|
-
return { providers, isLoading, error, rawProviders };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Check if a specific provider is available.
|
|
136
|
-
*
|
|
137
|
-
* @param provider The provider to check
|
|
138
|
-
* @returns boolean indicating if provider is configured
|
|
139
|
-
*/
|
|
140
|
-
export function useIsProviderAvailable(provider: FederatedProvider): boolean {
|
|
141
|
-
const { providers } = useAvailableProviders();
|
|
142
|
-
return providers.includes(provider);
|
|
143
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* useAvailableProviders Hook
|
|
3
|
+
*
|
|
4
|
+
* Returns the list of OAuth providers configured for this client.
|
|
5
|
+
* Ensures UI only shows buttons for providers that are enabled in IDP.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import { useState, useEffect } from 'react';
|
|
11
|
+
import type { FederatedProvider } from '../types/auth';
|
|
12
|
+
|
|
13
|
+
// Map provider IDs to our FederatedProvider type
|
|
14
|
+
const PROVIDER_MAP: Record<string, FederatedProvider> = {
|
|
15
|
+
'google': 'google',
|
|
16
|
+
'apple': 'apple',
|
|
17
|
+
'facebook': 'facebook',
|
|
18
|
+
'github': 'github',
|
|
19
|
+
'azure-ad': 'microsoft',
|
|
20
|
+
'microsoft-entra-id': 'microsoft',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Providers we support in UI (excludes credentials)
|
|
24
|
+
const OAUTH_PROVIDERS = ['google', 'apple', 'facebook', 'github', 'azure-ad', 'microsoft-entra-id'];
|
|
25
|
+
|
|
26
|
+
export interface UseAvailableProvidersResult {
|
|
27
|
+
providers: FederatedProvider[];
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
error: Error | null;
|
|
30
|
+
rawProviders: Record<string, any> | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hook to get available federated OAuth providers.
|
|
35
|
+
*
|
|
36
|
+
* Returns only the providers that are enabled in IDP config.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* function SignupPage() {
|
|
41
|
+
* const { providers, isLoading } = useAvailableProviders();
|
|
42
|
+
*
|
|
43
|
+
* if (isLoading) return <Spinner />;
|
|
44
|
+
*
|
|
45
|
+
* return (
|
|
46
|
+
* <FederatedAuthSection
|
|
47
|
+
* providers={providers}
|
|
48
|
+
* onProviderClick={handleClick}
|
|
49
|
+
* />
|
|
50
|
+
* );
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function useAvailableProviders(): UseAvailableProvidersResult {
|
|
55
|
+
const [providers, setProviders] = useState<FederatedProvider[]>([]);
|
|
56
|
+
const [rawProviders, setRawProviders] = useState<Record<string, any> | null>(null);
|
|
57
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
58
|
+
const [error, setError] = useState<Error | null>(null);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
let mounted = true;
|
|
62
|
+
|
|
63
|
+
async function fetchProviders() {
|
|
64
|
+
try {
|
|
65
|
+
// PAY-2104: THIS USED TO BE A HARDCODED GOOGLE LITERAL.
|
|
66
|
+
//
|
|
67
|
+
// const result = { google: { id: 'google', name: 'Google' } };
|
|
68
|
+
//
|
|
69
|
+
// under a comment claiming providers were "loaded dynamically". It
|
|
70
|
+
// returned Google for EVERY client whatever the tenant had enabled -
|
|
71
|
+
// a config-shaped constant, so Apple/Facebook (PAY-2104) could never
|
|
72
|
+
// appear and a client that had DISABLED Google would still see its
|
|
73
|
+
// button. The real answer is served by /api/auth/settings (the same
|
|
74
|
+
// route routes/auth/settings.ts builds from the IDP's oauthProviders).
|
|
75
|
+
const response = await fetch('/api/auth/settings', { cache: 'no-store' });
|
|
76
|
+
|
|
77
|
+
if (!mounted) return;
|
|
78
|
+
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
// FAIL CLOSED. An unreadable policy is not an empty provider list
|
|
81
|
+
// that we may then pad with a guess - it is "we do not know". See
|
|
82
|
+
// the PAY-1243 rule: never fabricate a provider list.
|
|
83
|
+
throw new Error(`Failed to fetch auth settings: ${response.status}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const body = await response.json().catch(() => null);
|
|
87
|
+
const settings = body?.data ?? body;
|
|
88
|
+
if (body?.success === false || !settings) {
|
|
89
|
+
throw new Error(body?.error?.code ?? 'POLICY_UNAVAILABLE');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Keep the raw provider rows (id -> the settings row) for callers that
|
|
93
|
+
// want more than the mapped union type, exactly as before.
|
|
94
|
+
const enabledIds: string[] = Array.isArray(settings.enabledProviders)
|
|
95
|
+
? settings.enabledProviders.filter((p: unknown): p is string => typeof p === 'string')
|
|
96
|
+
: [];
|
|
97
|
+
const raw: Record<string, { id: string; name: string }> = {};
|
|
98
|
+
for (const id of enabledIds) raw[id] = { id, name: id };
|
|
99
|
+
|
|
100
|
+
if (!mounted) return;
|
|
101
|
+
setRawProviders(raw);
|
|
102
|
+
|
|
103
|
+
// Filter to OAuth providers only (exclude credentials) and map to our
|
|
104
|
+
// FederatedProvider union, in the IDP's order.
|
|
105
|
+
const oauthProviders = enabledIds
|
|
106
|
+
.filter(id => OAUTH_PROVIDERS.includes(id))
|
|
107
|
+
.map(id => PROVIDER_MAP[id])
|
|
108
|
+
.filter((p): p is FederatedProvider => p !== undefined);
|
|
109
|
+
|
|
110
|
+
setProviders(oauthProviders);
|
|
111
|
+
setError(null);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (!mounted) return;
|
|
114
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
115
|
+
setProviders([]);
|
|
116
|
+
setRawProviders(null); // never leave a stale list behind a failure
|
|
117
|
+
} finally {
|
|
118
|
+
if (mounted) {
|
|
119
|
+
setIsLoading(false);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fetchProviders();
|
|
125
|
+
|
|
126
|
+
return () => {
|
|
127
|
+
mounted = false;
|
|
128
|
+
};
|
|
129
|
+
}, []);
|
|
130
|
+
|
|
131
|
+
return { providers, isLoading, error, rawProviders };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if a specific provider is available.
|
|
136
|
+
*
|
|
137
|
+
* @param provider The provider to check
|
|
138
|
+
* @returns boolean indicating if provider is configured
|
|
139
|
+
*/
|
|
140
|
+
export function useIsProviderAvailable(provider: FederatedProvider): boolean {
|
|
141
|
+
const { providers } = useAvailableProviders();
|
|
142
|
+
return providers.includes(provider);
|
|
143
|
+
}
|
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Password Validation Hook for @payez/next-mvp
|
|
3
|
-
*
|
|
4
|
-
* Provides real-time password validation against IDP password policy.
|
|
5
|
-
* Uses debounced API calls to validate password strength.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use client';
|
|
9
|
-
|
|
10
|
-
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
11
|
-
|
|
12
|
-
interface PasswordPolicy {
|
|
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
|
-
interface ValidationResult {
|
|
22
|
-
is_valid: boolean;
|
|
23
|
-
score: number;
|
|
24
|
-
failed_requirements: string[];
|
|
25
|
-
tip?: string;
|
|
26
|
-
policy?: PasswordPolicy;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface UsePasswordValidationOptions {
|
|
30
|
-
debounceMs?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function usePasswordValidation(options: UsePasswordValidationOptions = {}) {
|
|
34
|
-
const { debounceMs = 300 } = options;
|
|
35
|
-
|
|
36
|
-
const [isValid, setIsValid] = useState(false);
|
|
37
|
-
const [score, setScore] = useState(0);
|
|
38
|
-
const [failedRequirements, setFailedRequirements] = useState<string[]>([]);
|
|
39
|
-
const [tip, setTip] = useState<string | undefined>(undefined);
|
|
40
|
-
const [policy, setPolicy] = useState<PasswordPolicy | undefined>(undefined);
|
|
41
|
-
const [isValidating, setIsValidating] = useState(false);
|
|
42
|
-
|
|
43
|
-
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
44
|
-
const abortControllerRef = useRef<AbortController | null>(null);
|
|
45
|
-
|
|
46
|
-
const validatePassword = useCallback(async (password: string) => {
|
|
47
|
-
// Cancel any pending request
|
|
48
|
-
if (abortControllerRef.current) {
|
|
49
|
-
abortControllerRef.current.abort();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Don't validate empty passwords
|
|
53
|
-
if (!password || password.length === 0) {
|
|
54
|
-
setIsValid(false);
|
|
55
|
-
setScore(0);
|
|
56
|
-
setFailedRequirements([]);
|
|
57
|
-
setTip(undefined);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
setIsValidating(true);
|
|
62
|
-
abortControllerRef.current = new AbortController();
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const response = await fetch('/api/account/validate-password', {
|
|
66
|
-
method: 'POST',
|
|
67
|
-
headers: { 'Content-Type': 'application/json' },
|
|
68
|
-
body: JSON.stringify({ password }),
|
|
69
|
-
signal: abortControllerRef.current.signal,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const data: ValidationResult = await response.json();
|
|
73
|
-
|
|
74
|
-
setIsValid(data.is_valid);
|
|
75
|
-
setScore(data.score);
|
|
76
|
-
setFailedRequirements(data.failed_requirements || []);
|
|
77
|
-
setTip(data.tip);
|
|
78
|
-
if (data.policy) {
|
|
79
|
-
setPolicy(data.policy);
|
|
80
|
-
}
|
|
81
|
-
} catch (err: any) {
|
|
82
|
-
if (err.name !== 'AbortError') {
|
|
83
|
-
console.error('[usePasswordValidation] Error:', err);
|
|
84
|
-
// On error, assume invalid but don't block user
|
|
85
|
-
setIsValid(false);
|
|
86
|
-
setScore(0);
|
|
87
|
-
setFailedRequirements(['Unable to validate password']);
|
|
88
|
-
}
|
|
89
|
-
} finally {
|
|
90
|
-
setIsValidating(false);
|
|
91
|
-
}
|
|
92
|
-
}, []);
|
|
93
|
-
|
|
94
|
-
const setPassword = useCallback((password: string) => {
|
|
95
|
-
// Clear any pending debounce
|
|
96
|
-
if (debounceTimerRef.current) {
|
|
97
|
-
clearTimeout(debounceTimerRef.current);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Debounce the validation
|
|
101
|
-
debounceTimerRef.current = setTimeout(() => {
|
|
102
|
-
validatePassword(password);
|
|
103
|
-
}, debounceMs);
|
|
104
|
-
}, [debounceMs, validatePassword]);
|
|
105
|
-
|
|
106
|
-
// Cleanup on unmount
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
return () => {
|
|
109
|
-
if (debounceTimerRef.current) {
|
|
110
|
-
clearTimeout(debounceTimerRef.current);
|
|
111
|
-
}
|
|
112
|
-
if (abortControllerRef.current) {
|
|
113
|
-
abortControllerRef.current.abort();
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
}, []);
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
setPassword,
|
|
120
|
-
isValid,
|
|
121
|
-
score,
|
|
122
|
-
failedRequirements,
|
|
123
|
-
tip,
|
|
124
|
-
policy,
|
|
125
|
-
isValidating,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Password Validation Hook for @payez/next-mvp
|
|
3
|
+
*
|
|
4
|
+
* Provides real-time password validation against IDP password policy.
|
|
5
|
+
* Uses debounced API calls to validate password strength.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
11
|
+
|
|
12
|
+
interface PasswordPolicy {
|
|
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
|
+
interface ValidationResult {
|
|
22
|
+
is_valid: boolean;
|
|
23
|
+
score: number;
|
|
24
|
+
failed_requirements: string[];
|
|
25
|
+
tip?: string;
|
|
26
|
+
policy?: PasswordPolicy;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface UsePasswordValidationOptions {
|
|
30
|
+
debounceMs?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function usePasswordValidation(options: UsePasswordValidationOptions = {}) {
|
|
34
|
+
const { debounceMs = 300 } = options;
|
|
35
|
+
|
|
36
|
+
const [isValid, setIsValid] = useState(false);
|
|
37
|
+
const [score, setScore] = useState(0);
|
|
38
|
+
const [failedRequirements, setFailedRequirements] = useState<string[]>([]);
|
|
39
|
+
const [tip, setTip] = useState<string | undefined>(undefined);
|
|
40
|
+
const [policy, setPolicy] = useState<PasswordPolicy | undefined>(undefined);
|
|
41
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
42
|
+
|
|
43
|
+
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
44
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
45
|
+
|
|
46
|
+
const validatePassword = useCallback(async (password: string) => {
|
|
47
|
+
// Cancel any pending request
|
|
48
|
+
if (abortControllerRef.current) {
|
|
49
|
+
abortControllerRef.current.abort();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Don't validate empty passwords
|
|
53
|
+
if (!password || password.length === 0) {
|
|
54
|
+
setIsValid(false);
|
|
55
|
+
setScore(0);
|
|
56
|
+
setFailedRequirements([]);
|
|
57
|
+
setTip(undefined);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setIsValidating(true);
|
|
62
|
+
abortControllerRef.current = new AbortController();
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch('/api/account/validate-password', {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: { 'Content-Type': 'application/json' },
|
|
68
|
+
body: JSON.stringify({ password }),
|
|
69
|
+
signal: abortControllerRef.current.signal,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const data: ValidationResult = await response.json();
|
|
73
|
+
|
|
74
|
+
setIsValid(data.is_valid);
|
|
75
|
+
setScore(data.score);
|
|
76
|
+
setFailedRequirements(data.failed_requirements || []);
|
|
77
|
+
setTip(data.tip);
|
|
78
|
+
if (data.policy) {
|
|
79
|
+
setPolicy(data.policy);
|
|
80
|
+
}
|
|
81
|
+
} catch (err: any) {
|
|
82
|
+
if (err.name !== 'AbortError') {
|
|
83
|
+
console.error('[usePasswordValidation] Error:', err);
|
|
84
|
+
// On error, assume invalid but don't block user
|
|
85
|
+
setIsValid(false);
|
|
86
|
+
setScore(0);
|
|
87
|
+
setFailedRequirements(['Unable to validate password']);
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
setIsValidating(false);
|
|
91
|
+
}
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
const setPassword = useCallback((password: string) => {
|
|
95
|
+
// Clear any pending debounce
|
|
96
|
+
if (debounceTimerRef.current) {
|
|
97
|
+
clearTimeout(debounceTimerRef.current);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Debounce the validation
|
|
101
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
102
|
+
validatePassword(password);
|
|
103
|
+
}, debounceMs);
|
|
104
|
+
}, [debounceMs, validatePassword]);
|
|
105
|
+
|
|
106
|
+
// Cleanup on unmount
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
return () => {
|
|
109
|
+
if (debounceTimerRef.current) {
|
|
110
|
+
clearTimeout(debounceTimerRef.current);
|
|
111
|
+
}
|
|
112
|
+
if (abortControllerRef.current) {
|
|
113
|
+
abortControllerRef.current.abort();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
setPassword,
|
|
120
|
+
isValid,
|
|
121
|
+
score,
|
|
122
|
+
failedRequirements,
|
|
123
|
+
tip,
|
|
124
|
+
policy,
|
|
125
|
+
isValidating,
|
|
126
|
+
};
|
|
127
|
+
}
|