@payez/next-mvp 4.8.1 → 4.8.3
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/api-handlers/admin/audit.js +13 -21
- package/dist/api-handlers/admin/sessions.js +59 -45
- package/dist/api-handlers/admin/stats.js +1 -1
- package/dist/api-handlers/admin/users.js +15 -23
- package/dist/api-handlers/admin/vibe-data.d.ts +5 -0
- package/dist/api-handlers/admin/vibe-data.js +5 -0
- 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/client.d.ts +1 -1
- package/dist/vibe/client.js +2 -1
- package/dist/vibe/filters.d.ts +36 -0
- package/dist/vibe/filters.js +47 -0
- package/dist/vibe/generic.js +2 -1
- 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 +223 -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 +341 -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 +240 -251
- package/src/api-handlers/admin/vibe-data.ts +338 -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 +878 -877
- package/src/vibe/enterprise-auth.ts +208 -208
- package/src/vibe/errors.ts +209 -209
- package/src/vibe/filters.ts +52 -0
- package/src/vibe/generic.ts +340 -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/redis.ts
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
// E:\Repos\PayEz-Next-MVP\packages\next-mvp\src\lib\redis.ts
|
|
2
|
-
import Redis, { RedisOptions } from 'ioredis';
|
|
3
|
-
|
|
4
|
-
let client: Redis | null = null;
|
|
5
|
-
|
|
6
|
-
// ── 183496: fail FAST and NAMED, never a 20s spinner ───────────────────────
|
|
7
|
-
// The DEFAULTS were the defect (Nextpert-Scout's killable-proxy measurement,
|
|
8
|
-
// BAPert 22626): no commandTimeout, so a command against a cut Redis path
|
|
9
|
-
// hung 20s+, and maxRetriesPerRequest defaults to 20, so every command was
|
|
10
|
-
// retried through every reconnect attempt. In the room that reads as "the
|
|
11
|
-
// app hung", not "Redis blipped" - and self-heal was instant all along, so
|
|
12
|
-
// the failure MODE was the whole defect.
|
|
13
|
-
//
|
|
14
|
-
// commandTimeout covers OFFLINE-QUEUED commands too (ioredis 5.8.2 Redis.js:
|
|
15
|
-
// command.setTimeout runs BEFORE the queue/writable check), which makes it
|
|
16
|
-
// the bounded offline queue the card asks for: no command, queued or sent,
|
|
17
|
-
// outlives 3s. The queue itself stays ENABLED - a transient blip still
|
|
18
|
-
// self-heals inside the window; only the failure mode changes. The throw
|
|
19
|
-
// lands in with-auth's catch and comes out a fast 5xx, not a spinner.
|
|
20
|
-
const REDIS_OPTIONS: RedisOptions = {
|
|
21
|
-
commandTimeout: 3000,
|
|
22
|
-
maxRetriesPerRequest: 1,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
function createClient(): Redis {
|
|
26
|
-
const url = process.env.REDIS_URL;
|
|
27
|
-
|
|
28
|
-
// ── THE COMMENT THAT USED TO BE HERE WAS FALSE (Nextpert-Scout 35159). ──
|
|
29
|
-
//
|
|
30
|
-
// It said: "No REDIS_URL set, create a client that will fail fast."
|
|
31
|
-
// IT DOES NOT FAIL. Measured, not read:
|
|
32
|
-
//
|
|
33
|
-
// new Redis({ commandTimeout, maxRetriesPerRequest, lazyConnect: true })
|
|
34
|
-
// -> options.host === 'localhost', options.port === 6379
|
|
35
|
-
// (ioredis DEFAULT_REDIS_OPTIONS: port 6379, host "localhost")
|
|
36
|
-
//
|
|
37
|
-
// `lazyConnect` only DEFERS the connection to the first command; it does not
|
|
38
|
-
// prevent one. So with REDIS_URL absent this client silently targets
|
|
39
|
-
// localhost:6379. Whether that is a loud failure or a SILENT CONNECTION TO
|
|
40
|
-
// THE WRONG STORE depends entirely on what happens to be listening on the
|
|
41
|
-
// local loopback - which is not a property this module controls, and is
|
|
42
|
-
// exactly the kind of thing that differs between a container and a dev box.
|
|
43
|
-
//
|
|
44
|
-
// This mattered operationally: I told the team that deleting REDIS_URL from a
|
|
45
|
-
// configmap would be fail-closed, and recommended it as a hardening step for
|
|
46
|
-
// the 249808 remediation. That advice was wrong and rested on this comment.
|
|
47
|
-
// A loopback-exempt firewall rule also does not stop a client that has
|
|
48
|
-
// fallen back to loopback.
|
|
49
|
-
//
|
|
50
|
-
// Corrected rather than deleted, because the comment's INTENT was right and
|
|
51
|
-
// is worth preserving as a stated goal: an absent REDIS_URL SHOULD fail
|
|
52
|
-
// loudly. Making it actually do so means changing behaviour at module load
|
|
53
|
-
// (`const redis = getRedis()` at the bottom of this file runs on import), so
|
|
54
|
-
// it is carded rather than changed at speed - throwing on import would break
|
|
55
|
-
// every consumer that imports this module without needing Redis.
|
|
56
|
-
//
|
|
57
|
-
// What IS done here: the misconfiguration is now ANNOUNCED, so it can never
|
|
58
|
-
// again be silent even though it is not yet fatal.
|
|
59
|
-
if (!url || url.trim() === '') {
|
|
60
|
-
console.error(
|
|
61
|
-
'[redis] MISCONFIGURED: REDIS_URL is unset or empty. ' +
|
|
62
|
-
'This client will fall back to ioredis defaults (localhost:6379) and may ' +
|
|
63
|
-
'connect to whatever is listening there, or fail on first command. ' +
|
|
64
|
-
'It does NOT fail fast. Set REDIS_URL explicitly.',
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const instance = url && url.trim() !== ''
|
|
69
|
-
// Use a standard configuration for better Docker compatibility
|
|
70
|
-
? new Redis(url, REDIS_OPTIONS)
|
|
71
|
-
// NOTE: this does NOT fail fast - see the block above. It targets
|
|
72
|
-
// localhost:6379 by ioredis default.
|
|
73
|
-
: new Redis({ ...REDIS_OPTIONS, lazyConnect: true });
|
|
74
|
-
|
|
75
|
-
// Loud AND load-bearing: an ioredis 'error' event with NO listener is an
|
|
76
|
-
// unhandled EventEmitter error, which can take the whole PROCESS down -
|
|
77
|
-
// a Redis blip becoming a dead server is worse than the spinner was.
|
|
78
|
-
instance.on('error', (err) => {
|
|
79
|
-
console.error(`[redis] PATH FAILURE ${new Date().toISOString()}: ${err instanceof Error ? err.message : String(err)}`);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// 185121 line 3: the 16:05 event left no named trigger because ONLY 'error'
|
|
83
|
-
// was listened for. A reconnect or a clean end is silent in the artifact,
|
|
84
|
-
// so a session-death window could not be correlated with a redis event.
|
|
85
|
-
// One line per event; a recurrence now says whether redis moved at all.
|
|
86
|
-
instance.on('reconnecting', (delayMs: number) => {
|
|
87
|
-
console.warn(`[redis] RECONNECTING ${new Date().toISOString()}: next attempt in ${delayMs}ms`);
|
|
88
|
-
});
|
|
89
|
-
instance.on('end', () => {
|
|
90
|
-
console.error(`[redis] CONNECTION ENDED ${new Date().toISOString()}: no further reconnects — every session read now fails or misses`);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
return instance;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function getRedis(): Redis {
|
|
97
|
-
if (!client) {
|
|
98
|
-
client = createClient();
|
|
99
|
-
}
|
|
100
|
-
return client;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const redis = getRedis();
|
|
104
|
-
export { redis };
|
|
105
|
-
export default redis;
|
|
1
|
+
// E:\Repos\PayEz-Next-MVP\packages\next-mvp\src\lib\redis.ts
|
|
2
|
+
import Redis, { RedisOptions } from 'ioredis';
|
|
3
|
+
|
|
4
|
+
let client: Redis | null = null;
|
|
5
|
+
|
|
6
|
+
// ── 183496: fail FAST and NAMED, never a 20s spinner ───────────────────────
|
|
7
|
+
// The DEFAULTS were the defect (Nextpert-Scout's killable-proxy measurement,
|
|
8
|
+
// BAPert 22626): no commandTimeout, so a command against a cut Redis path
|
|
9
|
+
// hung 20s+, and maxRetriesPerRequest defaults to 20, so every command was
|
|
10
|
+
// retried through every reconnect attempt. In the room that reads as "the
|
|
11
|
+
// app hung", not "Redis blipped" - and self-heal was instant all along, so
|
|
12
|
+
// the failure MODE was the whole defect.
|
|
13
|
+
//
|
|
14
|
+
// commandTimeout covers OFFLINE-QUEUED commands too (ioredis 5.8.2 Redis.js:
|
|
15
|
+
// command.setTimeout runs BEFORE the queue/writable check), which makes it
|
|
16
|
+
// the bounded offline queue the card asks for: no command, queued or sent,
|
|
17
|
+
// outlives 3s. The queue itself stays ENABLED - a transient blip still
|
|
18
|
+
// self-heals inside the window; only the failure mode changes. The throw
|
|
19
|
+
// lands in with-auth's catch and comes out a fast 5xx, not a spinner.
|
|
20
|
+
const REDIS_OPTIONS: RedisOptions = {
|
|
21
|
+
commandTimeout: 3000,
|
|
22
|
+
maxRetriesPerRequest: 1,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function createClient(): Redis {
|
|
26
|
+
const url = process.env.REDIS_URL;
|
|
27
|
+
|
|
28
|
+
// ── THE COMMENT THAT USED TO BE HERE WAS FALSE (Nextpert-Scout 35159). ──
|
|
29
|
+
//
|
|
30
|
+
// It said: "No REDIS_URL set, create a client that will fail fast."
|
|
31
|
+
// IT DOES NOT FAIL. Measured, not read:
|
|
32
|
+
//
|
|
33
|
+
// new Redis({ commandTimeout, maxRetriesPerRequest, lazyConnect: true })
|
|
34
|
+
// -> options.host === 'localhost', options.port === 6379
|
|
35
|
+
// (ioredis DEFAULT_REDIS_OPTIONS: port 6379, host "localhost")
|
|
36
|
+
//
|
|
37
|
+
// `lazyConnect` only DEFERS the connection to the first command; it does not
|
|
38
|
+
// prevent one. So with REDIS_URL absent this client silently targets
|
|
39
|
+
// localhost:6379. Whether that is a loud failure or a SILENT CONNECTION TO
|
|
40
|
+
// THE WRONG STORE depends entirely on what happens to be listening on the
|
|
41
|
+
// local loopback - which is not a property this module controls, and is
|
|
42
|
+
// exactly the kind of thing that differs between a container and a dev box.
|
|
43
|
+
//
|
|
44
|
+
// This mattered operationally: I told the team that deleting REDIS_URL from a
|
|
45
|
+
// configmap would be fail-closed, and recommended it as a hardening step for
|
|
46
|
+
// the 249808 remediation. That advice was wrong and rested on this comment.
|
|
47
|
+
// A loopback-exempt firewall rule also does not stop a client that has
|
|
48
|
+
// fallen back to loopback.
|
|
49
|
+
//
|
|
50
|
+
// Corrected rather than deleted, because the comment's INTENT was right and
|
|
51
|
+
// is worth preserving as a stated goal: an absent REDIS_URL SHOULD fail
|
|
52
|
+
// loudly. Making it actually do so means changing behaviour at module load
|
|
53
|
+
// (`const redis = getRedis()` at the bottom of this file runs on import), so
|
|
54
|
+
// it is carded rather than changed at speed - throwing on import would break
|
|
55
|
+
// every consumer that imports this module without needing Redis.
|
|
56
|
+
//
|
|
57
|
+
// What IS done here: the misconfiguration is now ANNOUNCED, so it can never
|
|
58
|
+
// again be silent even though it is not yet fatal.
|
|
59
|
+
if (!url || url.trim() === '') {
|
|
60
|
+
console.error(
|
|
61
|
+
'[redis] MISCONFIGURED: REDIS_URL is unset or empty. ' +
|
|
62
|
+
'This client will fall back to ioredis defaults (localhost:6379) and may ' +
|
|
63
|
+
'connect to whatever is listening there, or fail on first command. ' +
|
|
64
|
+
'It does NOT fail fast. Set REDIS_URL explicitly.',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const instance = url && url.trim() !== ''
|
|
69
|
+
// Use a standard configuration for better Docker compatibility
|
|
70
|
+
? new Redis(url, REDIS_OPTIONS)
|
|
71
|
+
// NOTE: this does NOT fail fast - see the block above. It targets
|
|
72
|
+
// localhost:6379 by ioredis default.
|
|
73
|
+
: new Redis({ ...REDIS_OPTIONS, lazyConnect: true });
|
|
74
|
+
|
|
75
|
+
// Loud AND load-bearing: an ioredis 'error' event with NO listener is an
|
|
76
|
+
// unhandled EventEmitter error, which can take the whole PROCESS down -
|
|
77
|
+
// a Redis blip becoming a dead server is worse than the spinner was.
|
|
78
|
+
instance.on('error', (err) => {
|
|
79
|
+
console.error(`[redis] PATH FAILURE ${new Date().toISOString()}: ${err instanceof Error ? err.message : String(err)}`);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 185121 line 3: the 16:05 event left no named trigger because ONLY 'error'
|
|
83
|
+
// was listened for. A reconnect or a clean end is silent in the artifact,
|
|
84
|
+
// so a session-death window could not be correlated with a redis event.
|
|
85
|
+
// One line per event; a recurrence now says whether redis moved at all.
|
|
86
|
+
instance.on('reconnecting', (delayMs: number) => {
|
|
87
|
+
console.warn(`[redis] RECONNECTING ${new Date().toISOString()}: next attempt in ${delayMs}ms`);
|
|
88
|
+
});
|
|
89
|
+
instance.on('end', () => {
|
|
90
|
+
console.error(`[redis] CONNECTION ENDED ${new Date().toISOString()}: no further reconnects — every session read now fails or misses`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return instance;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getRedis(): Redis {
|
|
97
|
+
if (!client) {
|
|
98
|
+
client = createClient();
|
|
99
|
+
}
|
|
100
|
+
return client;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const redis = getRedis();
|
|
104
|
+
export { redis };
|
|
105
|
+
export default redis;
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
import { jwtDecode } from './jwt-decode';
|
|
2
|
-
import { tokenRefreshLogger } from '../config/logger';
|
|
3
|
-
|
|
4
|
-
interface RefreshTokenPayload { exp: number; iat: number; jti: string; user_id: string; token_type: string; binding: string; }
|
|
5
|
-
|
|
6
|
-
export function isRefreshTokenValid(token: string): boolean {
|
|
7
|
-
if (!token) return false;
|
|
8
|
-
try { const decoded = jwtDecode<RefreshTokenPayload>(token); if (!decoded) return false; const now = Math.floor(Date.now() / 1000); if (decoded.exp < now) return false; if (decoded.token_type !== 'refresh_token') return false; return true; } catch { return false; }
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function isRefreshTokenExpiring(token: string, bufferMinutes: number = 60): boolean {
|
|
12
|
-
if (!token) return true;
|
|
13
|
-
try { const decoded = jwtDecode<RefreshTokenPayload>(token); if (!decoded?.exp) return true; const now = Math.floor(Date.now() / 1000); const buffer = bufferMinutes * 60; return decoded.exp <= (now + buffer); } catch { return true; }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function getRefreshTokenExpiration(token: string): number | null {
|
|
17
|
-
if (!token) return null; try { const decoded = jwtDecode<RefreshTokenPayload>(token); if (!decoded?.exp) return null; return decoded.exp * 1000; } catch { return null; }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function getRefreshTokenTimeRemaining(token: string): number | null {
|
|
21
|
-
if (!token) return null; try { const decoded = jwtDecode<RefreshTokenPayload>(token); if (!decoded?.exp) return null; const now = Math.floor(Date.now() / 1000); const timeRemaining = decoded.exp - now; return timeRemaining > 0 ? timeRemaining : null; } catch { return null; }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface RefreshViabilityCheck {
|
|
25
|
-
canRefresh: boolean;
|
|
26
|
-
reason: 'valid_refresh_token' | 'no_refresh_token' | 'refresh_token_expired' | 'session_missing';
|
|
27
|
-
timeRemaining?: number;
|
|
28
|
-
expiresAt?: string;
|
|
29
|
-
accessTokenExpired?: boolean;
|
|
30
|
-
accessTokenTimeRemaining?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function checkRefreshViability(sessionData: any): RefreshViabilityCheck {
|
|
34
|
-
if (!sessionData) return { canRefresh: false, reason: 'session_missing' };
|
|
35
|
-
let accessTokenExpired = false; let accessTokenTimeRemaining: number | undefined;
|
|
36
|
-
if (sessionData.idpAccessTokenExpires) {
|
|
37
|
-
const now = Date.now(); let expiresAtMs = sessionData.idpAccessTokenExpires;
|
|
38
|
-
if (typeof expiresAtMs === 'string') expiresAtMs = parseInt(expiresAtMs, 10);
|
|
39
|
-
if (expiresAtMs < 1000000000000) expiresAtMs = expiresAtMs * 1000;
|
|
40
|
-
accessTokenTimeRemaining = Math.floor((expiresAtMs - now) / 1000);
|
|
41
|
-
const bufferSec = 5 * 60; // 5 minutes pre-expiry buffer
|
|
42
|
-
accessTokenExpired = accessTokenTimeRemaining <= bufferSec;
|
|
43
|
-
tokenRefreshLogger.debug('[REFRESH_VIABILITY] Access token expiration check', { now, expiresAtMs, accessTokenTimeRemaining, bufferSec, accessTokenExpired });
|
|
44
|
-
}
|
|
45
|
-
if (!sessionData.idpRefreshToken) return { canRefresh: false, reason: 'no_refresh_token', accessTokenExpired, accessTokenTimeRemaining };
|
|
46
|
-
if (sessionData.idpRefreshTokenExpires) {
|
|
47
|
-
let refreshExpMs = sessionData.idpRefreshTokenExpires;
|
|
48
|
-
if (typeof refreshExpMs === 'string') refreshExpMs = parseInt(refreshExpMs, 10);
|
|
49
|
-
if (refreshExpMs < 1000000000000) refreshExpMs = refreshExpMs * 1000;
|
|
50
|
-
const nowMs = Date.now(); const timeRemainingSec = Math.floor((refreshExpMs - nowMs) / 1000);
|
|
51
|
-
if (timeRemainingSec <= 0) return { canRefresh: false, reason: 'refresh_token_expired', accessTokenExpired, accessTokenTimeRemaining };
|
|
52
|
-
return { canRefresh: true, reason: 'valid_refresh_token', timeRemaining: timeRemainingSec, expiresAt: new Date(refreshExpMs).toISOString(), accessTokenExpired, accessTokenTimeRemaining };
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
const decoded = jwtDecode<RefreshTokenPayload>(sessionData.idpRefreshToken); const nowSec = Math.floor(Date.now() / 1000);
|
|
56
|
-
if (!decoded?.exp || decoded.token_type !== 'refresh_token') return { canRefresh: false, reason: 'refresh_token_expired', accessTokenExpired, accessTokenTimeRemaining };
|
|
57
|
-
const timeRemaining = decoded.exp - nowSec; if (timeRemaining <= 0) return { canRefresh: false, reason: 'refresh_token_expired', accessTokenExpired, accessTokenTimeRemaining };
|
|
58
|
-
const expiresAtIso = new Date(decoded.exp * 1000).toISOString();
|
|
59
|
-
return { canRefresh: true, reason: 'valid_refresh_token', timeRemaining, expiresAt: expiresAtIso, accessTokenExpired, accessTokenTimeRemaining };
|
|
60
|
-
} catch (error) {
|
|
61
|
-
tokenRefreshLogger.debug('[REFRESH_VIABILITY] Failed to decode refresh token for viability', { error: error instanceof Error ? error.message : String(error) });
|
|
62
|
-
return { canRefresh: false, reason: 'refresh_token_expired', accessTokenExpired, accessTokenTimeRemaining };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
1
|
+
import { jwtDecode } from './jwt-decode';
|
|
2
|
+
import { tokenRefreshLogger } from '../config/logger';
|
|
3
|
+
|
|
4
|
+
interface RefreshTokenPayload { exp: number; iat: number; jti: string; user_id: string; token_type: string; binding: string; }
|
|
5
|
+
|
|
6
|
+
export function isRefreshTokenValid(token: string): boolean {
|
|
7
|
+
if (!token) return false;
|
|
8
|
+
try { const decoded = jwtDecode<RefreshTokenPayload>(token); if (!decoded) return false; const now = Math.floor(Date.now() / 1000); if (decoded.exp < now) return false; if (decoded.token_type !== 'refresh_token') return false; return true; } catch { return false; }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isRefreshTokenExpiring(token: string, bufferMinutes: number = 60): boolean {
|
|
12
|
+
if (!token) return true;
|
|
13
|
+
try { const decoded = jwtDecode<RefreshTokenPayload>(token); if (!decoded?.exp) return true; const now = Math.floor(Date.now() / 1000); const buffer = bufferMinutes * 60; return decoded.exp <= (now + buffer); } catch { return true; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getRefreshTokenExpiration(token: string): number | null {
|
|
17
|
+
if (!token) return null; try { const decoded = jwtDecode<RefreshTokenPayload>(token); if (!decoded?.exp) return null; return decoded.exp * 1000; } catch { return null; }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getRefreshTokenTimeRemaining(token: string): number | null {
|
|
21
|
+
if (!token) return null; try { const decoded = jwtDecode<RefreshTokenPayload>(token); if (!decoded?.exp) return null; const now = Math.floor(Date.now() / 1000); const timeRemaining = decoded.exp - now; return timeRemaining > 0 ? timeRemaining : null; } catch { return null; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RefreshViabilityCheck {
|
|
25
|
+
canRefresh: boolean;
|
|
26
|
+
reason: 'valid_refresh_token' | 'no_refresh_token' | 'refresh_token_expired' | 'session_missing';
|
|
27
|
+
timeRemaining?: number;
|
|
28
|
+
expiresAt?: string;
|
|
29
|
+
accessTokenExpired?: boolean;
|
|
30
|
+
accessTokenTimeRemaining?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function checkRefreshViability(sessionData: any): RefreshViabilityCheck {
|
|
34
|
+
if (!sessionData) return { canRefresh: false, reason: 'session_missing' };
|
|
35
|
+
let accessTokenExpired = false; let accessTokenTimeRemaining: number | undefined;
|
|
36
|
+
if (sessionData.idpAccessTokenExpires) {
|
|
37
|
+
const now = Date.now(); let expiresAtMs = sessionData.idpAccessTokenExpires;
|
|
38
|
+
if (typeof expiresAtMs === 'string') expiresAtMs = parseInt(expiresAtMs, 10);
|
|
39
|
+
if (expiresAtMs < 1000000000000) expiresAtMs = expiresAtMs * 1000;
|
|
40
|
+
accessTokenTimeRemaining = Math.floor((expiresAtMs - now) / 1000);
|
|
41
|
+
const bufferSec = 5 * 60; // 5 minutes pre-expiry buffer
|
|
42
|
+
accessTokenExpired = accessTokenTimeRemaining <= bufferSec;
|
|
43
|
+
tokenRefreshLogger.debug('[REFRESH_VIABILITY] Access token expiration check', { now, expiresAtMs, accessTokenTimeRemaining, bufferSec, accessTokenExpired });
|
|
44
|
+
}
|
|
45
|
+
if (!sessionData.idpRefreshToken) return { canRefresh: false, reason: 'no_refresh_token', accessTokenExpired, accessTokenTimeRemaining };
|
|
46
|
+
if (sessionData.idpRefreshTokenExpires) {
|
|
47
|
+
let refreshExpMs = sessionData.idpRefreshTokenExpires;
|
|
48
|
+
if (typeof refreshExpMs === 'string') refreshExpMs = parseInt(refreshExpMs, 10);
|
|
49
|
+
if (refreshExpMs < 1000000000000) refreshExpMs = refreshExpMs * 1000;
|
|
50
|
+
const nowMs = Date.now(); const timeRemainingSec = Math.floor((refreshExpMs - nowMs) / 1000);
|
|
51
|
+
if (timeRemainingSec <= 0) return { canRefresh: false, reason: 'refresh_token_expired', accessTokenExpired, accessTokenTimeRemaining };
|
|
52
|
+
return { canRefresh: true, reason: 'valid_refresh_token', timeRemaining: timeRemainingSec, expiresAt: new Date(refreshExpMs).toISOString(), accessTokenExpired, accessTokenTimeRemaining };
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const decoded = jwtDecode<RefreshTokenPayload>(sessionData.idpRefreshToken); const nowSec = Math.floor(Date.now() / 1000);
|
|
56
|
+
if (!decoded?.exp || decoded.token_type !== 'refresh_token') return { canRefresh: false, reason: 'refresh_token_expired', accessTokenExpired, accessTokenTimeRemaining };
|
|
57
|
+
const timeRemaining = decoded.exp - nowSec; if (timeRemaining <= 0) return { canRefresh: false, reason: 'refresh_token_expired', accessTokenExpired, accessTokenTimeRemaining };
|
|
58
|
+
const expiresAtIso = new Date(decoded.exp * 1000).toISOString();
|
|
59
|
+
return { canRefresh: true, reason: 'valid_refresh_token', timeRemaining, expiresAt: expiresAtIso, accessTokenExpired, accessTokenTimeRemaining };
|
|
60
|
+
} catch (error) {
|
|
61
|
+
tokenRefreshLogger.debug('[REFRESH_VIABILITY] Failed to decode refresh token for viability', { error: error instanceof Error ? error.message : String(error) });
|
|
62
|
+
return { canRefresh: false, reason: 'refresh_token_expired', accessTokenExpired, accessTokenTimeRemaining };
|
|
63
|
+
}
|
|
64
|
+
}
|