@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
package/src/lib/token-log-id.ts
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tokenLogId - a log-safe correlation id for a session token (PR 154 follow-up, BAPert ruling on PAY-1427:
|
|
3
|
-
* "no token material in logs, not even partial").
|
|
4
|
-
*
|
|
5
|
-
* The package used to log `sessionToken.substring(0, 8) + '...'` so lines about one session could be
|
|
6
|
-
* correlated. Those 8 characters ARE token material. This returns `h:` + the first 12 hex characters of
|
|
7
|
-
* SHA-256(token) instead: the same correlation (one token -> one stable id, across every log line and
|
|
8
|
-
* process), with nothing of the token itself.
|
|
9
|
-
*
|
|
10
|
-
* Synchronous and dependency-free on purpose: the call sites include Edge middleware
|
|
11
|
-
* (create-middleware, auth-decision) and browser code (authStore, SessionSync), where node:crypto is
|
|
12
|
-
* unavailable and Web Crypto's digest is async. SHA-256 below follows FIPS 180-4 and is verified
|
|
13
|
-
* against node:crypto in test/session-token-log-id.test.cjs.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const K = new Uint32Array([
|
|
17
|
-
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
18
|
-
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
19
|
-
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
20
|
-
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
21
|
-
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
22
|
-
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
23
|
-
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
24
|
-
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
const rotr = (x: number, n: number): number => (x >>> n) | (x << (32 - n));
|
|
28
|
-
|
|
29
|
-
/** SHA-256 of the UTF-8 encoding of `input`, as lowercase hex. */
|
|
30
|
-
export function sha256Hex(input: string): string {
|
|
31
|
-
const msg = new TextEncoder().encode(input);
|
|
32
|
-
const bitLen = msg.length * 8;
|
|
33
|
-
const padded = new Uint8Array(((msg.length + 9 + 63) >> 6) << 6);
|
|
34
|
-
padded.set(msg);
|
|
35
|
-
padded[msg.length] = 0x80;
|
|
36
|
-
const view = new DataView(padded.buffer);
|
|
37
|
-
view.setUint32(padded.length - 8, Math.floor(bitLen / 0x100000000));
|
|
38
|
-
view.setUint32(padded.length - 4, bitLen >>> 0);
|
|
39
|
-
|
|
40
|
-
const h = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
|
|
41
|
-
const w = new Uint32Array(64);
|
|
42
|
-
for (let off = 0; off < padded.length; off += 64) {
|
|
43
|
-
for (let i = 0; i < 16; i++) w[i] = view.getUint32(off + i * 4);
|
|
44
|
-
for (let i = 16; i < 64; i++) {
|
|
45
|
-
const s15 = w[i - 15];
|
|
46
|
-
const s2 = w[i - 2];
|
|
47
|
-
const sigma0 = rotr(s15, 7) ^ rotr(s15, 18) ^ (s15 >>> 3);
|
|
48
|
-
const sigma1 = rotr(s2, 17) ^ rotr(s2, 19) ^ (s2 >>> 10);
|
|
49
|
-
w[i] = (w[i - 16] + sigma0 + w[i - 7] + sigma1) | 0;
|
|
50
|
-
}
|
|
51
|
-
let a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7];
|
|
52
|
-
for (let i = 0; i < 64; i++) {
|
|
53
|
-
const t1 = (hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + K[i] + w[i]) | 0;
|
|
54
|
-
const t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) | 0;
|
|
55
|
-
hh = g; g = f; f = e; e = (d + t1) | 0; d = c; c = b; b = a; a = (t1 + t2) | 0;
|
|
56
|
-
}
|
|
57
|
-
h[0] = (h[0] + a) | 0; h[1] = (h[1] + b) | 0; h[2] = (h[2] + c) | 0; h[3] = (h[3] + d) | 0;
|
|
58
|
-
h[4] = (h[4] + e) | 0; h[5] = (h[5] + f) | 0; h[6] = (h[6] + g) | 0; h[7] = (h[7] + hh) | 0;
|
|
59
|
-
}
|
|
60
|
-
let out = '';
|
|
61
|
-
for (let i = 0; i < 8; i++) out += (h[i] >>> 0).toString(16).padStart(8, '0');
|
|
62
|
-
return out;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** `h:` + 12 hex of SHA-256(token); `none` when there is no token. Never any part of the token itself. */
|
|
66
|
-
export function tokenLogId(token: string | null | undefined): string {
|
|
67
|
-
if (typeof token !== 'string' || token.length === 0) return 'none';
|
|
68
|
-
return 'h:' + sha256Hex(token).slice(0, 12);
|
|
69
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* tokenLogId - a log-safe correlation id for a session token (PR 154 follow-up, BAPert ruling on PAY-1427:
|
|
3
|
+
* "no token material in logs, not even partial").
|
|
4
|
+
*
|
|
5
|
+
* The package used to log `sessionToken.substring(0, 8) + '...'` so lines about one session could be
|
|
6
|
+
* correlated. Those 8 characters ARE token material. This returns `h:` + the first 12 hex characters of
|
|
7
|
+
* SHA-256(token) instead: the same correlation (one token -> one stable id, across every log line and
|
|
8
|
+
* process), with nothing of the token itself.
|
|
9
|
+
*
|
|
10
|
+
* Synchronous and dependency-free on purpose: the call sites include Edge middleware
|
|
11
|
+
* (create-middleware, auth-decision) and browser code (authStore, SessionSync), where node:crypto is
|
|
12
|
+
* unavailable and Web Crypto's digest is async. SHA-256 below follows FIPS 180-4 and is verified
|
|
13
|
+
* against node:crypto in test/session-token-log-id.test.cjs.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const K = new Uint32Array([
|
|
17
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
18
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
19
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
20
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
21
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
22
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
23
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
24
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
const rotr = (x: number, n: number): number => (x >>> n) | (x << (32 - n));
|
|
28
|
+
|
|
29
|
+
/** SHA-256 of the UTF-8 encoding of `input`, as lowercase hex. */
|
|
30
|
+
export function sha256Hex(input: string): string {
|
|
31
|
+
const msg = new TextEncoder().encode(input);
|
|
32
|
+
const bitLen = msg.length * 8;
|
|
33
|
+
const padded = new Uint8Array(((msg.length + 9 + 63) >> 6) << 6);
|
|
34
|
+
padded.set(msg);
|
|
35
|
+
padded[msg.length] = 0x80;
|
|
36
|
+
const view = new DataView(padded.buffer);
|
|
37
|
+
view.setUint32(padded.length - 8, Math.floor(bitLen / 0x100000000));
|
|
38
|
+
view.setUint32(padded.length - 4, bitLen >>> 0);
|
|
39
|
+
|
|
40
|
+
const h = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
|
|
41
|
+
const w = new Uint32Array(64);
|
|
42
|
+
for (let off = 0; off < padded.length; off += 64) {
|
|
43
|
+
for (let i = 0; i < 16; i++) w[i] = view.getUint32(off + i * 4);
|
|
44
|
+
for (let i = 16; i < 64; i++) {
|
|
45
|
+
const s15 = w[i - 15];
|
|
46
|
+
const s2 = w[i - 2];
|
|
47
|
+
const sigma0 = rotr(s15, 7) ^ rotr(s15, 18) ^ (s15 >>> 3);
|
|
48
|
+
const sigma1 = rotr(s2, 17) ^ rotr(s2, 19) ^ (s2 >>> 10);
|
|
49
|
+
w[i] = (w[i - 16] + sigma0 + w[i - 7] + sigma1) | 0;
|
|
50
|
+
}
|
|
51
|
+
let a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7];
|
|
52
|
+
for (let i = 0; i < 64; i++) {
|
|
53
|
+
const t1 = (hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + K[i] + w[i]) | 0;
|
|
54
|
+
const t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) | 0;
|
|
55
|
+
hh = g; g = f; f = e; e = (d + t1) | 0; d = c; c = b; b = a; a = (t1 + t2) | 0;
|
|
56
|
+
}
|
|
57
|
+
h[0] = (h[0] + a) | 0; h[1] = (h[1] + b) | 0; h[2] = (h[2] + c) | 0; h[3] = (h[3] + d) | 0;
|
|
58
|
+
h[4] = (h[4] + e) | 0; h[5] = (h[5] + f) | 0; h[6] = (h[6] + g) | 0; h[7] = (h[7] + hh) | 0;
|
|
59
|
+
}
|
|
60
|
+
let out = '';
|
|
61
|
+
for (let i = 0; i < 8; i++) out += (h[i] >>> 0).toString(16).padStart(8, '0');
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** `h:` + 12 hex of SHA-256(token); `none` when there is no token. Never any part of the token itself. */
|
|
66
|
+
export function tokenLogId(token: string | null | undefined): string {
|
|
67
|
+
if (typeof token !== 'string' || token.length === 0) return 'none';
|
|
68
|
+
return 'h:' + sha256Hex(token).slice(0, 12);
|
|
69
|
+
}
|
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Door B (/api/auth/admin-login) twofa verify: what to tell the user when every lane refused.
|
|
3
|
-
*
|
|
4
|
-
* Classification is by the MOST BLOCKING outcome across lanes (268121 ruling), in this order:
|
|
5
|
-
* 429 from the IdP, or any lane answering code `locked` -> 429 lockout (PAY-903)
|
|
6
|
-
* any 5xx, or a lane answering code server_error / service_unavailable (PAY-1589) -> 502 "our side, not your code"
|
|
7
|
-
* otherwise -> 400 wrong / expired code
|
|
8
|
-
*
|
|
9
|
-
* PAY-903: MfaController.VerifyTotp returns 400 {message, code:"locked"} for a 15-minute lockout
|
|
10
|
-
* (MfaTotpService ErrorCode "locked"). Status-only classification read that as "wrong code, try again" -
|
|
11
|
-
* coaching a locked-out user to spend attempts. The body is inspected for EVERY lane, because with lane
|
|
12
|
-
* order [totp, email] the last response is the email lane's and never carries the TOTP lockout.
|
|
13
|
-
* Disclosing the lockout here is safe: this step is post-password (the caller already proved the account).
|
|
14
|
-
*
|
|
15
|
-
* PAY-1608: the lockout states the LOCKED LANE's own Retry-After (Core sends one on every 2FA lock; VerifyTotp sends
|
|
16
|
-
* what is LEFT of it). It used to hard-code 15 minutes / 900 s. With no usable header, no number is stated and
|
|
17
|
-
* `retryAfter` is omitted rather than invented.
|
|
18
|
-
*/
|
|
19
|
-
import { lockedSentence, parseRetryAfterSeconds } from './lock-window';
|
|
20
|
-
|
|
21
|
-
export interface TwofaLaneResult {
|
|
22
|
-
status: number;
|
|
23
|
-
body: string;
|
|
24
|
-
/** PAY-2284: which lane this answer came from, so a fault in the TOTP/SMS lane can offer the email lane (never offered when the EMAIL lane is the one that faulted). */
|
|
25
|
-
lane?: 'totp' | 'sms' | 'email';
|
|
26
|
-
/** PAY-1608: this lane's Retry-After header, as received. */
|
|
27
|
-
retryAfter?: string | null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface TwofaRejection {
|
|
31
|
-
status: 400 | 429 | 502;
|
|
32
|
-
error: string;
|
|
33
|
-
retryAfter?: number;
|
|
34
|
-
/**
|
|
35
|
-
* PAY-2284: true when the authenticator (or text) lane failed on OUR side and the emailed-code lane has not faulted. The sign-in
|
|
36
|
-
* card then shows a real "Email me a code instead" action. Before this, the copy said "use another sign-in method" and the screen
|
|
37
|
-
* offered none, and a TOTP holder - for whom the door sends NOTHING at sign-in - was stuck (Jon, 12 Oct, dev-93 key version 57).
|
|
38
|
-
*/
|
|
39
|
-
offerEmailCode?: true;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Every code the IdP body carries: flat {code}, envelope {error:{code}}, or {error:{details:{code}}}. */
|
|
43
|
-
function laneCodes(body: string): string[] {
|
|
44
|
-
try {
|
|
45
|
-
const j = JSON.parse(body) as Record<string, unknown> | null;
|
|
46
|
-
if (!j || typeof j !== 'object') return [];
|
|
47
|
-
const err = (j.error && typeof j.error === 'object' ? j.error : null) as Record<string, unknown> | null;
|
|
48
|
-
const details = (err?.details && typeof err.details === 'object' ? err.details : null) as Record<string, unknown> | null;
|
|
49
|
-
return [j.code, err?.code, details?.code].filter((c): c is string => typeof c === 'string').map((c) => c.toLowerCase());
|
|
50
|
-
} catch {
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function classifyTwofaRejection(lanes: TwofaLaneResult[], retryAfterHeader?: string | null): TwofaRejection {
|
|
56
|
-
const lockedLane = lanes.find((l) => laneCodes(l.body).includes('locked'));
|
|
57
|
-
if (lockedLane) {
|
|
58
|
-
const secs = parseRetryAfterSeconds(lockedLane.retryAfter);
|
|
59
|
-
return { status: 429, error: lockedSentence(secs), ...(secs ? { retryAfter: secs } : {}) };
|
|
60
|
-
}
|
|
61
|
-
if (lanes.some((l) => l.status === 429)) {
|
|
62
|
-
const parsed = retryAfterHeader ? parseInt(retryAfterHeader, 10) : NaN;
|
|
63
|
-
return {
|
|
64
|
-
status: 429,
|
|
65
|
-
error: 'Too many attempts. Your code is still valid - wait before trying again.',
|
|
66
|
-
retryAfter: Number.isFinite(parsed) && parsed > 0 ? parsed : 60,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
// PAY-1589: an IdP-side fault that arrives as 400 + code (TOTP secret cannot be decrypted -> server_error;
|
|
70
|
-
// replay store down -> service_unavailable). It is not the user's code and does not count toward the lockout,
|
|
71
|
-
// so calling it "not right" sends them round a loop that cannot succeed. Email may still work, so say so.
|
|
72
|
-
const faulted = (l: TwofaLaneResult) => laneCodes(l.body).some((c) => c === 'server_error' || c === 'service_unavailable');
|
|
73
|
-
if (lanes.some(faulted)) {
|
|
74
|
-
const emailLaneFaulted = lanes.some((l) => l.lane === 'email' && (faulted(l) || l.status >= 500));
|
|
75
|
-
const offer = !emailLaneFaulted && lanes.some((l) => faulted(l) && (l.lane === 'totp' || l.lane === 'sms'));
|
|
76
|
-
return {
|
|
77
|
-
status: 502,
|
|
78
|
-
error: offer
|
|
79
|
-
? 'We could not check your code just now - this is a problem on our side, not with your code. You can have us email you a code instead.'
|
|
80
|
-
: 'We could not check your code just now - this is a problem on our side, not with your code. Try again shortly.',
|
|
81
|
-
...(offer ? { offerEmailCode: true as const } : {}),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
if (lanes.some((l) => l.status >= 500)) {
|
|
85
|
-
return {
|
|
86
|
-
status: 502,
|
|
87
|
-
error: 'We could not check your code just now - this is a problem on our side, not with your code. Try again shortly.',
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
return { status: 400, error: 'That code is not right or has expired. Try again.' };
|
|
91
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Door B (/api/auth/admin-login) twofa verify: what to tell the user when every lane refused.
|
|
3
|
+
*
|
|
4
|
+
* Classification is by the MOST BLOCKING outcome across lanes (268121 ruling), in this order:
|
|
5
|
+
* 429 from the IdP, or any lane answering code `locked` -> 429 lockout (PAY-903)
|
|
6
|
+
* any 5xx, or a lane answering code server_error / service_unavailable (PAY-1589) -> 502 "our side, not your code"
|
|
7
|
+
* otherwise -> 400 wrong / expired code
|
|
8
|
+
*
|
|
9
|
+
* PAY-903: MfaController.VerifyTotp returns 400 {message, code:"locked"} for a 15-minute lockout
|
|
10
|
+
* (MfaTotpService ErrorCode "locked"). Status-only classification read that as "wrong code, try again" -
|
|
11
|
+
* coaching a locked-out user to spend attempts. The body is inspected for EVERY lane, because with lane
|
|
12
|
+
* order [totp, email] the last response is the email lane's and never carries the TOTP lockout.
|
|
13
|
+
* Disclosing the lockout here is safe: this step is post-password (the caller already proved the account).
|
|
14
|
+
*
|
|
15
|
+
* PAY-1608: the lockout states the LOCKED LANE's own Retry-After (Core sends one on every 2FA lock; VerifyTotp sends
|
|
16
|
+
* what is LEFT of it). It used to hard-code 15 minutes / 900 s. With no usable header, no number is stated and
|
|
17
|
+
* `retryAfter` is omitted rather than invented.
|
|
18
|
+
*/
|
|
19
|
+
import { lockedSentence, parseRetryAfterSeconds } from './lock-window';
|
|
20
|
+
|
|
21
|
+
export interface TwofaLaneResult {
|
|
22
|
+
status: number;
|
|
23
|
+
body: string;
|
|
24
|
+
/** PAY-2284: which lane this answer came from, so a fault in the TOTP/SMS lane can offer the email lane (never offered when the EMAIL lane is the one that faulted). */
|
|
25
|
+
lane?: 'totp' | 'sms' | 'email';
|
|
26
|
+
/** PAY-1608: this lane's Retry-After header, as received. */
|
|
27
|
+
retryAfter?: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TwofaRejection {
|
|
31
|
+
status: 400 | 429 | 502;
|
|
32
|
+
error: string;
|
|
33
|
+
retryAfter?: number;
|
|
34
|
+
/**
|
|
35
|
+
* PAY-2284: true when the authenticator (or text) lane failed on OUR side and the emailed-code lane has not faulted. The sign-in
|
|
36
|
+
* card then shows a real "Email me a code instead" action. Before this, the copy said "use another sign-in method" and the screen
|
|
37
|
+
* offered none, and a TOTP holder - for whom the door sends NOTHING at sign-in - was stuck (Jon, 12 Oct, dev-93 key version 57).
|
|
38
|
+
*/
|
|
39
|
+
offerEmailCode?: true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Every code the IdP body carries: flat {code}, envelope {error:{code}}, or {error:{details:{code}}}. */
|
|
43
|
+
function laneCodes(body: string): string[] {
|
|
44
|
+
try {
|
|
45
|
+
const j = JSON.parse(body) as Record<string, unknown> | null;
|
|
46
|
+
if (!j || typeof j !== 'object') return [];
|
|
47
|
+
const err = (j.error && typeof j.error === 'object' ? j.error : null) as Record<string, unknown> | null;
|
|
48
|
+
const details = (err?.details && typeof err.details === 'object' ? err.details : null) as Record<string, unknown> | null;
|
|
49
|
+
return [j.code, err?.code, details?.code].filter((c): c is string => typeof c === 'string').map((c) => c.toLowerCase());
|
|
50
|
+
} catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function classifyTwofaRejection(lanes: TwofaLaneResult[], retryAfterHeader?: string | null): TwofaRejection {
|
|
56
|
+
const lockedLane = lanes.find((l) => laneCodes(l.body).includes('locked'));
|
|
57
|
+
if (lockedLane) {
|
|
58
|
+
const secs = parseRetryAfterSeconds(lockedLane.retryAfter);
|
|
59
|
+
return { status: 429, error: lockedSentence(secs), ...(secs ? { retryAfter: secs } : {}) };
|
|
60
|
+
}
|
|
61
|
+
if (lanes.some((l) => l.status === 429)) {
|
|
62
|
+
const parsed = retryAfterHeader ? parseInt(retryAfterHeader, 10) : NaN;
|
|
63
|
+
return {
|
|
64
|
+
status: 429,
|
|
65
|
+
error: 'Too many attempts. Your code is still valid - wait before trying again.',
|
|
66
|
+
retryAfter: Number.isFinite(parsed) && parsed > 0 ? parsed : 60,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// PAY-1589: an IdP-side fault that arrives as 400 + code (TOTP secret cannot be decrypted -> server_error;
|
|
70
|
+
// replay store down -> service_unavailable). It is not the user's code and does not count toward the lockout,
|
|
71
|
+
// so calling it "not right" sends them round a loop that cannot succeed. Email may still work, so say so.
|
|
72
|
+
const faulted = (l: TwofaLaneResult) => laneCodes(l.body).some((c) => c === 'server_error' || c === 'service_unavailable');
|
|
73
|
+
if (lanes.some(faulted)) {
|
|
74
|
+
const emailLaneFaulted = lanes.some((l) => l.lane === 'email' && (faulted(l) || l.status >= 500));
|
|
75
|
+
const offer = !emailLaneFaulted && lanes.some((l) => faulted(l) && (l.lane === 'totp' || l.lane === 'sms'));
|
|
76
|
+
return {
|
|
77
|
+
status: 502,
|
|
78
|
+
error: offer
|
|
79
|
+
? 'We could not check your code just now - this is a problem on our side, not with your code. You can have us email you a code instead.'
|
|
80
|
+
: 'We could not check your code just now - this is a problem on our side, not with your code. Try again shortly.',
|
|
81
|
+
...(offer ? { offerEmailCode: true as const } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (lanes.some((l) => l.status >= 500)) {
|
|
85
|
+
return {
|
|
86
|
+
status: 502,
|
|
87
|
+
error: 'We could not check your code just now - this is a problem on our side, not with your code. Try again shortly.',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return { status: 400, error: 'That code is not right or has expired. Try again.' };
|
|
91
|
+
}
|