@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,181 +1,181 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public Auth Settings Route
|
|
3
|
-
*
|
|
4
|
-
* Returns client auth settings for pre-login pages (signup, login).
|
|
5
|
-
* Does NOT require authentication - these are public client settings.
|
|
6
|
-
*
|
|
7
|
-
* ── 257255: THIS ROUTE USED TO FABRICATE AN AUTHORIZATION POLICY ───────────
|
|
8
|
-
* It returned `success: true` with a hardcoded policy on ANY failure, so a
|
|
9
|
-
* caller could not distinguish "the IDP says X" from "I never reached the
|
|
10
|
-
* IDP". On 2026-08-24 it served require2FA=true / ["email","sms"] for hours
|
|
11
|
-
* while the database said require_2fa=false / ["emailcode","totp"] - wrong
|
|
12
|
-
* about WHETHER 2FA applied and WHICH factors existed, simultaneously and
|
|
13
|
-
* confidently. Six agents spent a night measuring policy with this instrument.
|
|
14
|
-
*
|
|
15
|
-
* Worse, the alarm it swallowed was CORRECT: idp-client-config throws
|
|
16
|
-
* specifically to stop an empty secret reaching betterAuth() and being
|
|
17
|
-
* silently replaced by the PUBLIC DEFAULT_SECRET. This route caught that and
|
|
18
|
-
* returned success.
|
|
19
|
-
*
|
|
20
|
-
* BAPert ruling v32: THE ENDPOINT HARD-FAILS, THE PAGE DEGRADES.
|
|
21
|
-
* QAPert's decisive measurement: the SAME client is require_2fa=TRUE on AKS
|
|
22
|
-
* and FALSE on dev-93, so ANY hardcoded fallback is provably wrong in one
|
|
23
|
-
* environment by construction. There is no safe default because the value is
|
|
24
|
-
* per-client per-environment DATA.
|
|
25
|
-
*
|
|
26
|
-
* The positive control already existed in this package: /api/session/viability
|
|
27
|
-
* surfaces this same fault honestly. These two routes now agree.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import { NextResponse } from 'next/server';
|
|
31
|
-
import { getIDPClientConfig } from '../../lib/idp-client-config';
|
|
32
|
-
import { secretSafeJson } from '../../lib/never-in-response';
|
|
33
|
-
|
|
34
|
-
export interface PublicAuthSettings {
|
|
35
|
-
// OAuth providers enabled for this client
|
|
36
|
-
enabledProviders: string[];
|
|
37
|
-
allowFederatedLogin: boolean;
|
|
38
|
-
|
|
39
|
-
// 2FA settings (public - no secrets)
|
|
40
|
-
require2FA: boolean;
|
|
41
|
-
allowed2FAMethods: string[];
|
|
42
|
-
|
|
43
|
-
// allowPublicRegistration / enablePasswordReset: OPTIONAL, never invented.
|
|
44
|
-
//
|
|
45
|
-
// 257255 removed these outright on the measurement that AuthSettings
|
|
46
|
-
// "carries no such fields, so they cannot be read today", and left one
|
|
47
|
-
// instruction: "Re-add them ONLY when the config document actually carries
|
|
48
|
-
// them." That precondition is now MET. The 240447 fix landed on master
|
|
49
|
-
// after 257255 was written and taught the loader to read them - AuthSettings
|
|
50
|
-
// now declares allowPublicRegistration?/enablePasswordReset? and
|
|
51
|
-
// idp-client-config maps them from auth_settings.allow_public_registration /
|
|
52
|
-
// enable_password_reset at BOTH construction sites. Re-adding them here is
|
|
53
|
-
// that instruction being followed, not overridden: deleting them now would
|
|
54
|
-
// discard a real measurement the loader already performs.
|
|
55
|
-
//
|
|
56
|
-
// They are OPTIONAL rather than defaulted, which is the part 240447 got
|
|
57
|
-
// wrong. Its `?? true` was defended as "preserves today's behaviour ...
|
|
58
|
-
// cannot regress an older IDP build", but `true` is a PERMISSION CLAIM about
|
|
59
|
-
// a tenant we did not successfully ask - the same defect class this card
|
|
60
|
-
// exists to kill, and the exact literal named in its title
|
|
61
|
-
// (allowPublicRegistration:TRUE). Absent means the IDP did not say; a
|
|
62
|
-
// consumer must then decide, and cannot mistake a guess for a policy.
|
|
63
|
-
//
|
|
64
|
-
// They are NOT validated into a hard 503 like require2FA below, because an
|
|
65
|
-
// older IDP build may legitimately omit them. Absent-and-honest is correct
|
|
66
|
-
// there; absent-and-fabricated is not.
|
|
67
|
-
allowPublicRegistration?: boolean;
|
|
68
|
-
enablePasswordReset?: boolean;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Stable, machine-readable failure codes. Consumers branch on these. */
|
|
72
|
-
export type AuthSettingsErrorCode = 'IDP_UNREACHABLE' | 'IDP_CONFIG_INVALID';
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Fail closed and LOUD. 503 + success:false + a code, and CRUCIALLY no policy
|
|
76
|
-
* fields at all - not defaults, not partials. A consumer cannot mistake an
|
|
77
|
-
* absent body for an answer, which is exactly the mistake the old shape invited.
|
|
78
|
-
*/
|
|
79
|
-
function policyUnavailable(code: AuthSettingsErrorCode, message: string) {
|
|
80
|
-
return NextResponse.json(
|
|
81
|
-
{ success: false, error: { code, message } },
|
|
82
|
-
{ status: 503, headers: { 'Cache-Control': 'no-store' } },
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* GET /api/auth/settings
|
|
88
|
-
*
|
|
89
|
-
* Returns public auth settings for the current client, or 503 if the real
|
|
90
|
-
* policy could not be read. It never answers from a default.
|
|
91
|
-
*/
|
|
92
|
-
export async function GET() {
|
|
93
|
-
let config;
|
|
94
|
-
try {
|
|
95
|
-
config = await getIDPClientConfig();
|
|
96
|
-
} catch (error) {
|
|
97
|
-
// Keep the loud line - it is what finally unmasked this in production.
|
|
98
|
-
console.error('[AUTH_SETTINGS] Failed to get settings:', error);
|
|
99
|
-
// The `degraded: true` shape that used to live here (240447) is gone
|
|
100
|
-
// deliberately. It still answered `success: true` with a full invented
|
|
101
|
-
// policy and merely LABELLED the guess - which BAPert v32 rules out:
|
|
102
|
-
// THE ENDPOINT HARD-FAILS, THE PAGE DEGRADES. A flag a consumer may
|
|
103
|
-
// ignore is not a failure; 503 with no policy fields is.
|
|
104
|
-
return policyUnavailable(
|
|
105
|
-
'IDP_UNREACHABLE',
|
|
106
|
-
'Could not read client configuration from the identity service.',
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ── THE CONDITION IS "DID I READ A VALIDATED POLICY", NOT "DID THE FETCH
|
|
111
|
-
// THROW". This distinction is load-bearing and is why the catch above is
|
|
112
|
-
// not sufficient on its own (QAPert, and card 257661).
|
|
113
|
-
//
|
|
114
|
-
// getIDPClientConfig caches BEFORE it validates, and the cache-hit paths
|
|
115
|
-
// do NOT re-validate. So it can RETURN NORMALLY - no throw, no log line -
|
|
116
|
-
// with a config that FAILED validation on the request that cached it.
|
|
117
|
-
// Measured on :4702 on 2026-08-24: the loader was throwing 53 times per
|
|
118
|
-
// 400 log lines while THIS endpoint served a well-formed policy from
|
|
119
|
-
// cache. A fix that only handles the throw is bypassed by the cache on
|
|
120
|
-
// every request after the first.
|
|
121
|
-
//
|
|
122
|
-
// authSecret is the invariant the loader itself enforces on the fetch
|
|
123
|
-
// path (idp-client-config: "LOAD-BEARING - empty must fail here, never
|
|
124
|
-
// downstream"). Re-checking it here is what makes a cache hit as safe as
|
|
125
|
-
// a fresh read.
|
|
126
|
-
if (!config?.authSecret) {
|
|
127
|
-
console.error(
|
|
128
|
-
'[AUTH_SETTINGS] Refusing to serve policy: config has no authSecret ' +
|
|
129
|
-
'(likely served from cache before validation - see card 257661).',
|
|
130
|
-
);
|
|
131
|
-
return policyUnavailable(
|
|
132
|
-
'IDP_CONFIG_INVALID',
|
|
133
|
-
'Client configuration is incomplete; refusing to report a policy.',
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// No `??` defaults. If the policy section is absent or malformed we did not
|
|
138
|
-
// read a policy, and inventing one is the defect this card exists for.
|
|
139
|
-
const authSettings = config.authSettings;
|
|
140
|
-
if (
|
|
141
|
-
!authSettings ||
|
|
142
|
-
typeof authSettings.require2FA !== 'boolean' ||
|
|
143
|
-
!Array.isArray(authSettings.allowed2FAMethods)
|
|
144
|
-
) {
|
|
145
|
-
console.error(
|
|
146
|
-
'[AUTH_SETTINGS] Refusing to serve policy: authSettings absent or malformed.',
|
|
147
|
-
);
|
|
148
|
-
return policyUnavailable(
|
|
149
|
-
'IDP_CONFIG_INVALID',
|
|
150
|
-
'Client configuration carried no usable auth policy.',
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const settings: PublicAuthSettings = {
|
|
155
|
-
enabledProviders: config.oauthProviders?.filter(p => p.enabled).map(p => p.provider) ?? [],
|
|
156
|
-
|
|
157
|
-
// Prefer the EXPLICIT policy field over inferring from the provider
|
|
158
|
-
// list (240447). Inferring is only a proxy: a tenant can have federated
|
|
159
|
-
// login switched off while enabled providers are still configured, and
|
|
160
|
-
// the inference reports `true` for it. Fall back to the inference only
|
|
161
|
-
// where the IDP does not state the field.
|
|
162
|
-
allowFederatedLogin:
|
|
163
|
-
authSettings.allowSocialLogin ??
|
|
164
|
-
(config.oauthProviders?.some(p => p.enabled) ?? false),
|
|
165
|
-
|
|
166
|
-
// Real reads, no `??` literal. Absent stays absent (see interface).
|
|
167
|
-
allowPublicRegistration: authSettings.allowPublicRegistration,
|
|
168
|
-
enablePasswordReset: authSettings.enablePasswordReset,
|
|
169
|
-
|
|
170
|
-
require2FA: authSettings.require2FA,
|
|
171
|
-
allowed2FAMethods: authSettings.allowed2FAMethods,
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// PAY-1529: the public policy is built from an allowlist above; secretSafeJson is the runtime backstop
|
|
175
|
-
// that answers 500 instead of serving it if a registered secret ever reaches this body anyway.
|
|
176
|
-
return secretSafeJson(
|
|
177
|
-
{ success: true, data: settings },
|
|
178
|
-
{ headers: { 'Cache-Control': 'no-store' } },
|
|
179
|
-
'GET /api/auth/settings',
|
|
180
|
-
);
|
|
181
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Public Auth Settings Route
|
|
3
|
+
*
|
|
4
|
+
* Returns client auth settings for pre-login pages (signup, login).
|
|
5
|
+
* Does NOT require authentication - these are public client settings.
|
|
6
|
+
*
|
|
7
|
+
* ── 257255: THIS ROUTE USED TO FABRICATE AN AUTHORIZATION POLICY ───────────
|
|
8
|
+
* It returned `success: true` with a hardcoded policy on ANY failure, so a
|
|
9
|
+
* caller could not distinguish "the IDP says X" from "I never reached the
|
|
10
|
+
* IDP". On 2026-08-24 it served require2FA=true / ["email","sms"] for hours
|
|
11
|
+
* while the database said require_2fa=false / ["emailcode","totp"] - wrong
|
|
12
|
+
* about WHETHER 2FA applied and WHICH factors existed, simultaneously and
|
|
13
|
+
* confidently. Six agents spent a night measuring policy with this instrument.
|
|
14
|
+
*
|
|
15
|
+
* Worse, the alarm it swallowed was CORRECT: idp-client-config throws
|
|
16
|
+
* specifically to stop an empty secret reaching betterAuth() and being
|
|
17
|
+
* silently replaced by the PUBLIC DEFAULT_SECRET. This route caught that and
|
|
18
|
+
* returned success.
|
|
19
|
+
*
|
|
20
|
+
* BAPert ruling v32: THE ENDPOINT HARD-FAILS, THE PAGE DEGRADES.
|
|
21
|
+
* QAPert's decisive measurement: the SAME client is require_2fa=TRUE on AKS
|
|
22
|
+
* and FALSE on dev-93, so ANY hardcoded fallback is provably wrong in one
|
|
23
|
+
* environment by construction. There is no safe default because the value is
|
|
24
|
+
* per-client per-environment DATA.
|
|
25
|
+
*
|
|
26
|
+
* The positive control already existed in this package: /api/session/viability
|
|
27
|
+
* surfaces this same fault honestly. These two routes now agree.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { NextResponse } from 'next/server';
|
|
31
|
+
import { getIDPClientConfig } from '../../lib/idp-client-config';
|
|
32
|
+
import { secretSafeJson } from '../../lib/never-in-response';
|
|
33
|
+
|
|
34
|
+
export interface PublicAuthSettings {
|
|
35
|
+
// OAuth providers enabled for this client
|
|
36
|
+
enabledProviders: string[];
|
|
37
|
+
allowFederatedLogin: boolean;
|
|
38
|
+
|
|
39
|
+
// 2FA settings (public - no secrets)
|
|
40
|
+
require2FA: boolean;
|
|
41
|
+
allowed2FAMethods: string[];
|
|
42
|
+
|
|
43
|
+
// allowPublicRegistration / enablePasswordReset: OPTIONAL, never invented.
|
|
44
|
+
//
|
|
45
|
+
// 257255 removed these outright on the measurement that AuthSettings
|
|
46
|
+
// "carries no such fields, so they cannot be read today", and left one
|
|
47
|
+
// instruction: "Re-add them ONLY when the config document actually carries
|
|
48
|
+
// them." That precondition is now MET. The 240447 fix landed on master
|
|
49
|
+
// after 257255 was written and taught the loader to read them - AuthSettings
|
|
50
|
+
// now declares allowPublicRegistration?/enablePasswordReset? and
|
|
51
|
+
// idp-client-config maps them from auth_settings.allow_public_registration /
|
|
52
|
+
// enable_password_reset at BOTH construction sites. Re-adding them here is
|
|
53
|
+
// that instruction being followed, not overridden: deleting them now would
|
|
54
|
+
// discard a real measurement the loader already performs.
|
|
55
|
+
//
|
|
56
|
+
// They are OPTIONAL rather than defaulted, which is the part 240447 got
|
|
57
|
+
// wrong. Its `?? true` was defended as "preserves today's behaviour ...
|
|
58
|
+
// cannot regress an older IDP build", but `true` is a PERMISSION CLAIM about
|
|
59
|
+
// a tenant we did not successfully ask - the same defect class this card
|
|
60
|
+
// exists to kill, and the exact literal named in its title
|
|
61
|
+
// (allowPublicRegistration:TRUE). Absent means the IDP did not say; a
|
|
62
|
+
// consumer must then decide, and cannot mistake a guess for a policy.
|
|
63
|
+
//
|
|
64
|
+
// They are NOT validated into a hard 503 like require2FA below, because an
|
|
65
|
+
// older IDP build may legitimately omit them. Absent-and-honest is correct
|
|
66
|
+
// there; absent-and-fabricated is not.
|
|
67
|
+
allowPublicRegistration?: boolean;
|
|
68
|
+
enablePasswordReset?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Stable, machine-readable failure codes. Consumers branch on these. */
|
|
72
|
+
export type AuthSettingsErrorCode = 'IDP_UNREACHABLE' | 'IDP_CONFIG_INVALID';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fail closed and LOUD. 503 + success:false + a code, and CRUCIALLY no policy
|
|
76
|
+
* fields at all - not defaults, not partials. A consumer cannot mistake an
|
|
77
|
+
* absent body for an answer, which is exactly the mistake the old shape invited.
|
|
78
|
+
*/
|
|
79
|
+
function policyUnavailable(code: AuthSettingsErrorCode, message: string) {
|
|
80
|
+
return NextResponse.json(
|
|
81
|
+
{ success: false, error: { code, message } },
|
|
82
|
+
{ status: 503, headers: { 'Cache-Control': 'no-store' } },
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* GET /api/auth/settings
|
|
88
|
+
*
|
|
89
|
+
* Returns public auth settings for the current client, or 503 if the real
|
|
90
|
+
* policy could not be read. It never answers from a default.
|
|
91
|
+
*/
|
|
92
|
+
export async function GET() {
|
|
93
|
+
let config;
|
|
94
|
+
try {
|
|
95
|
+
config = await getIDPClientConfig();
|
|
96
|
+
} catch (error) {
|
|
97
|
+
// Keep the loud line - it is what finally unmasked this in production.
|
|
98
|
+
console.error('[AUTH_SETTINGS] Failed to get settings:', error);
|
|
99
|
+
// The `degraded: true` shape that used to live here (240447) is gone
|
|
100
|
+
// deliberately. It still answered `success: true` with a full invented
|
|
101
|
+
// policy and merely LABELLED the guess - which BAPert v32 rules out:
|
|
102
|
+
// THE ENDPOINT HARD-FAILS, THE PAGE DEGRADES. A flag a consumer may
|
|
103
|
+
// ignore is not a failure; 503 with no policy fields is.
|
|
104
|
+
return policyUnavailable(
|
|
105
|
+
'IDP_UNREACHABLE',
|
|
106
|
+
'Could not read client configuration from the identity service.',
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── THE CONDITION IS "DID I READ A VALIDATED POLICY", NOT "DID THE FETCH
|
|
111
|
+
// THROW". This distinction is load-bearing and is why the catch above is
|
|
112
|
+
// not sufficient on its own (QAPert, and card 257661).
|
|
113
|
+
//
|
|
114
|
+
// getIDPClientConfig caches BEFORE it validates, and the cache-hit paths
|
|
115
|
+
// do NOT re-validate. So it can RETURN NORMALLY - no throw, no log line -
|
|
116
|
+
// with a config that FAILED validation on the request that cached it.
|
|
117
|
+
// Measured on :4702 on 2026-08-24: the loader was throwing 53 times per
|
|
118
|
+
// 400 log lines while THIS endpoint served a well-formed policy from
|
|
119
|
+
// cache. A fix that only handles the throw is bypassed by the cache on
|
|
120
|
+
// every request after the first.
|
|
121
|
+
//
|
|
122
|
+
// authSecret is the invariant the loader itself enforces on the fetch
|
|
123
|
+
// path (idp-client-config: "LOAD-BEARING - empty must fail here, never
|
|
124
|
+
// downstream"). Re-checking it here is what makes a cache hit as safe as
|
|
125
|
+
// a fresh read.
|
|
126
|
+
if (!config?.authSecret) {
|
|
127
|
+
console.error(
|
|
128
|
+
'[AUTH_SETTINGS] Refusing to serve policy: config has no authSecret ' +
|
|
129
|
+
'(likely served from cache before validation - see card 257661).',
|
|
130
|
+
);
|
|
131
|
+
return policyUnavailable(
|
|
132
|
+
'IDP_CONFIG_INVALID',
|
|
133
|
+
'Client configuration is incomplete; refusing to report a policy.',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// No `??` defaults. If the policy section is absent or malformed we did not
|
|
138
|
+
// read a policy, and inventing one is the defect this card exists for.
|
|
139
|
+
const authSettings = config.authSettings;
|
|
140
|
+
if (
|
|
141
|
+
!authSettings ||
|
|
142
|
+
typeof authSettings.require2FA !== 'boolean' ||
|
|
143
|
+
!Array.isArray(authSettings.allowed2FAMethods)
|
|
144
|
+
) {
|
|
145
|
+
console.error(
|
|
146
|
+
'[AUTH_SETTINGS] Refusing to serve policy: authSettings absent or malformed.',
|
|
147
|
+
);
|
|
148
|
+
return policyUnavailable(
|
|
149
|
+
'IDP_CONFIG_INVALID',
|
|
150
|
+
'Client configuration carried no usable auth policy.',
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const settings: PublicAuthSettings = {
|
|
155
|
+
enabledProviders: config.oauthProviders?.filter(p => p.enabled).map(p => p.provider) ?? [],
|
|
156
|
+
|
|
157
|
+
// Prefer the EXPLICIT policy field over inferring from the provider
|
|
158
|
+
// list (240447). Inferring is only a proxy: a tenant can have federated
|
|
159
|
+
// login switched off while enabled providers are still configured, and
|
|
160
|
+
// the inference reports `true` for it. Fall back to the inference only
|
|
161
|
+
// where the IDP does not state the field.
|
|
162
|
+
allowFederatedLogin:
|
|
163
|
+
authSettings.allowSocialLogin ??
|
|
164
|
+
(config.oauthProviders?.some(p => p.enabled) ?? false),
|
|
165
|
+
|
|
166
|
+
// Real reads, no `??` literal. Absent stays absent (see interface).
|
|
167
|
+
allowPublicRegistration: authSettings.allowPublicRegistration,
|
|
168
|
+
enablePasswordReset: authSettings.enablePasswordReset,
|
|
169
|
+
|
|
170
|
+
require2FA: authSettings.require2FA,
|
|
171
|
+
allowed2FAMethods: authSettings.allowed2FAMethods,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// PAY-1529: the public policy is built from an allowlist above; secretSafeJson is the runtime backstop
|
|
175
|
+
// that answers 500 instead of serving it if a registered secret ever reaches this body anyway.
|
|
176
|
+
return secretSafeJson(
|
|
177
|
+
{ success: true, data: settings },
|
|
178
|
+
{ headers: { 'Cache-Control': 'no-store' } },
|
|
179
|
+
'GET /api/auth/settings',
|
|
180
|
+
);
|
|
181
|
+
}
|