@payez/next-mvp 4.1.21 → 4.4.0
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 -0
- package/README.md +7 -0
- package/dist/api-handlers/account/recovery/initiate.js +35 -2
- package/dist/api-handlers/account/recovery/send-code.d.ts +0 -33
- package/dist/api-handlers/account/recovery/send-code.js +35 -2
- package/dist/api-handlers/account/recovery/verify-code.d.ts +0 -33
- package/dist/api-handlers/account/recovery/verify-code.js +35 -2
- package/dist/api-handlers/account/reset-password.js +35 -2
- package/dist/api-handlers/account/validate-password.js +21 -3
- package/dist/api-handlers/admin/analytics.js +2 -1
- package/dist/api-handlers/admin/audit.js +2 -1
- package/dist/api-handlers/admin/sessions.js +2 -1
- package/dist/api-handlers/admin/stats.js +2 -1
- package/dist/api-handlers/admin/users.js +2 -1
- package/dist/api-handlers/admin/vibe-data.js +2 -1
- package/dist/api-handlers/auth/jwks.js +10 -0
- package/dist/api-handlers/auth/login.js +5 -2
- package/dist/api-handlers/auth/passkey-signin.js +16 -1
- package/dist/api-handlers/auth/refresh.d.ts +23 -3
- package/dist/api-handlers/auth/refresh.js +107 -16
- package/dist/api-handlers/auth/signout.js +3 -2
- package/dist/api-handlers/auth/verify-code.js +5 -2
- package/dist/api-handlers/session/viability.js +27 -1
- package/dist/api-handlers/test/force-expire.d.ts +29 -15
- package/dist/api-handlers/test/force-expire.js +109 -14
- package/dist/auth/better-auth.d.ts +263 -6
- package/dist/auth/better-auth.js +437 -59
- package/dist/auth/utils/idp-client.js +1 -1
- package/dist/client/AuthContext.js +49 -17
- package/dist/client/better-auth-client.d.ts +62 -62
- package/dist/components/account/UserAvatarMenu.js +12 -0
- package/dist/components/auth/ModeAwareLoginPage.js +10 -8
- package/dist/components/auth/ModeAwareSignupPage.js +5 -2
- package/dist/components/auth/TraditionalAuthSection.js +5 -1
- package/dist/hooks/useAuthSettings.js +14 -2
- package/dist/hooks/usePublicAuthSettings.d.ts +2 -2
- package/dist/hooks/usePublicAuthSettings.js +28 -14
- package/dist/lib/client-key.d.ts +61 -0
- package/dist/lib/client-key.js +72 -0
- package/dist/lib/demo-mode.js +2 -1
- package/dist/lib/ensure-fresh-access-token.js +5 -1
- package/dist/lib/idp-client-config.d.ts +25 -2
- package/dist/lib/idp-client-config.js +381 -36
- package/dist/lib/idp-fetch.d.ts +2 -0
- package/dist/lib/idp-fetch.js +31 -1
- package/dist/lib/idp-url-guard.d.ts +29 -0
- package/dist/lib/idp-url-guard.js +38 -0
- package/dist/lib/redis.js +77 -5
- package/dist/lib/session-cookie-shape.d.ts +69 -0
- package/dist/lib/session-cookie-shape.js +126 -0
- package/dist/lib/session-store.d.ts +35 -2
- package/dist/lib/session-store.js +135 -4
- package/dist/lib/single-flight.d.ts +35 -0
- package/dist/lib/single-flight.js +30 -0
- package/dist/lib/token-lifecycle.js +12 -0
- package/dist/middleware/create-middleware.js +12 -0
- package/dist/models/SessionModel.d.ts +41 -0
- package/dist/models/SessionModel.js +9 -0
- package/dist/pages/admin-login/page.d.ts +1 -1
- package/dist/pages/admin-login/page.js +5 -41
- package/dist/pages/security/EnhancedSecurityPage.d.ts +35 -1
- package/dist/pages/security/EnhancedSecurityPage.js +42 -2
- package/dist/pages/security/index.d.ts +1 -0
- package/dist/pages/security/index.js +3 -0
- package/dist/pages/test-env/EmergencyLogoutPage.d.ts +17 -5
- package/dist/pages/test-env/EmergencyLogoutPage.js +17 -5
- package/dist/pages/test-env/JwtInspectPage.d.ts +30 -5
- package/dist/pages/test-env/JwtInspectPage.js +166 -7
- package/dist/pages/test-env/RefreshTokenPage.d.ts +22 -6
- package/dist/pages/test-env/RefreshTokenPage.js +22 -6
- package/dist/pages/test-env/TestEnvPage.d.ts +16 -4
- package/dist/pages/test-env/TestEnvPage.js +16 -4
- package/dist/pages/test-env/guarded.d.ts +4 -0
- package/dist/pages/test-env/guarded.js +86 -0
- package/dist/pages/test-env/index.d.ts +50 -7
- package/dist/pages/test-env/index.js +58 -12
- package/dist/routes/auth/logout.js +3 -2
- package/dist/routes/auth/refresh.js +27 -1
- package/dist/routes/auth/session.d.ts +12 -3
- package/dist/routes/auth/session.js +92 -9
- package/dist/routes/auth/settings.d.ts +34 -4
- package/dist/routes/auth/settings.js +87 -33
- package/dist/routes/auth/viability.d.ts +1 -1
- package/dist/routes/auth/viability.js +122 -11
- package/dist/server/auth-guard.js +22 -15
- package/dist/server/auth.d.ts +177 -20
- package/dist/server/auth.js +34 -75
- package/dist/server/decode-session.js +84 -10
- package/dist/types/auth.d.ts +18 -0
- package/package.json +969 -958
- package/scripts/check-internal-url-usage.sh +73 -73
- package/scripts/check-publish-health.mjs +120 -0
- package/scripts/dev-broker.ps1 +71 -6
- package/scripts/print-client-config.mjs +162 -0
- package/scripts/server-only-stub.cjs +4 -0
- package/src/api-handlers/account/recovery/initiate.ts +35 -2
- package/src/api-handlers/account/recovery/send-code.ts +36 -2
- package/src/api-handlers/account/recovery/verify-code.ts +36 -2
- package/src/api-handlers/account/reset-password.ts +35 -2
- package/src/api-handlers/account/validate-password.ts +22 -3
- package/src/api-handlers/admin/analytics.ts +2 -1
- package/src/api-handlers/admin/audit.ts +2 -1
- package/src/api-handlers/admin/sessions.ts +2 -1
- package/src/api-handlers/admin/stats.ts +2 -1
- package/src/api-handlers/admin/users.ts +2 -1
- package/src/api-handlers/admin/vibe-data.ts +2 -1
- package/src/api-handlers/auth/jwks.ts +10 -0
- package/src/api-handlers/auth/login.ts +5 -2
- package/src/api-handlers/auth/passkey-signin.ts +112 -98
- package/src/api-handlers/auth/refresh.ts +112 -15
- package/src/api-handlers/auth/signout.ts +4 -3
- package/src/api-handlers/auth/verify-code.ts +5 -2
- package/src/api-handlers/mfa/backup-code-verify.ts +175 -175
- package/src/api-handlers/mfa/backup-codes.ts +41 -41
- package/src/api-handlers/mfa/methods.ts +46 -46
- package/src/api-handlers/mfa/totp-confirm.ts +42 -42
- package/src/api-handlers/mfa/totp-enroll.ts +42 -42
- package/src/api-handlers/passkeys/list.ts +31 -31
- package/src/api-handlers/passkeys/manage.ts +54 -54
- package/src/api-handlers/passkeys/register-options.ts +42 -42
- package/src/api-handlers/passkeys/register-verify.ts +41 -41
- package/src/api-handlers/session/viability.ts +29 -1
- package/src/api-handlers/test/force-expire.ts +118 -13
- package/src/auth/better-auth.ts +486 -51
- package/src/auth/utils/idp-client.ts +1 -1
- package/src/client/AuthContext.tsx +55 -19
- package/src/components/account/UserAvatarMenu.tsx +11 -0
- package/src/components/auth/ModeAwareLoginPage.tsx +40 -49
- package/src/components/auth/ModeAwareSignupPage.tsx +5 -2
- package/src/components/auth/TraditionalAuthSection.tsx +5 -1
- package/src/hooks/useAuthSettings.ts +14 -2
- package/src/hooks/usePublicAuthSettings.ts +38 -16
- package/src/lib/client-key.ts +88 -0
- package/src/lib/demo-mode.ts +2 -1
- package/src/lib/ensure-fresh-access-token.ts +5 -1
- package/src/lib/idp-client-config.ts +399 -36
- package/src/lib/idp-fetch.ts +36 -1
- package/src/lib/idp-url-guard.ts +37 -0
- package/src/lib/redis.ts +84 -6
- package/src/lib/session-cookie-shape.ts +184 -0
- package/src/lib/session-store.ts +152 -6
- package/src/lib/single-flight.ts +64 -0
- package/src/lib/token-lifecycle.ts +12 -0
- package/src/middleware/create-middleware.ts +11 -0
- package/src/models/SessionModel.ts +49 -0
- package/src/pages/admin-login/page.tsx +17 -127
- package/src/pages/security/EnhancedSecurityPage.tsx +95 -9
- package/src/pages/security/index.ts +3 -0
- package/src/pages/test-env/EmergencyLogoutPage.tsx +17 -5
- package/src/pages/test-env/JwtInspectPage.tsx +235 -20
- package/src/pages/test-env/RefreshTokenPage.tsx +22 -6
- package/src/pages/test-env/TestEnvPage.tsx +16 -4
- package/src/pages/test-env/guarded.tsx +81 -0
- package/src/pages/test-env/index.ts +59 -7
- package/src/routes/auth/logout.ts +4 -3
- package/src/routes/auth/passkey-signin.ts +13 -13
- package/src/routes/auth/refresh.ts +30 -1
- package/src/routes/auth/session.ts +98 -9
- package/src/routes/auth/settings.ts +144 -42
- package/src/routes/auth/viability.ts +129 -11
- 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/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/server/auth-guard.ts +24 -17
- package/src/server/auth.ts +37 -73
- package/src/server/decode-session.ts +95 -11
- package/src/templates/instrumentation.ts +21 -2
- package/src/types/auth.ts +20 -0
- package/dist/lib/auth-secret.d.ts +0 -17
- package/dist/lib/auth-secret.js +0 -116
- package/src/lib/auth-secret.ts +0 -137
|
@@ -72,21 +72,57 @@ async function getTenantRequiresTwoFactor() {
|
|
|
72
72
|
* nullable require_2fa fix: "absent" has to be expressible, or callers cannot
|
|
73
73
|
* tell a real value from a default.
|
|
74
74
|
*
|
|
75
|
-
* No local default:
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
75
|
+
* No local default. 257255 UPDATE: allowed2FAMethods is now OPTIONAL on
|
|
76
|
+
* AuthSettings - the config mapper no longer substitutes ['email','sms'] when
|
|
77
|
+
* the document carries no policy, because that made "no policy" and "exactly
|
|
78
|
+
* these methods" indistinguishable. So absent now correctly reaches this
|
|
79
|
+
* function as undefined and is reported as UNKNOWN (null), which is what this
|
|
80
|
+
* function's own contract already said it wanted. Before the change it would
|
|
81
|
+
* have returned the mapper's fabrication AS IF IT WERE THE TENANT'S VALUE.
|
|
79
82
|
*/
|
|
80
83
|
async function getTenantAllowed2FAMethods() {
|
|
81
84
|
try {
|
|
82
85
|
const config = await (0, idp_client_config_1.getIDPClientConfig)();
|
|
83
|
-
|
|
86
|
+
// `?? null` is the whole point: undefined means the document carried no
|
|
87
|
+
// policy, and this function reports UNKNOWN rather than guessing.
|
|
88
|
+
return config.authSettings.allowed2FAMethods ?? null;
|
|
84
89
|
}
|
|
85
90
|
catch {
|
|
86
91
|
console.warn('[VIABILITY] Could not get client config — reporting allowed 2FA methods as UNKNOWN (null), not guessing');
|
|
87
92
|
return null;
|
|
88
93
|
}
|
|
89
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Resolve the client id reported to the caller.
|
|
97
|
+
*
|
|
98
|
+
* Was: `session?.idpClientId || process.env.IDP_CLIENT_ID || ''`. Two defects,
|
|
99
|
+
* both of the "report a confident value where the truth is unknown" family that
|
|
100
|
+
* card 221076 was about:
|
|
101
|
+
*
|
|
102
|
+
* 1. `|| ''` made "we do not know this session's client" indistinguishable from
|
|
103
|
+
* "the client id is blank". Callers cannot tell absence from a known-empty
|
|
104
|
+
* value, so nothing can react to the former. Absence is now `null`, which is
|
|
105
|
+
* representable — and still falsy, so existing `if (!clientId)` guards behave
|
|
106
|
+
* exactly as before.
|
|
107
|
+
*
|
|
108
|
+
* 2. The env fallback SILENTLY substitutes the deployment's default client for a
|
|
109
|
+
* session whose own client is unknown. On a multi-tenant IDP that is a
|
|
110
|
+
* cross-tenant wrong answer, not a default. It is kept for single-tenant
|
|
111
|
+
* deployments that rely on it, but it now says so in the log rather than
|
|
112
|
+
* passing the substitution off as the session's own identity.
|
|
113
|
+
*/
|
|
114
|
+
function resolveClientId(sessionClientId) {
|
|
115
|
+
if (sessionClientId)
|
|
116
|
+
return sessionClientId;
|
|
117
|
+
const envClientId = process.env.IDP_CLIENT_ID;
|
|
118
|
+
if (envClientId) {
|
|
119
|
+
console.warn('[VIABILITY_ROUTE] session carries no idpClientId - substituting IDP_CLIENT_ID ' +
|
|
120
|
+
'from the environment. On a multi-tenant deployment this is a DIFFERENT client ' +
|
|
121
|
+
'than the session belongs to.');
|
|
122
|
+
return envClientId;
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
90
126
|
/**
|
|
91
127
|
* GET /api/session/viability - Check if session is viable
|
|
92
128
|
*
|
|
@@ -157,9 +193,84 @@ async function GET(req) {
|
|
|
157
193
|
// default that reads like a measurement. Same mistake as the nullable
|
|
158
194
|
// require_2fa case: absent has to stay expressible.
|
|
159
195
|
const nextStep = session?.nextStep;
|
|
196
|
+
/**
|
|
197
|
+
* 266007 — port the sibling's CRITICAL override (api-handlers/session/
|
|
198
|
+
* viability.ts:82-86) into this, the impl consumers actually MOUNT.
|
|
199
|
+
* An OAuth session whose callback set mfaVerified === false because no
|
|
200
|
+
* refresh_token was issued ALWAYS needs 2FA completion, whatever the
|
|
201
|
+
* tenant config says — completing 2FA is how it obtains one.
|
|
202
|
+
*
|
|
203
|
+
* STRICT === false, deliberately: an ABSENT mfaVerified (every session
|
|
204
|
+
* minted before that field existed) is not a signal, and treating it as
|
|
205
|
+
* one would force-rechallenge the entire existing session population.
|
|
206
|
+
*
|
|
207
|
+
* *** R807 (BAPert, card 362550) - WHY THE `=== false` TEST ALONE NO LONGER
|
|
208
|
+
* CARRIES THAT INTENT, AND WHAT RESTORES IT. ***
|
|
209
|
+
*
|
|
210
|
+
* The intent above is right, but by the time a session reaches here the
|
|
211
|
+
* distinction it depends on IS ALREADY GONE: every construction site
|
|
212
|
+
* (session-store, decode-session, token-lifecycle x2, SessionModel) collapses
|
|
213
|
+
* an absent mfaVerified to `false` via `?? false`. So `=== false` had stopped
|
|
214
|
+
* meaning "the callback affirmatively said false" and had come to mean
|
|
215
|
+
* "affirmatively false OR never knew" - i.e. the exact mass re-challenge this
|
|
216
|
+
* comment says it is preventing. The guard read as strict while behaving as
|
|
217
|
+
* permissive, because the collapse happens upstream and out of sight.
|
|
218
|
+
*
|
|
219
|
+
* R807 keeps `mfaVerified` fail-closed at all five sites (removing `?? false`
|
|
220
|
+
* would be an AUTH BYPASS - a session that never proved MFA read as having
|
|
221
|
+
* proved it) and gives THIS question its own signal instead. So the test is
|
|
222
|
+
* now two parts:
|
|
223
|
+
*
|
|
224
|
+
* mfaSignalPresent === true -> the producer really did emit a value, so the
|
|
225
|
+
* `false` below is a MEASUREMENT. Fire.
|
|
226
|
+
* absent / false -> LEGACY session (minted before the signal
|
|
227
|
+
* existed) or a path with no IDP tokens at all.
|
|
228
|
+
* NOT a signal. Do NOT force a re-challenge.
|
|
229
|
+
*
|
|
230
|
+
* That absent-means-do-not-fire read is the migration behaviour, and it is
|
|
231
|
+
* what preserves this comment's original guarantee verbatim through rollout:
|
|
232
|
+
* on the day this ships, EVERY existing session has no signal and none of them
|
|
233
|
+
* is re-challenged.
|
|
234
|
+
*
|
|
235
|
+
* *** AND IT IS SAFE ONLY BECAUSE IT IS CONFINED HERE. *** mfaSignalPresent is
|
|
236
|
+
* permissive by construction - it can only ever SUPPRESS this override. It must
|
|
237
|
+
* never reach a path that grants access; gate those on `mfaVerified`, which is
|
|
238
|
+
* still fail-closed everywhere. R807 is void if this signal escapes into an
|
|
239
|
+
* auth decision.
|
|
240
|
+
*
|
|
241
|
+
* Declared once here, like nextStep above, so the three authenticated
|
|
242
|
+
* responses cannot drift apart. Feeds the legacy `requires2FA` field the
|
|
243
|
+
* middleware actually gates on; `tenantRequiresTwoFactor` stays pure —
|
|
244
|
+
* it names TENANT policy, and this override is not tenant state.
|
|
245
|
+
*/
|
|
246
|
+
const oauthSessionNeedsTwoFactor = session?.mfaSignalPresent === true &&
|
|
247
|
+
session?.mfaVerified === false &&
|
|
248
|
+
!(session.idpRefreshToken || session?.refreshToken);
|
|
160
249
|
// Check access token expiry
|
|
161
250
|
const now = Math.floor(Date.now() / 1000);
|
|
162
|
-
|
|
251
|
+
// 240654 addendum 2: fall back to the Redis session's own expiry.
|
|
252
|
+
//
|
|
253
|
+
// The first two reads come off the Better Auth session OBJECT. A Path-B
|
|
254
|
+
// synthetic session — the shape server/auth.ts builds for a legacy JWS
|
|
255
|
+
// session — carries NEITHER: it is `{session:{token}, user:{...}}` with no
|
|
256
|
+
// accessTokenExpires and no exp. So every legacy session fell into the
|
|
257
|
+
// "no expiry info" branch below and answered needsRefresh:true on EVERY
|
|
258
|
+
// viability read from the moment of login, while the token was in fact
|
|
259
|
+
// fresh (15-minute TTL measured). Benign for the viable/authenticated
|
|
260
|
+
// verdict, but it recommends a refresh constantly, which is pure perf
|
|
261
|
+
// noise and multiplies exposure to the empty-clientId refresh path (223542).
|
|
262
|
+
//
|
|
263
|
+
// lib/session-store puts the real value on the session as
|
|
264
|
+
// idpAccessTokenExpires, in MILLISECONDS (better-auth.ts:784 sets
|
|
265
|
+
// `Date.now() + expires_in * 1000`). The other two sources are in SECONDS.
|
|
266
|
+
// Mixing units here would be a silent disaster, so note that the
|
|
267
|
+
// normalisation below (`> 1e12 ? /1000 : as-is`) already discriminates
|
|
268
|
+
// them by magnitude: a seconds timestamp is ~1.7e9, a milliseconds one
|
|
269
|
+
// ~1.7e12. This fallback is therefore safe in either unit, and that
|
|
270
|
+
// existing normaliser is load-bearing for it -- do not remove it.
|
|
271
|
+
const accessTokenExpires = token.accessTokenExpires ||
|
|
272
|
+
token.exp ||
|
|
273
|
+
session?.idpAccessTokenExpires;
|
|
163
274
|
if (!accessTokenExpires) {
|
|
164
275
|
// No expiry info, assume viable but recommend refresh
|
|
165
276
|
const tenantRequiresTwoFactor = await getTenantRequiresTwoFactor();
|
|
@@ -186,7 +297,7 @@ async function GET(req) {
|
|
|
186
297
|
tenantAllowed2FAMethods,
|
|
187
298
|
nextStep,
|
|
188
299
|
// Legacy field names for backwards compatibility
|
|
189
|
-
requires2FA: tenantRequiresTwoFactor,
|
|
300
|
+
requires2FA: tenantRequiresTwoFactor || oauthSessionNeedsTwoFactor,
|
|
190
301
|
twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
|
|
191
302
|
accessTokenExpired: false,
|
|
192
303
|
reason: 'No expiry information'
|
|
@@ -246,13 +357,13 @@ async function GET(req) {
|
|
|
246
357
|
tenantAllowed2FAMethods,
|
|
247
358
|
nextStep,
|
|
248
359
|
// Legacy names for backwards compatibility
|
|
249
|
-
requires2FA: tenantRequiresTwoFactor,
|
|
360
|
+
requires2FA: tenantRequiresTwoFactor || oauthSessionNeedsTwoFactor,
|
|
250
361
|
twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
|
|
251
362
|
accessTokenExpired: true,
|
|
252
363
|
reason: 'Token expired',
|
|
253
364
|
// RBAC fields
|
|
254
365
|
roles: session?.roles || [],
|
|
255
|
-
clientId: session?.idpClientId
|
|
366
|
+
clientId: resolveClientId(session?.idpClientId),
|
|
256
367
|
});
|
|
257
368
|
}
|
|
258
369
|
return server_1.NextResponse.json({
|
|
@@ -269,13 +380,13 @@ async function GET(req) {
|
|
|
269
380
|
tenantAllowed2FAMethods,
|
|
270
381
|
nextStep,
|
|
271
382
|
// Legacy names for backwards compatibility
|
|
272
|
-
requires2FA: tenantRequiresTwoFactor,
|
|
383
|
+
requires2FA: tenantRequiresTwoFactor || oauthSessionNeedsTwoFactor,
|
|
273
384
|
twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
|
|
274
385
|
accessTokenExpired: false,
|
|
275
386
|
expiresAt: new Date(expiryTime * 1000).toISOString(),
|
|
276
387
|
// RBAC fields
|
|
277
388
|
roles: session?.roles || [],
|
|
278
|
-
clientId: session?.idpClientId
|
|
389
|
+
clientId: resolveClientId(session?.idpClientId),
|
|
279
390
|
});
|
|
280
391
|
}
|
|
281
392
|
catch (error) {
|
|
@@ -70,29 +70,36 @@ async function authGuard(options) {
|
|
|
70
70
|
(0, navigation_1.redirect)(`${loginUrl}?callbackUrl=${callbackUrl}&reason=invalidated`);
|
|
71
71
|
}
|
|
72
72
|
// --- 2FA check ---
|
|
73
|
+
// This try wraps ONLY the config fetch, because that is the only thing here
|
|
74
|
+
// that can genuinely fail. Do NOT widen it to cover the redirect below:
|
|
75
|
+
// next/navigation's redirect() signals by THROWING a NEXT_REDIRECT digest, so
|
|
76
|
+
// a redirect inside this try is caught by the catch and rewritten into a
|
|
77
|
+
// service-unavailable page - denying a 2FA user the one page that would let
|
|
78
|
+
// them satisfy the challenge, and reporting it as an outage. See card 312242.
|
|
79
|
+
let requires2FA;
|
|
73
80
|
try {
|
|
74
81
|
const config = await (0, idp_client_config_1.getIDPClientConfig)();
|
|
75
|
-
|
|
76
|
-
if (requires2FA) {
|
|
77
|
-
const mfaVerified = sessionData.mfaVerified ?? sessionData.twoFactorComplete ?? false;
|
|
78
|
-
const mfaExpiresAt = sessionData.mfaExpiresAt || 0;
|
|
79
|
-
const mfaExpired = mfaExpiresAt > 0 && mfaExpiresAt < Date.now();
|
|
80
|
-
if (!mfaVerified || mfaExpired) {
|
|
81
|
-
console.log('[AUTH-GUARD] 2FA required', {
|
|
82
|
-
mfaVerified,
|
|
83
|
-
mfaExpired,
|
|
84
|
-
userId: sessionData.userId,
|
|
85
|
-
pathname,
|
|
86
|
-
});
|
|
87
|
-
(0, navigation_1.redirect)(`${verifyCodeUrl}?callbackUrl=${callbackUrl}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
82
|
+
requires2FA = config.authSettings?.require2FA ?? true;
|
|
90
83
|
}
|
|
91
84
|
catch (error) {
|
|
92
85
|
// If we can't check 2FA config, fail closed
|
|
93
86
|
console.error('[AUTH-GUARD] 2FA config check failed:', error instanceof Error ? error.message : String(error));
|
|
94
87
|
(0, navigation_1.redirect)(serviceUnavailableUrl);
|
|
95
88
|
}
|
|
89
|
+
if (requires2FA) {
|
|
90
|
+
const mfaVerified = sessionData.mfaVerified ?? sessionData.twoFactorComplete ?? false;
|
|
91
|
+
const mfaExpiresAt = sessionData.mfaExpiresAt || 0;
|
|
92
|
+
const mfaExpired = mfaExpiresAt > 0 && mfaExpiresAt < Date.now();
|
|
93
|
+
if (!mfaVerified || mfaExpired) {
|
|
94
|
+
console.log('[AUTH-GUARD] 2FA required', {
|
|
95
|
+
mfaVerified,
|
|
96
|
+
mfaExpired,
|
|
97
|
+
userId: sessionData.userId,
|
|
98
|
+
pathname,
|
|
99
|
+
});
|
|
100
|
+
(0, navigation_1.redirect)(`${verifyCodeUrl}?callbackUrl=${callbackUrl}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
96
103
|
// --- Custom checks (beta, admin, etc.) ---
|
|
97
104
|
if (options?.checks) {
|
|
98
105
|
for (const check of options.checks) {
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -24,9 +24,6 @@ export type IdpTokenResult = {
|
|
|
24
24
|
terminal: false;
|
|
25
25
|
sessionData: SessionData;
|
|
26
26
|
};
|
|
27
|
-
/**
|
|
28
|
-
* Get the initialized Better Auth instance (singleton).
|
|
29
|
-
*/
|
|
30
27
|
export declare function getAuthInstance(): Promise<import("better-auth/types").Auth<{
|
|
31
28
|
baseURL: string;
|
|
32
29
|
secret: string;
|
|
@@ -57,26 +54,26 @@ export declare function getAuthInstance(): Promise<import("better-auth/types").A
|
|
|
57
54
|
hooks: {
|
|
58
55
|
before: {
|
|
59
56
|
matcher(ctx: import("@better-auth/core").HookEndpointContext): boolean;
|
|
60
|
-
handler: (inputContext: import("better-
|
|
57
|
+
handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
|
|
61
58
|
}[];
|
|
62
59
|
after: {
|
|
63
60
|
matcher(ctx: import("@better-auth/core").HookEndpointContext): true;
|
|
64
|
-
handler: (inputContext: import("better-
|
|
61
|
+
handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
|
|
65
62
|
}[];
|
|
66
63
|
};
|
|
67
64
|
}, ...(import("@better-auth/core").BetterAuthPlugin | {
|
|
68
65
|
id: "magic-link";
|
|
69
66
|
endpoints: {
|
|
70
|
-
signInMagicLink: import("better-
|
|
67
|
+
signInMagicLink: import("better-call").StrictEndpoint<"/sign-in/magic-link", {
|
|
71
68
|
method: "POST";
|
|
72
69
|
requireHeaders: true;
|
|
73
|
-
body: import("
|
|
74
|
-
email: import("
|
|
75
|
-
name: import("
|
|
76
|
-
callbackURL: import("
|
|
77
|
-
newUserCallbackURL: import("
|
|
78
|
-
errorCallbackURL: import("
|
|
79
|
-
metadata: import("
|
|
70
|
+
body: import("zod").ZodObject<{
|
|
71
|
+
email: import("zod").ZodEmail;
|
|
72
|
+
name: import("zod").ZodOptional<import("zod").ZodString>;
|
|
73
|
+
callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
|
|
74
|
+
newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
|
|
75
|
+
errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
|
|
76
|
+
metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
|
|
80
77
|
}, import("better-auth/*").$strip>;
|
|
81
78
|
metadata: {
|
|
82
79
|
openapi: {
|
|
@@ -104,15 +101,15 @@ export declare function getAuthInstance(): Promise<import("better-auth/types").A
|
|
|
104
101
|
}, {
|
|
105
102
|
status: boolean;
|
|
106
103
|
}>;
|
|
107
|
-
magicLinkVerify: import("better-
|
|
104
|
+
magicLinkVerify: import("better-call").StrictEndpoint<"/magic-link/verify", {
|
|
108
105
|
method: "GET";
|
|
109
|
-
query: import("
|
|
110
|
-
token: import("
|
|
111
|
-
callbackURL: import("
|
|
112
|
-
errorCallbackURL: import("
|
|
113
|
-
newUserCallbackURL: import("
|
|
106
|
+
query: import("zod").ZodObject<{
|
|
107
|
+
token: import("zod").ZodString;
|
|
108
|
+
callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
|
|
109
|
+
errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
|
|
110
|
+
newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
|
|
114
111
|
}, import("better-auth/*").$strip>;
|
|
115
|
-
use: ((inputContext: import("better-
|
|
112
|
+
use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>)[];
|
|
116
113
|
requireHeaders: true;
|
|
117
114
|
metadata: {
|
|
118
115
|
openapi: {
|
|
@@ -159,6 +156,166 @@ export declare function getAuthInstance(): Promise<import("better-auth/types").A
|
|
|
159
156
|
max: number;
|
|
160
157
|
}[];
|
|
161
158
|
options: import("better-auth/plugins/magic-link").MagicLinkOptions;
|
|
159
|
+
} | {
|
|
160
|
+
id: "generic-oauth";
|
|
161
|
+
init: (ctx: import("@better-auth/core").AuthContext) => {
|
|
162
|
+
context: {
|
|
163
|
+
socialProviders: import("better-auth/*").OAuthProvider<Record<string, any>, Partial<import("better-auth/*").ProviderOptions<any>>>[];
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
endpoints: {
|
|
167
|
+
signInWithOAuth2: import("better-call").StrictEndpoint<"/sign-in/oauth2", {
|
|
168
|
+
method: "POST";
|
|
169
|
+
body: import("zod").ZodObject<{
|
|
170
|
+
providerId: import("zod").ZodString;
|
|
171
|
+
callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
|
|
172
|
+
errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
|
|
173
|
+
newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
|
|
174
|
+
disableRedirect: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
175
|
+
scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
176
|
+
requestSignUp: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
177
|
+
additionalData: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
|
|
178
|
+
}, import("better-auth/*").$strip>;
|
|
179
|
+
metadata: {
|
|
180
|
+
openapi: {
|
|
181
|
+
description: string;
|
|
182
|
+
responses: {
|
|
183
|
+
200: {
|
|
184
|
+
description: string;
|
|
185
|
+
content: {
|
|
186
|
+
"application/json": {
|
|
187
|
+
schema: {
|
|
188
|
+
type: "object";
|
|
189
|
+
properties: {
|
|
190
|
+
url: {
|
|
191
|
+
type: string;
|
|
192
|
+
};
|
|
193
|
+
redirect: {
|
|
194
|
+
type: string;
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
}, {
|
|
205
|
+
url: string;
|
|
206
|
+
redirect: boolean;
|
|
207
|
+
}>;
|
|
208
|
+
oAuth2Callback: import("better-call").StrictEndpoint<"/oauth2/callback/:providerId", {
|
|
209
|
+
method: "GET";
|
|
210
|
+
query: import("zod").ZodObject<{
|
|
211
|
+
code: import("zod").ZodOptional<import("zod").ZodString>;
|
|
212
|
+
error: import("zod").ZodOptional<import("zod").ZodString>;
|
|
213
|
+
error_description: import("zod").ZodOptional<import("zod").ZodString>;
|
|
214
|
+
state: import("zod").ZodOptional<import("zod").ZodString>;
|
|
215
|
+
iss: import("zod").ZodOptional<import("zod").ZodString>;
|
|
216
|
+
}, import("better-auth/*").$strip>;
|
|
217
|
+
metadata: {
|
|
218
|
+
allowedMediaTypes: string[];
|
|
219
|
+
openapi: {
|
|
220
|
+
description: string;
|
|
221
|
+
responses: {
|
|
222
|
+
200: {
|
|
223
|
+
description: string;
|
|
224
|
+
content: {
|
|
225
|
+
"application/json": {
|
|
226
|
+
schema: {
|
|
227
|
+
type: "object";
|
|
228
|
+
properties: {
|
|
229
|
+
url: {
|
|
230
|
+
type: string;
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
scope: "server";
|
|
240
|
+
};
|
|
241
|
+
}, void>;
|
|
242
|
+
oAuth2LinkAccount: import("better-call").StrictEndpoint<"/oauth2/link", {
|
|
243
|
+
method: "POST";
|
|
244
|
+
body: import("zod").ZodObject<{
|
|
245
|
+
providerId: import("zod").ZodString;
|
|
246
|
+
callbackURL: import("zod").ZodString;
|
|
247
|
+
scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
248
|
+
errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
|
|
249
|
+
}, import("better-auth/*").$strip>;
|
|
250
|
+
use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
251
|
+
session: {
|
|
252
|
+
session: Record<string, any> & {
|
|
253
|
+
id: string;
|
|
254
|
+
createdAt: Date;
|
|
255
|
+
updatedAt: Date;
|
|
256
|
+
userId: string;
|
|
257
|
+
expiresAt: Date;
|
|
258
|
+
token: string;
|
|
259
|
+
ipAddress?: string | null | undefined;
|
|
260
|
+
userAgent?: string | null | undefined;
|
|
261
|
+
};
|
|
262
|
+
user: Record<string, any> & {
|
|
263
|
+
id: string;
|
|
264
|
+
createdAt: Date;
|
|
265
|
+
updatedAt: Date;
|
|
266
|
+
email: string;
|
|
267
|
+
emailVerified: boolean;
|
|
268
|
+
name: string;
|
|
269
|
+
image?: string | null | undefined;
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
}>)[];
|
|
273
|
+
metadata: {
|
|
274
|
+
openapi: {
|
|
275
|
+
description: string;
|
|
276
|
+
responses: {
|
|
277
|
+
"200": {
|
|
278
|
+
description: string;
|
|
279
|
+
content: {
|
|
280
|
+
"application/json": {
|
|
281
|
+
schema: {
|
|
282
|
+
type: "object";
|
|
283
|
+
properties: {
|
|
284
|
+
url: {
|
|
285
|
+
type: string;
|
|
286
|
+
format: string;
|
|
287
|
+
description: string;
|
|
288
|
+
};
|
|
289
|
+
redirect: {
|
|
290
|
+
type: string;
|
|
291
|
+
description: string;
|
|
292
|
+
enum: boolean[];
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
required: string[];
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
}, {
|
|
304
|
+
url: string;
|
|
305
|
+
redirect: boolean;
|
|
306
|
+
}>;
|
|
307
|
+
};
|
|
308
|
+
options: import("better-auth/plugins/generic-oauth").GenericOAuthOptions;
|
|
309
|
+
$ERROR_CODES: {
|
|
310
|
+
INVALID_OAUTH_CONFIGURATION: import("better-auth/*").RawError<"INVALID_OAUTH_CONFIGURATION">;
|
|
311
|
+
TOKEN_URL_NOT_FOUND: import("better-auth/*").RawError<"TOKEN_URL_NOT_FOUND">;
|
|
312
|
+
PROVIDER_CONFIG_NOT_FOUND: import("better-auth/*").RawError<"PROVIDER_CONFIG_NOT_FOUND">;
|
|
313
|
+
PROVIDER_ID_REQUIRED: import("better-auth/*").RawError<"PROVIDER_ID_REQUIRED">;
|
|
314
|
+
INVALID_OAUTH_CONFIG: import("better-auth/*").RawError<"INVALID_OAUTH_CONFIG">;
|
|
315
|
+
SESSION_REQUIRED: import("better-auth/*").RawError<"SESSION_REQUIRED">;
|
|
316
|
+
ISSUER_MISMATCH: import("better-auth/*").RawError<"ISSUER_MISMATCH">;
|
|
317
|
+
ISSUER_MISSING: import("better-auth/*").RawError<"ISSUER_MISSING">;
|
|
318
|
+
};
|
|
162
319
|
})[]];
|
|
163
320
|
}>>;
|
|
164
321
|
/**
|
package/dist/server/auth.js
CHANGED
|
@@ -49,9 +49,9 @@ require("server-only");
|
|
|
49
49
|
const better_auth_1 = require("../auth/better-auth");
|
|
50
50
|
const idp_client_config_1 = require("../lib/idp-client-config");
|
|
51
51
|
const session_store_1 = require("../lib/session-store");
|
|
52
|
-
const
|
|
52
|
+
const session_cookie_shape_1 = require("../lib/session-cookie-shape");
|
|
53
|
+
const single_flight_1 = require("../lib/single-flight");
|
|
53
54
|
let authInstance = null;
|
|
54
|
-
let authInitPromise = null;
|
|
55
55
|
function buildSessionDataFromAuthSession(session) {
|
|
56
56
|
const user = session?.user;
|
|
57
57
|
if (!user?.id && !user?.email) {
|
|
@@ -105,82 +105,41 @@ function attachSessionData(session, sessionData, sessionToken) {
|
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
107
|
* Get the initialized Better Auth instance (singleton).
|
|
108
|
+
*
|
|
109
|
+
* 244549: the single-flight memoisation lives in lib/single-flight.ts rather
|
|
110
|
+
* than inline here, and that placement is deliberate rather than tidiness.
|
|
111
|
+
* The bug being fixed -- a REJECTED init promise cached for the life of the
|
|
112
|
+
* process, so recovery was restart-only -- went unnoticed precisely because
|
|
113
|
+
* this module cannot be loaded in a unit test: importing it pulls in
|
|
114
|
+
* better-auth and the whole IDP config stack. Extracted into a zero-import
|
|
115
|
+
* module, the retry semantics are covered by
|
|
116
|
+
* test/single-flight-retry.test.cjs, which imports dist/ and therefore
|
|
117
|
+
* exercises the artefact that actually ships.
|
|
118
|
+
*
|
|
119
|
+
* Semantics, all asserted by that suite: concurrent callers share ONE
|
|
120
|
+
* underlying init; a rejection reaches every caller awaiting that attempt
|
|
121
|
+
* (failures stay loud -- getIDPClientConfig is fail-loud by design and callers
|
|
122
|
+
* depend on it); the NEXT call after a failure starts a fresh attempt; and a
|
|
123
|
+
* slow failure never evicts a newer in-flight attempt.
|
|
108
124
|
*/
|
|
125
|
+
const authInit = (0, single_flight_1.createSingleFlight)(async () => {
|
|
126
|
+
const config = await (0, idp_client_config_1.getIDPClientConfig)(true);
|
|
127
|
+
authInstance = (0, better_auth_1.createBetterAuthInstance)(config);
|
|
128
|
+
// 185121 line 1, path 2: this module-scope instance had NO creation log
|
|
129
|
+
// line — first-called during a config transition window it pinned a
|
|
130
|
+
// divergent secret INVISIBLY. Name the path and the secret fingerprint
|
|
131
|
+
// (the createBetterAuthInstance line already logged the same hash;
|
|
132
|
+
// this line is what attributes it to THIS init path).
|
|
133
|
+
// Guard matches createBetterAuthInstance's: getIDPClientConfig currently
|
|
134
|
+
// throws FATAL on a missing authSecret (QAPert 27328 verified), but that
|
|
135
|
+
// guarantee is one refactor away — log ABSENT rather than ever throw here.
|
|
136
|
+
console.log(`[AUTH] getAuthInstance: created BA instance via server/auth.ts — authSecret ${config.authSecret ? `sha256:${(0, better_auth_1.secretFingerprint)(config.authSecret)}` : 'ABSENT'}`);
|
|
137
|
+
return authInstance;
|
|
138
|
+
});
|
|
109
139
|
async function getAuthInstance() {
|
|
110
140
|
if (authInstance)
|
|
111
141
|
return authInstance;
|
|
112
|
-
|
|
113
|
-
authInitPromise = (0, idp_client_config_1.getIDPClientConfig)(true).then(config => {
|
|
114
|
-
authInstance = (0, better_auth_1.createBetterAuthInstance)(config);
|
|
115
|
-
return authInstance;
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return authInitPromise;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* JWT body-decode helper — base64-decode only, NO signature verification.
|
|
122
|
-
* Safe because the value is then used as a Redis lookup key: Redis is the
|
|
123
|
-
* liveness gate, so an attacker forging a JWT body with a guessed
|
|
124
|
-
* sessionToken claim still has to land on a real Redis session (infeasible
|
|
125
|
-
* against high-entropy UUIDs). Used by the canonical-fallback path in
|
|
126
|
-
* `getSession` to extract the session token when Better Auth's primary
|
|
127
|
-
* path returns null (cookie cache miss / secondary storage eviction).
|
|
128
|
-
*/
|
|
129
|
-
function decodeJwtBody(jwt) {
|
|
130
|
-
try {
|
|
131
|
-
const parts = jwt.split('.');
|
|
132
|
-
if (parts.length !== 3)
|
|
133
|
-
return null;
|
|
134
|
-
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8');
|
|
135
|
-
return JSON.parse(decoded);
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
/** Extract the session-token claim from the Better Auth session cookie
|
|
142
|
-
* WITHOUT going through Better Auth's `auth.api.getSession()`. Handles
|
|
143
|
-
* both single-cookie and chunked-cookie cases (Better Auth chunks long
|
|
144
|
-
* JWTs across `{name}.0`, `{name}.1`, …). Returns the raw `sessionToken`
|
|
145
|
-
* claim suitable for use as a Redis key. Returns null if no cookie is
|
|
146
|
-
* present or the JWT can't be parsed. */
|
|
147
|
-
function extractSessionTokenFromCookie(request) {
|
|
148
|
-
const cookieHeader = request.headers.get('cookie');
|
|
149
|
-
if (!cookieHeader)
|
|
150
|
-
return null;
|
|
151
|
-
const cookieName = (0, app_slug_1.getSessionCookieName)();
|
|
152
|
-
let rawJwt = null;
|
|
153
|
-
// Direct cookie first.
|
|
154
|
-
const chunks = [];
|
|
155
|
-
for (const part of cookieHeader.split(';')) {
|
|
156
|
-
const trimmed = part.trim();
|
|
157
|
-
const eq = trimmed.indexOf('=');
|
|
158
|
-
if (eq === -1)
|
|
159
|
-
continue;
|
|
160
|
-
const k = trimmed.slice(0, eq);
|
|
161
|
-
const v = trimmed.slice(eq + 1);
|
|
162
|
-
if (k === cookieName) {
|
|
163
|
-
rawJwt = decodeURIComponent(v);
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
if (k.startsWith(`${cookieName}.`)) {
|
|
167
|
-
const idx = parseInt(k.split('.').pop() || '0', 10);
|
|
168
|
-
if (Number.isFinite(idx))
|
|
169
|
-
chunks.push({ idx, value: decodeURIComponent(v) });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// Chunked cookies fallback (Better Auth splits long JWTs across .0/.1/.2 …).
|
|
173
|
-
if (!rawJwt && chunks.length > 0) {
|
|
174
|
-
chunks.sort((a, b) => a.idx - b.idx);
|
|
175
|
-
rawJwt = chunks.map(c => c.value).join('');
|
|
176
|
-
}
|
|
177
|
-
if (!rawJwt)
|
|
178
|
-
return null;
|
|
179
|
-
const decoded = decodeJwtBody(rawJwt);
|
|
180
|
-
if (decoded && typeof decoded === 'object' && typeof decoded.sessionToken === 'string') {
|
|
181
|
-
return decoded.sessionToken;
|
|
182
|
-
}
|
|
183
|
-
return null;
|
|
142
|
+
return authInit.get();
|
|
184
143
|
}
|
|
185
144
|
/**
|
|
186
145
|
* Get the current session from a request.
|
|
@@ -232,7 +191,7 @@ async function getSession(request) {
|
|
|
232
191
|
// in-memory cookie cache returned stale-null. No JWT signature check —
|
|
233
192
|
// Redis lookup is the validation: an attacker forging a session-token
|
|
234
193
|
// claim hits NO_SESSION because Redis has no matching entry.
|
|
235
|
-
const sessionToken = extractSessionTokenFromCookie(request);
|
|
194
|
+
const sessionToken = (0, session_cookie_shape_1.extractSessionTokenFromCookie)(request);
|
|
236
195
|
if (!sessionToken)
|
|
237
196
|
return null;
|
|
238
197
|
let sessionData = null;
|