@payez/next-mvp 4.1.20 → 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/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 +12 -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 +170 -4
- package/dist/server/auth.js +42 -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 +12 -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 +54 -74
- 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
|
@@ -47,6 +47,7 @@ const session_store_1 = require("../lib/session-store");
|
|
|
47
47
|
const idp_client_config_1 = require("../lib/idp-client-config");
|
|
48
48
|
const app_slug_1 = require("../lib/app-slug");
|
|
49
49
|
const startup_init_1 = require("../lib/startup-init");
|
|
50
|
+
const session_cookie_shape_1 = require("../lib/session-cookie-shape");
|
|
50
51
|
/**
|
|
51
52
|
* Try Better Auth's server-side session API.
|
|
52
53
|
* Returns a DecodedSession if Better Auth has an active session, null otherwise.
|
|
@@ -126,6 +127,43 @@ async function tryBetterAuthSession(requestCookies) {
|
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
catch { /* Redis unavailable */ }
|
|
130
|
+
// LEG-1 FAIL-CLOSED ON UNUSABLE ROLES (card 340015, BAPert R412, 2026-09-03).
|
|
131
|
+
//
|
|
132
|
+
// `idpTokens.roles` is the ONLY source of roles for every role-gated route
|
|
133
|
+
// (see :109 below). When it is empty this leg used to return a perfectly
|
|
134
|
+
// valid-LOOKING session carrying `roles: []` — so the caller was
|
|
135
|
+
// authenticated but authorised for nothing. Every admin screen 403s and
|
|
136
|
+
// renders its chrome with no data and NO ERROR TEXT: it does not look
|
|
137
|
+
// broken, it looks empty.
|
|
138
|
+
//
|
|
139
|
+
// Three independent causes produce that state, so the test is on the
|
|
140
|
+
// CONDITION THAT ACTUALLY HARMS — "no usable roles" — rather than on any
|
|
141
|
+
// one route to it. A fourth cause is then covered for free:
|
|
142
|
+
// A MINT the password / admin-login lane never writes the block
|
|
143
|
+
// (it never calls storeIdpTokensInSession), so it is
|
|
144
|
+
// absent from birth.
|
|
145
|
+
// B CLOBBER better-auth's own 24h session refresh rebuilds the
|
|
146
|
+
// `ba:` record as `{session, user}` and drops the
|
|
147
|
+
// `idpTokens` SIBLING structurally, not conditionally
|
|
148
|
+
// (better-auth internal-adapter.mjs:287). This hits the
|
|
149
|
+
// OAuth and passkey lanes too — they are safe for 24
|
|
150
|
+
// hours and then stop being safe.
|
|
151
|
+
// C DERIVATION the block is PRESENT and fully populated with only
|
|
152
|
+
// `roles` empty (card 339748). `!idpTokens` is FALSE
|
|
153
|
+
// here and cannot see it — which is exactly why the
|
|
154
|
+
// predicate is `!idpTokens?.roles?.length` and not an
|
|
155
|
+
// absence check.
|
|
156
|
+
//
|
|
157
|
+
// Declining here hands the request to the legacy `{slug}:sess:` leg below,
|
|
158
|
+
// which carries the same roles. *** IF THAT RECORD IS ALSO ABSENT THE USER
|
|
159
|
+
// IS SIGNED OUT. THAT IS DELIBERATE AND ACCEPTED, NOT A REGRESSION *** —
|
|
160
|
+
// an honest, visible sign-out in place of a silently half-authorised
|
|
161
|
+
// session. Read the commit message before treating a sign-out here as a
|
|
162
|
+
// bug introduced by this fix.
|
|
163
|
+
if (!idpTokens?.roles?.length) {
|
|
164
|
+
console.warn('[DECODE-SESSION] Leg 1 declining: idpTokens unusable (no roles) for session', result.session.token.substring(0, 10), '— falling through to the sess: leg');
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
129
167
|
// Map Better Auth session + IDP tokens to SessionData
|
|
130
168
|
const sessionData = {
|
|
131
169
|
userId: idpTokens?.userId || result.user.id || '',
|
|
@@ -137,6 +175,9 @@ async function tryBetterAuthSession(requestCookies) {
|
|
|
137
175
|
idpAccessTokenExpires: idpTokens?.idpAccessTokenExpires
|
|
138
176
|
|| (result.session.expiresAt ? new Date(result.session.expiresAt).getTime() : Date.now() + 24 * 60 * 60 * 1000),
|
|
139
177
|
mfaVerified: idpTokens?.mfaVerified ?? false,
|
|
178
|
+
// R807 (362550): fail-closed collapse above stays; this preserves the bit it
|
|
179
|
+
// destroys. Explicit `false` counts as a signal - hence `!== undefined`.
|
|
180
|
+
mfaSignalPresent: idpTokens?.mfaVerified !== undefined,
|
|
140
181
|
/**
|
|
141
182
|
* ⚠ WAS HARDCODED `'google'` — third site of QAPert finding #3 (card
|
|
142
183
|
* 167892), found by sweeping rather than by report.
|
|
@@ -207,23 +248,56 @@ async function decodeSession(requestCookies) {
|
|
|
207
248
|
console.error('[DECODE-SESSION] No authSecret available from IDP config');
|
|
208
249
|
return null;
|
|
209
250
|
}
|
|
210
|
-
|
|
251
|
+
// BA-SHAPE-AWARE FALLBACK LEG (183047, 2026-08-08).
|
|
252
|
+
//
|
|
253
|
+
// Better Auth mints session cookies as `{token}.{base64(hmac)}` — TWO
|
|
254
|
+
// segments, one dot. jose's `jwtVerify()` below requires a genuine
|
|
255
|
+
// compact JWS — THREE segments — and throws "Invalid Compact JWS" on a
|
|
256
|
+
// BA-shaped cookie unconditionally, before it ever looks at the secret.
|
|
257
|
+
// Since Better Auth is the only minter in production, that meant this
|
|
258
|
+
// fallback could never succeed for the cookies it actually receives: any
|
|
259
|
+
// single Better-Auth-primary miss (secondary-storage eviction, disabled
|
|
260
|
+
// cookie cache, in-flight secret divergence) read as total session
|
|
261
|
+
// death, because the ONLY fallback was structurally blind to the only
|
|
262
|
+
// shape it would ever be handed.
|
|
263
|
+
//
|
|
264
|
+
// Check the shape FIRST. A BA-hmac cookie skips jose entirely and goes
|
|
265
|
+
// straight to the Redis-canonical liveness check below — same posture
|
|
266
|
+
// as the JWS path always had (no signature re-check here either; Redis
|
|
267
|
+
// presence is the authority). A JWS-shaped cookie (legacy NextAuth-era,
|
|
268
|
+
// if any still linger) keeps the original jwtVerify path unchanged.
|
|
269
|
+
const shaped = (0, session_cookie_shape_1.extractSessionTokenFromCookieValue)(cookieValue);
|
|
270
|
+
let sessionToken;
|
|
211
271
|
let payload;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
payload = result.payload;
|
|
272
|
+
if (shaped?.shape === 'ba-hmac') {
|
|
273
|
+
sessionToken = shaped.token;
|
|
215
274
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
275
|
+
else {
|
|
276
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
277
|
+
try {
|
|
278
|
+
const result = await (0, jose_1.jwtVerify)(cookieValue, secretKey);
|
|
279
|
+
payload = result.payload;
|
|
280
|
+
}
|
|
281
|
+
catch (jwtError) {
|
|
282
|
+
console.warn('[DECODE-SESSION] JWT verification failed:', jwtError instanceof Error ? jwtError.message : String(jwtError));
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
sessionToken = payload.sessionToken || payload.redisSessionId;
|
|
219
286
|
}
|
|
220
|
-
const sessionToken = payload.sessionToken || payload.redisSessionId;
|
|
221
287
|
if (!sessionToken) {
|
|
222
|
-
console.warn('[DECODE-SESSION]
|
|
288
|
+
console.warn('[DECODE-SESSION] No sessionToken resolved from cookie (shape:', shaped?.shape ?? 'unrecognized', ')');
|
|
223
289
|
return null;
|
|
224
290
|
}
|
|
225
291
|
const sessionData = await (0, session_store_1.getSession)(sessionToken);
|
|
226
292
|
if (!sessionData) {
|
|
293
|
+
// The OTHER half of R412(ii). There are two ways decodeSession returns
|
|
294
|
+
// null and they mean different things, so they must not both be silent:
|
|
295
|
+
// Leg 1 declining is routine and recoverable (this leg normally holds
|
|
296
|
+
// the roles), whereas reaching HERE means there is no fallback record
|
|
297
|
+
// either and the caller is about to be signed out. Different meanings,
|
|
298
|
+
// different remediations — do not ship a second silent path while
|
|
299
|
+
// fixing a silent path.
|
|
300
|
+
console.warn('[DECODE-SESSION] No sess: record for token', sessionToken.substring(0, 10), '— no fallback available, session will be signed out');
|
|
227
301
|
return null;
|
|
228
302
|
}
|
|
229
303
|
// Backwards compat: session.user.email works alongside session.email
|
|
@@ -237,7 +311,7 @@ async function decodeSession(requestCookies) {
|
|
|
237
311
|
}
|
|
238
312
|
return {
|
|
239
313
|
sessionData,
|
|
240
|
-
jwtPayload: payload,
|
|
314
|
+
jwtPayload: (payload ?? { sessionToken }),
|
|
241
315
|
};
|
|
242
316
|
}
|
|
243
317
|
catch (error) {
|
package/dist/types/auth.d.ts
CHANGED
|
@@ -2,7 +2,25 @@ import { JwtPayload } from 'jwt-decode';
|
|
|
2
2
|
export type AuthMode = 'traditional' | 'federated';
|
|
3
3
|
export type FederatedProvider = 'google' | 'microsoft' | 'facebook' | 'github' | 'apple';
|
|
4
4
|
export interface AuthConfig {
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Superseded by allowFederatedLogin / allowTraditionalLogin.
|
|
7
|
+
* A single mode made the two mutually exclusive, so a site could only ever show ONE of
|
|
8
|
+
* OAuth or username/password - and because sites set it themselves, a client's sign-in
|
|
9
|
+
* screen could contradict what its own IDP offered. Kept so existing callers compile.
|
|
10
|
+
*/
|
|
5
11
|
mode: AuthMode;
|
|
12
|
+
/**
|
|
13
|
+
* Whether federated (OAuth) sign-in is offered. Defaults to what the IDP reports at
|
|
14
|
+
* /api/auth/settings; a site may override. Both this and allowTraditionalLogin can be
|
|
15
|
+
* true at once - that is the normal case.
|
|
16
|
+
*/
|
|
17
|
+
allowFederatedLogin?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Whether username/password sign-in is offered. DEFAULTS ON. The IDP does not carry an
|
|
20
|
+
* explicit flag for it yet; until it does, it is available unless a site opts out.
|
|
21
|
+
* An OAuth-only client is a deliberate choice, never the default.
|
|
22
|
+
*/
|
|
23
|
+
allowTraditionalLogin?: boolean;
|
|
6
24
|
providers: FederatedProvider[];
|
|
7
25
|
enableRecovery: boolean;
|
|
8
26
|
enableEmailSignup: boolean;
|