@payez/next-mvp 4.1.14 → 4.1.21
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/dist/api-handlers/account/profile.d.ts +38 -0
- package/dist/api-handlers/account/profile.js +73 -45
- package/dist/api-handlers/account/recovery/send-code.d.ts +33 -0
- package/dist/api-handlers/account/recovery/send-code.js +52 -18
- package/dist/api-handlers/account/recovery/verify-code.d.ts +33 -0
- package/dist/api-handlers/account/recovery/verify-code.js +52 -18
- package/dist/api-handlers/account/verify-email.js +15 -1
- package/dist/api-handlers/account/verify-sms.js +15 -1
- package/dist/api-handlers/auth/passkey-signin.d.ts +29 -0
- package/dist/api-handlers/auth/passkey-signin.js +97 -0
- package/dist/api-handlers/auth/refresh.js +38 -15
- package/dist/api-handlers/mfa/backup-code-verify.d.ts +73 -0
- package/dist/api-handlers/mfa/backup-code-verify.js +153 -0
- package/dist/api-handlers/mfa/backup-codes.d.ts +7 -0
- package/dist/api-handlers/mfa/backup-codes.js +40 -0
- package/dist/api-handlers/mfa/methods.d.ts +3 -0
- package/dist/api-handlers/mfa/methods.js +41 -0
- package/dist/api-handlers/mfa/totp-confirm.d.ts +8 -0
- package/dist/api-handlers/mfa/totp-confirm.js +41 -0
- package/dist/api-handlers/mfa/totp-enroll.d.ts +8 -0
- package/dist/api-handlers/mfa/totp-enroll.js +41 -0
- package/dist/api-handlers/mfa/totp-verify.d.ts +47 -0
- package/dist/api-handlers/mfa/totp-verify.js +134 -0
- package/dist/api-handlers/passkeys/list.d.ts +7 -0
- package/dist/api-handlers/passkeys/list.js +29 -0
- package/dist/api-handlers/passkeys/manage.d.ts +3 -0
- package/dist/api-handlers/passkeys/manage.js +54 -0
- package/dist/api-handlers/passkeys/register-options.d.ts +8 -0
- package/dist/api-handlers/passkeys/register-options.js +41 -0
- package/dist/api-handlers/passkeys/register-verify.d.ts +7 -0
- package/dist/api-handlers/passkeys/register-verify.js +40 -0
- package/dist/api-handlers/session/viability.js +19 -0
- package/dist/auth/better-auth.d.ts +68 -2
- package/dist/auth/better-auth.js +137 -18
- package/dist/auth/utils/idp-client.d.ts +13 -22
- package/dist/auth/utils/idp-client.js +12 -82
- package/dist/client/better-auth-client.d.ts +62 -62
- package/dist/lib/ensure-fresh-access-token.js +49 -8
- package/dist/lib/session-store.d.ts +1 -0
- package/dist/lib/session-store.js +19 -8
- package/dist/lib/token-lifecycle.d.ts +2 -0
- package/dist/lib/token-lifecycle.js +61 -7
- package/dist/models/SessionModel.d.ts +17 -0
- package/dist/pages/login/page.d.ts +10 -1
- package/dist/pages/login/page.js +69 -80
- package/dist/pages/verify-code/page.d.ts +10 -1
- package/dist/pages/verify-code/page.js +72 -4
- package/dist/routes/auth/passkey-signin.d.ts +13 -0
- package/dist/routes/auth/passkey-signin.js +18 -0
- package/dist/routes/auth/viability.d.ts +2 -0
- package/dist/routes/auth/viability.js +88 -0
- package/dist/routes/mfa/backup-code-verify.d.ts +1 -0
- package/dist/routes/mfa/backup-code-verify.js +5 -0
- package/dist/routes/mfa/backup-codes.d.ts +10 -0
- package/dist/routes/mfa/backup-codes.js +14 -0
- package/dist/routes/mfa/methods.d.ts +21 -0
- package/dist/routes/mfa/methods.js +10 -0
- package/dist/routes/mfa/totp-confirm.d.ts +10 -0
- package/dist/routes/mfa/totp-confirm.js +14 -0
- package/dist/routes/mfa/totp-enroll.d.ts +10 -0
- package/dist/routes/mfa/totp-enroll.js +14 -0
- package/dist/routes/mfa/totp-verify.d.ts +14 -0
- package/dist/routes/mfa/totp-verify.js +18 -0
- package/dist/routes/passkeys/list.d.ts +10 -0
- package/dist/routes/passkeys/list.js +14 -0
- package/dist/routes/passkeys/manage.d.ts +18 -0
- package/dist/routes/passkeys/manage.js +13 -0
- package/dist/routes/passkeys/register-options.d.ts +10 -0
- package/dist/routes/passkeys/register-options.js +14 -0
- package/dist/routes/passkeys/register-verify.d.ts +10 -0
- package/dist/routes/passkeys/register-verify.js +14 -0
- package/dist/server/auth.d.ts +29 -20
- package/dist/server/auth.js +8 -0
- package/dist/server/decode-session.js +37 -3
- package/package.json +958 -894
- package/scripts/check-internal-url-usage.sh +73 -73
- package/src/api-handlers/account/profile.ts +76 -57
- package/src/api-handlers/account/recovery/send-code.ts +65 -17
- package/src/api-handlers/account/recovery/verify-code.ts +65 -17
- package/src/api-handlers/account/verify-email.ts +15 -1
- package/src/api-handlers/account/verify-sms.ts +15 -1
- package/src/api-handlers/auth/passkey-signin.ts +98 -0
- package/src/api-handlers/auth/refresh.ts +38 -15
- package/src/api-handlers/mfa/backup-code-verify.ts +175 -0
- package/src/api-handlers/mfa/backup-codes.ts +41 -0
- package/src/api-handlers/mfa/methods.ts +46 -0
- package/src/api-handlers/mfa/totp-confirm.ts +42 -0
- package/src/api-handlers/mfa/totp-enroll.ts +42 -0
- package/src/api-handlers/mfa/totp-verify.ts +156 -0
- package/src/api-handlers/passkeys/list.ts +31 -0
- package/src/api-handlers/passkeys/manage.ts +54 -0
- package/src/api-handlers/passkeys/register-options.ts +42 -0
- package/src/api-handlers/passkeys/register-verify.ts +41 -0
- package/src/api-handlers/session/viability.ts +19 -0
- package/src/auth/better-auth.ts +158 -18
- package/src/auth/utils/idp-client.ts +13 -95
- package/src/lib/ensure-fresh-access-token.ts +49 -8
- package/src/lib/session-store.ts +11 -1
- package/src/lib/token-lifecycle.ts +70 -7
- package/src/models/SessionModel.ts +18 -0
- package/src/pages/login/page.tsx +123 -228
- package/src/pages/verify-code/page.tsx +71 -5
- package/src/routes/auth/passkey-signin.ts +13 -0
- package/src/routes/auth/viability.ts +89 -0
- package/src/routes/mfa/backup-code-verify.ts +1 -0
- package/src/routes/mfa/backup-codes.ts +10 -0
- package/src/routes/mfa/methods.ts +22 -0
- package/src/routes/mfa/totp-confirm.ts +10 -0
- package/src/routes/mfa/totp-enroll.ts +10 -0
- package/src/routes/mfa/totp-verify.ts +14 -0
- package/src/routes/passkeys/list.ts +10 -0
- package/src/routes/passkeys/manage.ts +23 -0
- package/src/routes/passkeys/register-options.ts +10 -0
- package/src/routes/passkeys/register-verify.ts +10 -0
- package/src/server/auth.ts +17 -1
- package/src/server/decode-session.ts +36 -3
package/dist/auth/better-auth.js
CHANGED
|
@@ -51,6 +51,7 @@ exports.configureBetterAuth = configureBetterAuth;
|
|
|
51
51
|
exports.getBetterAuthInstance = getBetterAuthInstance;
|
|
52
52
|
exports.getBetterAuthHandler = getBetterAuthHandler;
|
|
53
53
|
exports.exchangeOAuthForIdpTokens = exchangeOAuthForIdpTokens;
|
|
54
|
+
exports.storeIdpTokensInSession = storeIdpTokensInSession;
|
|
54
55
|
exports.createAuthGetHandler = createAuthGetHandler;
|
|
55
56
|
require("server-only");
|
|
56
57
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
@@ -62,6 +63,7 @@ const magic_link_1 = require("better-auth/plugins/magic-link");
|
|
|
62
63
|
const idp_client_config_1 = require("../lib/idp-client-config");
|
|
63
64
|
const app_slug_1 = require("../lib/app-slug");
|
|
64
65
|
const redis_1 = require("../lib/redis");
|
|
66
|
+
const session_store_1 = require("../lib/session-store");
|
|
65
67
|
/**
|
|
66
68
|
* Build Better Auth social providers from IDP config.
|
|
67
69
|
*/
|
|
@@ -210,16 +212,24 @@ function createBetterAuthInstance(idpConfig, opts = {}) {
|
|
|
210
212
|
// Redis-backed session storage via secondaryStorage
|
|
211
213
|
secondaryStorage: {
|
|
212
214
|
get: async (key) => {
|
|
213
|
-
|
|
215
|
+
try {
|
|
216
|
+
return await (0, redis_1.getRedis)().get(`ba:${appSlug}:${key}`);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
214
221
|
},
|
|
215
222
|
set: async (key, value, ttl) => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
try {
|
|
224
|
+
const redis = (0, redis_1.getRedis)();
|
|
225
|
+
if (ttl) {
|
|
226
|
+
await redis.setex(`ba:${appSlug}:${key}`, ttl, value);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
await redis.setex(`ba:${appSlug}:${key}`, session_store_1.SESSION_TTL, value);
|
|
230
|
+
}
|
|
222
231
|
}
|
|
232
|
+
catch { /* Redis unavailable — cookie cache still works */ }
|
|
223
233
|
},
|
|
224
234
|
delete: async (key) => {
|
|
225
235
|
try {
|
|
@@ -264,6 +274,9 @@ function createBetterAuthInstance(idpConfig, opts = {}) {
|
|
|
264
274
|
plugins: [
|
|
265
275
|
(0, next_js_1.nextCookies)(),
|
|
266
276
|
...(opts.magicLink ? [(0, magic_link_1.magicLink)(opts.magicLink)] : []),
|
|
277
|
+
// App-supplied plugins last: they may depend on the built-ins above, and
|
|
278
|
+
// ordering is BA's own concern once they are in the array.
|
|
279
|
+
...(opts.plugins ?? []),
|
|
267
280
|
],
|
|
268
281
|
});
|
|
269
282
|
}
|
|
@@ -427,8 +440,20 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
|
|
|
427
440
|
return false;
|
|
428
441
|
}
|
|
429
442
|
// Build IDP token data
|
|
430
|
-
//
|
|
431
|
-
//
|
|
443
|
+
// ORIGINAL INTENT: IDP withholds refresh_token until 2FA is complete, so
|
|
444
|
+
// !hasRefreshToken was a reliable "2FA required" signal.
|
|
445
|
+
//
|
|
446
|
+
// BUG [IDP-2FA-BUG]: IDP is currently returning refresh_token on the initial
|
|
447
|
+
// oauth-callback even for require_2fa tenants that haven't done 2FA yet.
|
|
448
|
+
// This made !hasRefreshToken=false → requiresTwoFactor=false → mfaVerified=true,
|
|
449
|
+
// bypassing the 2FA gate entirely.
|
|
450
|
+
//
|
|
451
|
+
// FIX: Always set mfaVerified=false on the initial OAuth exchange.
|
|
452
|
+
// The tenant's require_2fa setting is enforced by the viability endpoint
|
|
453
|
+
// (tenantRequiresTwoFactor=true), which redirects to verify-code whenever
|
|
454
|
+
// mfaVerified=false. After real 2FA, transitionTo2FASession() sets it true.
|
|
455
|
+
// Revert this comment + restore `mfaVerified: !requiresTwoFactor` once the
|
|
456
|
+
// IDP correctly withholds refresh_token until post-2FA.
|
|
432
457
|
const hasRefreshToken = !!result.refresh_token;
|
|
433
458
|
const requiresTwoFactor = result.user?.requiresTwoFactor ?? result.requiresTwoFactor ?? !hasRefreshToken;
|
|
434
459
|
const idpTokenData = {
|
|
@@ -442,31 +467,125 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
|
|
|
442
467
|
name: result.user?.full_name || result.user?.name || result.name || name,
|
|
443
468
|
image: image,
|
|
444
469
|
roles: result.user?.roles || result.roles || [],
|
|
445
|
-
mfaVerified:
|
|
470
|
+
mfaVerified: false, // Always false post-OAuth — see IDP-2FA-BUG note above
|
|
471
|
+
// Cards 141688/141741 — RELAYED, NOT DERIVED.
|
|
472
|
+
//
|
|
473
|
+
// The IDP already decides this (ExternalAuthController:943 and :2024,
|
|
474
|
+
// WO 113023): "enroll_mfa" = no CONFIRMED method, "mfa_verify" = a
|
|
475
|
+
// confirmed method exists, null = fully authenticated. It was emitted
|
|
476
|
+
// here and dropped on this line — `idpTokenData` is built field-by-field,
|
|
477
|
+
// so anything not named is silently discarded. Both downstream consumers
|
|
478
|
+
// (the middleware gate and the verify-code page) then reasoned in its
|
|
479
|
+
// absence and sent zero-method users to a page they cannot act on.
|
|
480
|
+
//
|
|
481
|
+
// WIRE NAME IS SNAKE: `next_step`, per [JsonPropertyName("next_step")] on
|
|
482
|
+
// BOTH OAuthCallbackResponse and ExternalTokenResponse. Reading
|
|
483
|
+
// `result.nextStep` here would be `undefined` forever and fail silently —
|
|
484
|
+
// the same camel/snake break `requires_two_factor` already cost us once.
|
|
485
|
+
//
|
|
486
|
+
// Do NOT recompute this from a method query. The IDP owns the fact; it is
|
|
487
|
+
// already usable-aware (confirmed and not revoked, per DotNetPert), which
|
|
488
|
+
// is the distinction that decides correctness: a revoked-TOTP user has
|
|
489
|
+
// count>0 but usable=0, and deriving it locally would let through exactly
|
|
490
|
+
// the users we must challenge.
|
|
491
|
+
// No `?? null`: if the response has no `next_step` at all (older IDP
|
|
492
|
+
// build, or a path that does not set it), that is UNKNOWN, not "fully
|
|
493
|
+
// authenticated". Defaulting it to null here would manufacture a
|
|
494
|
+
// clean-bill-of-health the IDP never gave and fail open at the gate.
|
|
495
|
+
nextStep: result.next_step,
|
|
446
496
|
idpClientId: result.client_id ? String(result.client_id) : undefined,
|
|
447
497
|
merchantId: result.merchant_id ? String(result.merchant_id) : undefined,
|
|
448
498
|
};
|
|
449
|
-
//
|
|
499
|
+
// Storage is shared with the passkey sign-in path — see storeIdpTokensInSession.
|
|
500
|
+
const stored = await storeIdpTokensInSession(sessionToken, idpTokenData, provider);
|
|
501
|
+
if (stored)
|
|
502
|
+
console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
|
|
503
|
+
return stored;
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
console.error('[BETTER_AUTH] IDP token exchange failed:', err instanceof Error ? err.message : String(err));
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Write IDP tokens into an EXISTING Better Auth session. Storage only — this
|
|
512
|
+
* function never acquires tokens and never decides who the caller is.
|
|
513
|
+
*
|
|
514
|
+
* ── WHY THIS IS SPLIT OUT (BAPert ruling, 2026-08-04) ────────────────────────
|
|
515
|
+
*
|
|
516
|
+
* `exchangeOAuthForIdpTokens` above does TWO things: it ACQUIRES tokens by
|
|
517
|
+
* POSTing to `/api/ExternalAuth/oauth-callback`, and then it STORES them. Only
|
|
518
|
+
* the second half is reusable, and the first half is actively dangerous to
|
|
519
|
+
* reuse:
|
|
520
|
+
*
|
|
521
|
+
* oauth-callback mints an IDP bearer from an ASSERTED EMAIL with no
|
|
522
|
+
* cryptographic verification of any kind (card 138635).
|
|
523
|
+
*
|
|
524
|
+
* Passkey sign-in already holds something strictly better — a grant issued by
|
|
525
|
+
* `/api/ExternalAuth/passwordless/passkey/verify`, which checked a real
|
|
526
|
+
* signature against a registered credential. Routing that through
|
|
527
|
+
* oauth-callback to obtain a session would DOWNGRADE a cryptographically
|
|
528
|
+
* verified assertion into an unverified one, make passkey sign-in weaker than
|
|
529
|
+
* its own ceremony, and inherit 138635 wholesale.
|
|
530
|
+
*
|
|
531
|
+
* That is not hypothetical: "do what the OAuth path does" is the obvious
|
|
532
|
+
* implementation and it is the wrong one. Splitting acquisition from storage is
|
|
533
|
+
* what makes the wrong one hard to write by accident.
|
|
534
|
+
*
|
|
535
|
+
* ── WHAT CALLERS MUST HAVE DONE FIRST ───────────────────────────────────────
|
|
536
|
+
*
|
|
537
|
+
* A Better Auth session must ALREADY EXIST for `sessionToken`. This function
|
|
538
|
+
* enriches; it does not create. Better Auth owns session creation and cookie
|
|
539
|
+
* signing, and nothing in this package should replicate either — the cookie is
|
|
540
|
+
* `{token}.{base64(hmac)}` with standard padding, signed with the runtime
|
|
541
|
+
* `authSecret` from Redis, and hand-rolling that produces a session which
|
|
542
|
+
* decodes but does not authenticate.
|
|
543
|
+
*
|
|
544
|
+
* @param sessionToken the Better Auth session token (from its Set-Cookie)
|
|
545
|
+
* @param idpTokenData tokens + identity, already obtained by the CALLER'S OWN
|
|
546
|
+
* verified route. Built field-by-field by the caller so an
|
|
547
|
+
* unnamed field cannot ride along silently.
|
|
548
|
+
* @param authProvider provenance label recorded on the canonical row. Written
|
|
549
|
+
* to the historical `oauthProvider` key, which predates
|
|
550
|
+
* non-OAuth callers; the NAME is legacy, the VALUE should
|
|
551
|
+
* state what actually authenticated ('passkey', 'google').
|
|
552
|
+
*/
|
|
553
|
+
async function storeIdpTokensInSession(sessionToken, idpTokenData, authProvider) {
|
|
554
|
+
try {
|
|
555
|
+
const config = await (0, idp_client_config_1.getIDPClientConfig)();
|
|
556
|
+
const appSlug = config.clientSlug || (0, app_slug_1.getAppSlug)();
|
|
557
|
+
const baKey = `ba:${appSlug}:${sessionToken}`;
|
|
558
|
+
const baRaw = await (0, redis_1.getRedis)().get(baKey).catch(() => null);
|
|
559
|
+
if (!baRaw) {
|
|
560
|
+
// No session to enrich. Do NOT create one as a fallback: a session
|
|
561
|
+
// conjured here would carry no Better Auth cookie, so it would be
|
|
562
|
+
// unreachable by the browser and invisible to every other consumer —
|
|
563
|
+
// failing as "silently signed out" rather than as an error.
|
|
564
|
+
console.warn('[BETTER_AUTH] storeIdpTokensInSession: no BA session for token', sessionToken.substring(0, 10));
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
const baData = JSON.parse(baRaw);
|
|
450
568
|
baData.idpTokens = idpTokenData;
|
|
451
|
-
await (0, redis_1.getRedis)().setex(baKey,
|
|
452
|
-
//
|
|
453
|
-
//
|
|
569
|
+
await (0, redis_1.getRedis)().setex(baKey, session_store_1.SESSION_TTL, JSON.stringify(baData));
|
|
570
|
+
// Canonical store — the key getSession() reads, used by the refresh handler
|
|
571
|
+
// and token lifecycle. A failure here leaves the BA session correct and the
|
|
572
|
+
// canonical row missing, so it is warned and not fatal: the user is signed
|
|
573
|
+
// in, and refresh degrades rather than the sign-in failing outright.
|
|
454
574
|
try {
|
|
455
575
|
const { getSessionPrefix } = await Promise.resolve().then(() => __importStar(require('../lib/app-slug')));
|
|
456
576
|
const canonicalKey = `${getSessionPrefix()}${sessionToken}`;
|
|
457
|
-
await (0, redis_1.getRedis)().setex(canonicalKey,
|
|
577
|
+
await (0, redis_1.getRedis)().setex(canonicalKey, session_store_1.SESSION_TTL, JSON.stringify({
|
|
458
578
|
...idpTokenData,
|
|
459
|
-
oauthProvider:
|
|
579
|
+
oauthProvider: authProvider,
|
|
460
580
|
}));
|
|
461
581
|
}
|
|
462
582
|
catch (canonicalErr) {
|
|
463
583
|
console.warn('[BETTER_AUTH] Failed to write canonical session:', canonicalErr instanceof Error ? canonicalErr.message : String(canonicalErr));
|
|
464
584
|
}
|
|
465
|
-
console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
|
|
466
585
|
return true;
|
|
467
586
|
}
|
|
468
587
|
catch (err) {
|
|
469
|
-
console.error('[BETTER_AUTH]
|
|
588
|
+
console.error('[BETTER_AUTH] storeIdpTokensInSession failed:', err instanceof Error ? err.message : String(err));
|
|
470
589
|
return false;
|
|
471
590
|
}
|
|
472
591
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @version 1.0.0
|
|
12
12
|
* @since auth-refactor-2026-01
|
|
13
13
|
*/
|
|
14
|
-
import type { IdpLoginResponse,
|
|
14
|
+
import type { IdpLoginResponse, IdpRefreshResponse, LoginCredentials } from '../types/auth-types';
|
|
15
15
|
/**
|
|
16
16
|
* Get IDP base URL. Throws if not configured.
|
|
17
17
|
*/
|
|
@@ -32,30 +32,21 @@ export declare function idpLogin(credentials: LoginCredentials, clientHeaders?:
|
|
|
32
32
|
userAgent?: string;
|
|
33
33
|
}): Promise<IdpLoginResponse>;
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* idpOAuthCallback REMOVED 2026-08-05 (NextPert, BAPert 20168 / QAPert 20166).
|
|
36
36
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* It POSTed to /api/ExternalAuth/oauth-callback with provider, email, name,
|
|
38
|
+
* tokens and client_id — ALL ASSERTIONS, no id_token, nothing verifiable. After
|
|
39
|
+
* the 170003 fix (13436d56e) that call can only ever return 401.
|
|
39
40
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
provider: string;
|
|
45
|
-
providerAccountId: string;
|
|
46
|
-
email: string;
|
|
47
|
-
name?: string;
|
|
48
|
-
image?: string;
|
|
49
|
-
accessToken?: string;
|
|
50
|
-
refreshToken?: string;
|
|
51
|
-
expiresAt?: number;
|
|
52
|
-
}): Promise<IdpOAuthCallbackResponse>;
|
|
53
|
-
/**
|
|
54
|
-
* Refresh an expired access token using the refresh token.
|
|
41
|
+
* It was a SECOND caller of the same endpoint, and only the first
|
|
42
|
+
* (better-auth.ts, card 138635) was hardened — the same duplicated-integration
|
|
43
|
+
* trap the viability code documents ("adding the relay to just one would leave
|
|
44
|
+
* an app that mounts THIS one still blind").
|
|
55
45
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
46
|
+
* It had no call sites and was not reachable through the package exports map
|
|
47
|
+
* (no ./auth/utils key, no wildcard), so removing it breaks nothing. Deleted
|
|
48
|
+
* rather than fixed, because an unreachable function whose only outcome is a
|
|
49
|
+
* 401 that LOOKS like a security success is a trap for whoever finds it next.
|
|
59
50
|
*/
|
|
60
51
|
export declare function idpRefreshToken(refreshToken: string, mfaContext?: {
|
|
61
52
|
amr?: string[];
|
|
@@ -16,7 +16,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
16
16
|
exports.getIdpUrl = getIdpUrl;
|
|
17
17
|
exports.getClientId = getClientId;
|
|
18
18
|
exports.idpLogin = idpLogin;
|
|
19
|
-
exports.idpOAuthCallback = idpOAuthCallback;
|
|
20
19
|
exports.idpRefreshToken = idpRefreshToken;
|
|
21
20
|
exports.idpVerify2FA = idpVerify2FA;
|
|
22
21
|
exports.idpSend2FACode = idpSend2FACode;
|
|
@@ -133,90 +132,21 @@ function getLoginErrorMessage(status, responseData) {
|
|
|
133
132
|
// OAUTH CALLBACK
|
|
134
133
|
// ============================================================================
|
|
135
134
|
/**
|
|
136
|
-
*
|
|
135
|
+
* idpOAuthCallback REMOVED 2026-08-05 (NextPert, BAPert 20168 / QAPert 20166).
|
|
137
136
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
137
|
+
* It POSTed to /api/ExternalAuth/oauth-callback with provider, email, name,
|
|
138
|
+
* tokens and client_id — ALL ASSERTIONS, no id_token, nothing verifiable. After
|
|
139
|
+
* the 170003 fix (13436d56e) that call can only ever return 401.
|
|
140
140
|
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const idpUrl = getIdpUrl();
|
|
146
|
-
const clientId = getClientId();
|
|
147
|
-
try {
|
|
148
|
-
const response = await fetch(`${idpUrl}/api/ExternalAuth/oauth-callback`, {
|
|
149
|
-
method: 'POST',
|
|
150
|
-
headers: {
|
|
151
|
-
'Content-Type': 'application/json',
|
|
152
|
-
'X-Client-Id': clientId,
|
|
153
|
-
},
|
|
154
|
-
body: JSON.stringify({
|
|
155
|
-
provider: oauthData.provider,
|
|
156
|
-
provider_account_id: oauthData.providerAccountId,
|
|
157
|
-
email: oauthData.email,
|
|
158
|
-
name: oauthData.name || '',
|
|
159
|
-
image: oauthData.image || '',
|
|
160
|
-
access_token: oauthData.accessToken || '',
|
|
161
|
-
refresh_token: oauthData.refreshToken || '',
|
|
162
|
-
expires_at: oauthData.expiresAt || 0,
|
|
163
|
-
client_id: clientId,
|
|
164
|
-
}),
|
|
165
|
-
});
|
|
166
|
-
if (!response.ok) {
|
|
167
|
-
const errorText = await response.text().catch(() => 'Unknown error');
|
|
168
|
-
console.error('[IDP_CLIENT] OAuth callback failed:', response.status, errorText);
|
|
169
|
-
return {
|
|
170
|
-
success: false,
|
|
171
|
-
error: {
|
|
172
|
-
code: `HTTP_${response.status}`,
|
|
173
|
-
message: 'OAuth registration failed',
|
|
174
|
-
},
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
const data = await response.json();
|
|
178
|
-
const responseData = data.data || data;
|
|
179
|
-
// Normalize snake_case to camelCase
|
|
180
|
-
return {
|
|
181
|
-
success: responseData.success !== false,
|
|
182
|
-
data: responseData.success !== false
|
|
183
|
-
? {
|
|
184
|
-
accessToken: responseData.accessToken || responseData.access_token,
|
|
185
|
-
refreshToken: responseData.refreshToken || responseData.refresh_token,
|
|
186
|
-
isNewUser: responseData.isNewUser ?? responseData.is_new_user ?? false,
|
|
187
|
-
user: responseData.user
|
|
188
|
-
? {
|
|
189
|
-
userId: responseData.user.userId || responseData.user.user_id,
|
|
190
|
-
email: responseData.user.email || responseData.user.Email,
|
|
191
|
-
fullName: responseData.user.fullName || responseData.user.full_name || responseData.user.name,
|
|
192
|
-
roles: responseData.user.roles || [],
|
|
193
|
-
}
|
|
194
|
-
: undefined,
|
|
195
|
-
}
|
|
196
|
-
: undefined,
|
|
197
|
-
error: responseData.error,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
console.error('[IDP_CLIENT] OAuth callback request failed:', error);
|
|
202
|
-
return {
|
|
203
|
-
success: false,
|
|
204
|
-
error: {
|
|
205
|
-
code: 'NETWORK_ERROR',
|
|
206
|
-
message: 'Failed to connect to authentication service',
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// ============================================================================
|
|
212
|
-
// TOKEN REFRESH
|
|
213
|
-
// ============================================================================
|
|
214
|
-
/**
|
|
215
|
-
* Refresh an expired access token using the refresh token.
|
|
141
|
+
* It was a SECOND caller of the same endpoint, and only the first
|
|
142
|
+
* (better-auth.ts, card 138635) was hardened — the same duplicated-integration
|
|
143
|
+
* trap the viability code documents ("adding the relay to just one would leave
|
|
144
|
+
* an app that mounts THIS one still blind").
|
|
216
145
|
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
146
|
+
* It had no call sites and was not reachable through the package exports map
|
|
147
|
+
* (no ./auth/utils key, no wildcard), so removing it breaks nothing. Deleted
|
|
148
|
+
* rather than fixed, because an unreachable function whose only outcome is a
|
|
149
|
+
* 401 that LOOKS like a security success is a trap for whoever finds it next.
|
|
220
150
|
*/
|
|
221
151
|
async function idpRefreshToken(refreshToken, mfaContext) {
|
|
222
152
|
const idpUrl = getIdpUrl();
|