@payez/next-mvp 4.4.5 → 4.4.7
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/README.md +10 -3
- package/dist/api/auth-handler.js +5 -3
- package/dist/api-handlers/account/change-password.js +33 -8
- package/dist/api-handlers/account/masked-info.js +2 -1
- package/dist/api-handlers/account/recovery/forward.d.ts +14 -0
- package/dist/api-handlers/account/recovery/forward.js +105 -0
- package/dist/api-handlers/account/recovery/initiate.js +4 -0
- package/dist/api-handlers/account/recovery/send-code.d.ts +12 -2
- package/dist/api-handlers/account/recovery/send-code.js +11 -84
- package/dist/api-handlers/account/recovery/verify-code.d.ts +11 -2
- package/dist/api-handlers/account/recovery/verify-code.js +10 -84
- package/dist/api-handlers/account/reset-password.js +24 -1
- package/dist/api-handlers/account/send-code.d.ts +1 -15
- package/dist/api-handlers/account/send-code.js +3 -6
- package/dist/api-handlers/account/update-phone.js +6 -1
- package/dist/api-handlers/account/validate-password.js +3 -1
- package/dist/api-handlers/account/verify-email.d.ts +1 -17
- package/dist/api-handlers/account/verify-email.js +4 -6
- package/dist/api-handlers/account/verify-sms.d.ts +1 -17
- package/dist/api-handlers/account/verify-sms.js +4 -6
- package/dist/api-handlers/admin/analytics.js +12 -4
- package/dist/api-handlers/admin/audit.js +12 -4
- package/dist/api-handlers/admin/redis-sessions.js +16 -8
- package/dist/api-handlers/admin/sessions.js +14 -6
- package/dist/api-handlers/admin/site-logs.js +39 -16
- package/dist/api-handlers/admin/stats.js +14 -4
- package/dist/api-handlers/admin/users.js +14 -6
- package/dist/api-handlers/admin/vibe-data.js +19 -11
- package/dist/api-handlers/auth/jwks.js +2 -1
- package/dist/api-handlers/auth/login.js +13 -4
- package/dist/api-handlers/auth/passkey-signin.js +6 -0
- package/dist/api-handlers/auth/refresh.js +38 -11
- package/dist/api-handlers/auth/signout.js +2 -1
- package/dist/api-handlers/auth/status.js +4 -1
- package/dist/api-handlers/auth/update-session.d.ts +25 -4
- package/dist/api-handlers/auth/update-session.js +38 -11
- package/dist/api-handlers/auth/validate.js +4 -2
- package/dist/api-handlers/auth/verify-code.js +3 -2
- package/dist/api-handlers/mfa/backup-code-verify.d.ts +1 -18
- package/dist/api-handlers/mfa/backup-code-verify.js +4 -6
- package/dist/api-handlers/mfa/backup-codes.js +2 -1
- package/dist/api-handlers/mfa/methods.js +2 -1
- package/dist/api-handlers/mfa/totp-confirm.js +2 -1
- package/dist/api-handlers/mfa/totp-enroll.js +2 -1
- package/dist/api-handlers/mfa/totp-verify.d.ts +1 -19
- package/dist/api-handlers/mfa/totp-verify.js +4 -6
- package/dist/api-handlers/passkeys/list.js +2 -1
- package/dist/api-handlers/passkeys/manage.js +2 -1
- package/dist/api-handlers/passkeys/register-options.js +2 -1
- package/dist/api-handlers/passkeys/register-verify.js +2 -1
- package/dist/api-handlers/session/refresh-viability.d.ts +4 -2
- package/dist/api-handlers/session/refresh-viability.js +10 -7
- package/dist/api-handlers/session/viability.js +4 -3
- package/dist/api-handlers/test/force-expire.js +5 -3
- package/dist/auth/auth-decision.js +2 -1
- package/dist/auth/better-auth.d.ts +58 -14
- package/dist/auth/better-auth.js +125 -54
- package/dist/auth/utils/idp-client.js +2 -1
- package/dist/client/better-auth-client.d.ts +101 -956
- package/dist/components/admin/DataBrowserTab.js +4 -1
- package/dist/components/admin/SessionsTab.js +9 -3
- package/dist/components/recovery/RequestedStep.d.ts +11 -0
- package/dist/components/recovery/RequestedStep.js +14 -0
- package/dist/components/recovery/SetPasswordStep.js +10 -1
- package/dist/config/env.d.ts +0 -2
- package/dist/config/env.js +0 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -1
- package/dist/lib/app-slug.d.ts +21 -0
- package/dist/lib/app-slug.js +51 -0
- package/dist/lib/ensure-fresh-access-token.d.ts +26 -0
- package/dist/lib/ensure-fresh-access-token.js +105 -4
- package/dist/lib/idp-client-config.d.ts +20 -0
- package/dist/lib/idp-client-config.js +73 -45
- package/dist/lib/idp-fetch.js +1 -1
- package/dist/lib/never-in-response.d.ts +30 -0
- package/dist/lib/never-in-response.js +71 -0
- package/dist/lib/password-rules.d.ts +22 -0
- package/dist/lib/password-rules.js +43 -0
- package/dist/lib/safe-error-response.d.ts +67 -0
- package/dist/lib/safe-error-response.js +130 -0
- package/dist/lib/session-cookie-shape.d.ts +22 -19
- package/dist/lib/session-cookie-shape.js +14 -18
- package/dist/lib/session-store.d.ts +8 -1
- package/dist/lib/session-store.js +170 -24
- package/dist/lib/startup-init.d.ts +6 -0
- package/dist/lib/startup-init.js +30 -4
- package/dist/lib/token-lifecycle.js +2 -1
- package/dist/lib/token-log-id.d.ts +18 -0
- package/dist/lib/token-log-id.js +84 -0
- package/dist/lib/two-factor-verify-failure.d.ts +78 -0
- package/dist/lib/two-factor-verify-failure.js +220 -0
- package/dist/middleware/create-middleware.d.ts +4 -1
- package/dist/middleware/create-middleware.js +79 -8
- package/dist/pages/login/page.js +5 -0
- package/dist/pages/recovery/page.js +14 -4
- package/dist/pages/test-env/JwtInspectPage.js +7 -149
- package/dist/pages/test-env/RefreshTokenPage.js +4 -1
- package/dist/pages/test-env/redact-credentials.d.ts +6 -0
- package/dist/pages/test-env/redact-credentials.js +101 -0
- package/dist/pages/verify-code/page.js +6 -3
- package/dist/routes/auth/logout.d.ts +2 -1
- package/dist/routes/auth/logout.js +4 -2
- package/dist/routes/auth/session.d.ts +0 -3
- package/dist/routes/auth/session.js +16 -10
- package/dist/routes/auth/settings.js +4 -1
- package/dist/routes/auth/viability.d.ts +3 -2
- package/dist/routes/auth/viability.js +11 -7
- package/dist/server/auth.d.ts +36 -294
- package/dist/server/auth.js +32 -31
- package/dist/server/decode-session.js +15 -26
- package/dist/server/verified-session-token.d.ts +13 -0
- package/dist/server/verified-session-token.js +103 -0
- package/dist/stores/authStore.js +14 -3
- package/dist/types/recovery.d.ts +1 -1
- package/dist/utils/api.d.ts +18 -0
- package/dist/utils/api.js +51 -9
- package/dist/vibe/client.d.ts +132 -16
- package/dist/vibe/client.js +288 -82
- package/dist/vibe/document-unwrap.d.ts +50 -0
- package/dist/vibe/document-unwrap.js +78 -0
- package/dist/vibe/errors.d.ts +17 -0
- package/dist/vibe/errors.js +21 -1
- package/dist/vibe/generic.d.ts +2 -49
- package/dist/vibe/generic.js +17 -78
- package/dist/vibe/index.d.ts +1 -1
- package/dist/vibe/index.js +2 -1
- package/dist/vibe/sessions.js +2 -1
- package/package.json +13 -2
- package/scripts/verify-release-provenance.mjs +410 -0
- package/src/api/auth-handler.ts +5 -3
- package/src/api-handlers/account/change-password.ts +36 -8
- package/src/api-handlers/account/masked-info.ts +2 -1
- package/src/api-handlers/account/recovery/forward.ts +117 -0
- package/src/api-handlers/account/recovery/initiate.ts +3 -0
- package/src/api-handlers/account/recovery/send-code.ts +13 -99
- package/src/api-handlers/account/recovery/verify-code.ts +12 -99
- package/src/api-handlers/account/reset-password.ts +30 -1
- package/src/api-handlers/account/send-code.ts +3 -9
- package/src/api-handlers/account/update-phone.ts +6 -1
- package/src/api-handlers/account/validate-password.ts +3 -1
- package/src/api-handlers/account/verify-email.ts +4 -9
- package/src/api-handlers/account/verify-sms.ts +4 -9
- package/src/api-handlers/admin/analytics.ts +13 -5
- package/src/api-handlers/admin/audit.ts +13 -5
- package/src/api-handlers/admin/redis-sessions.ts +16 -8
- package/src/api-handlers/admin/sessions.ts +15 -7
- package/src/api-handlers/admin/site-logs.ts +39 -16
- package/src/api-handlers/admin/stats.ts +15 -5
- package/src/api-handlers/admin/users.ts +15 -7
- package/src/api-handlers/admin/vibe-data.ts +20 -12
- package/src/api-handlers/auth/jwks.ts +2 -1
- package/src/api-handlers/auth/login.ts +13 -4
- package/src/api-handlers/auth/passkey-signin.ts +4 -0
- package/src/api-handlers/auth/refresh.ts +38 -11
- package/src/api-handlers/auth/signout.ts +2 -1
- package/src/api-handlers/auth/status.ts +4 -1
- package/src/api-handlers/auth/update-session.ts +45 -11
- package/src/api-handlers/auth/validate.ts +4 -2
- package/src/api-handlers/auth/verify-code.ts +3 -2
- package/src/api-handlers/mfa/backup-code-verify.ts +4 -9
- package/src/api-handlers/mfa/backup-codes.ts +2 -1
- package/src/api-handlers/mfa/methods.ts +2 -1
- package/src/api-handlers/mfa/totp-confirm.ts +2 -1
- package/src/api-handlers/mfa/totp-enroll.ts +2 -1
- package/src/api-handlers/mfa/totp-verify.ts +4 -9
- package/src/api-handlers/passkeys/list.ts +2 -1
- package/src/api-handlers/passkeys/manage.ts +2 -1
- package/src/api-handlers/passkeys/register-options.ts +2 -1
- package/src/api-handlers/passkeys/register-verify.ts +2 -1
- package/src/api-handlers/session/refresh-viability.ts +10 -7
- package/src/api-handlers/session/viability.ts +4 -3
- package/src/api-handlers/test/force-expire.ts +5 -3
- package/src/auth/auth-decision.ts +2 -1
- package/src/auth/better-auth.ts +128 -61
- package/src/auth/utils/idp-client.ts +2 -1
- package/src/components/admin/DataBrowserTab.tsx +4 -1
- package/src/components/admin/SessionsTab.tsx +10 -3
- package/src/components/recovery/RequestedStep.tsx +30 -0
- package/src/components/recovery/SetPasswordStep.tsx +24 -3
- package/src/config/env.ts +0 -2
- package/src/hooks/useViabilitySession.ts +2 -1
- package/src/index.ts +4 -0
- package/src/lib/app-slug.ts +50 -0
- package/src/lib/ensure-fresh-access-token.ts +133 -4
- package/src/lib/idp-client-config.ts +98 -45
- package/src/lib/idp-fetch.ts +1 -1
- package/src/lib/never-in-response.ts +64 -0
- package/src/lib/password-rules.ts +50 -0
- package/src/lib/safe-error-response.ts +126 -0
- package/src/lib/session-cookie-shape.ts +23 -22
- package/src/lib/session-store.ts +167 -24
- package/src/lib/startup-init.ts +29 -4
- package/src/lib/token-lifecycle.ts +2 -1
- package/src/lib/token-log-id.ts +69 -0
- package/src/lib/two-factor-verify-failure.ts +248 -0
- package/src/middleware/create-middleware.ts +97 -9
- package/src/pages/login/page.tsx +4 -0
- package/src/pages/recovery/page.tsx +21 -4
- package/src/pages/test-env/JwtInspectPage.tsx +11 -233
- package/src/pages/test-env/RefreshTokenPage.tsx +9 -4
- package/src/pages/test-env/redact-credentials.ts +96 -0
- package/src/pages/verify-code/page.tsx +6 -3
- package/src/routes/auth/logout.ts +4 -2
- package/src/routes/auth/session.ts +16 -10
- package/src/routes/auth/settings.ts +5 -1
- package/src/routes/auth/viability.ts +11 -7
- package/src/server/auth.ts +33 -35
- package/src/server/decode-session.ts +16 -26
- package/src/server/verified-session-token.ts +103 -0
- package/src/stores/authStore.ts +16 -5
- package/src/types/recovery.ts +1 -1
- package/src/utils/api.ts +47 -8
- package/src/vibe/client.ts +378 -91
- package/src/vibe/document-unwrap.ts +103 -0
- package/src/vibe/errors.ts +24 -0
- package/src/vibe/generic.ts +8 -100
- package/src/vibe/index.ts +1 -0
- package/src/vibe/sessions.ts +2 -1
package/dist/auth/better-auth.js
CHANGED
|
@@ -43,6 +43,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
43
43
|
};
|
|
44
44
|
})();
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.OAUTH_SIGN_IN_FAILED_ERROR = void 0;
|
|
46
47
|
exports.secretFingerprint = secretFingerprint;
|
|
47
48
|
exports.buildIdpOAuthProviderConfig = buildIdpOAuthProviderConfig;
|
|
48
49
|
exports.buildBetterAuthProviders = buildBetterAuthProviders;
|
|
@@ -54,6 +55,7 @@ exports.getBetterAuthInstance = getBetterAuthInstance;
|
|
|
54
55
|
exports.getBetterAuthHandler = getBetterAuthHandler;
|
|
55
56
|
exports.exchangeOAuthForIdpTokens = exchangeOAuthForIdpTokens;
|
|
56
57
|
exports.storeIdpTokensInSession = storeIdpTokensInSession;
|
|
58
|
+
exports.expireSetCookies = expireSetCookies;
|
|
57
59
|
exports.createAuthGetHandler = createAuthGetHandler;
|
|
58
60
|
require("server-only");
|
|
59
61
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
@@ -68,7 +70,9 @@ const generic_oauth_1 = require("better-auth/plugins/generic-oauth");
|
|
|
68
70
|
const idp_client_config_1 = require("../lib/idp-client-config");
|
|
69
71
|
const app_slug_1 = require("../lib/app-slug");
|
|
70
72
|
const redis_1 = require("../lib/redis");
|
|
73
|
+
const single_flight_1 = require("../lib/single-flight");
|
|
71
74
|
const session_store_1 = require("../lib/session-store");
|
|
75
|
+
const token_log_id_1 = require("../lib/token-log-id");
|
|
72
76
|
/**
|
|
73
77
|
* 185121: short sha256 fingerprint for secrets/tokens in log lines — enough
|
|
74
78
|
* to tell "same" from "divergent", never enough to reconstruct. 12 hex chars
|
|
@@ -463,6 +467,7 @@ function buildBetterAuthProviders(config) {
|
|
|
463
467
|
* Call after getIDPClientConfig() resolves.
|
|
464
468
|
*/
|
|
465
469
|
function createBetterAuthInstance(idpConfig, opts = {}) {
|
|
470
|
+
(0, app_slug_1.assertAppSlugMatchesIdp)(idpConfig.clientSlug);
|
|
466
471
|
const appSlug = idpConfig.clientSlug || (0, app_slug_1.getAppSlug)();
|
|
467
472
|
// 185121 line 1: name the secret this instance PINNED, as a hash, never the
|
|
468
473
|
// secret. 183047's surviving fork is HMAC-fail vs record-miss; an HMAC-fail
|
|
@@ -745,6 +750,18 @@ function createBetterAuthInstance(idpConfig, opts = {}) {
|
|
|
745
750
|
refreshCache: false,
|
|
746
751
|
},
|
|
747
752
|
},
|
|
753
|
+
// PAY-1781 B: KEEP OAUTH STATE IN THE ENCRYPTED COOKIE, as it was on better-auth 1.5.6.
|
|
754
|
+
// 1.5.6 resolved the default as `options.database ? 'database' : 'cookie'` - we have no
|
|
755
|
+
// database, so: cookie. 1.6.13+ resolves it as `(database || secondaryStorage) ? 'database'
|
|
756
|
+
// : 'cookie'`, and we DO have secondaryStorage, so the upgrade alone would silently move every
|
|
757
|
+
// OAuth state record into Redis through the adapter above (its enrichment read-modify-write
|
|
758
|
+
// and key handling were built for session records, not state). Pinned so the upgrade changes
|
|
759
|
+
// the version, not the design. The cookie branch is where GHSA-wxw3-q3m9-c3jr lived; 1.6.2+
|
|
760
|
+
// compares the returned `state` with the nonce sealed in the cookie, and our provider is also
|
|
761
|
+
// pkce:true, which the advisory required to be false. Test: oauth-state-strategy-cookie.
|
|
762
|
+
account: {
|
|
763
|
+
storeStateStrategy: 'cookie',
|
|
764
|
+
},
|
|
748
765
|
// Cookie prefix must match slim-middleware expectations ({slug}.session-token)
|
|
749
766
|
advanced: {
|
|
750
767
|
cookiePrefix: appSlug,
|
|
@@ -799,18 +816,33 @@ function configureBetterAuth(opts) {
|
|
|
799
816
|
}
|
|
800
817
|
state.configuredOpts = opts;
|
|
801
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* PAY-642 (card 338215): the init attempt no longer LATCHES A FAILURE.
|
|
821
|
+
*
|
|
822
|
+
* This used to be `state.initPromise ??= getIDPClientConfig(true).then(...)` with no .catch and no reset. One
|
|
823
|
+
* auth request against a cold process while the IdP was unreachable stored a rejected promise, and every later
|
|
824
|
+
* caller replayed it until the process restarted, even after the IdP recovered. This funnel backs every
|
|
825
|
+
* /api/auth/* route, the login GET handler and withAuth. server/auth.ts getAuthInstance() already used
|
|
826
|
+
* createSingleFlight for this exact bug; this sibling funnel was missed.
|
|
827
|
+
*
|
|
828
|
+
* Now the attempt runs through the same helper: concurrent callers share one attempt, a failure still rejects
|
|
829
|
+
* for every caller of THAT attempt (loud), the next call starts a fresh attempt, and a success is cached in
|
|
830
|
+
* state.cachedInstance as before. The flight lives on the globalThis state for the same reason the instance
|
|
831
|
+
* does (duplicated route chunks must share one init).
|
|
832
|
+
*/
|
|
802
833
|
async function getBetterAuthInstance() {
|
|
803
834
|
if (state.cachedInstance)
|
|
804
835
|
return state.cachedInstance;
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
836
|
+
state.initFlight ??= (0, single_flight_1.createSingleFlight)(() => (0, idp_client_config_1.getIDPClientConfig)(true).then(config => {
|
|
837
|
+
const instance = createBetterAuthInstance(config, state.configuredOpts);
|
|
838
|
+
state.cachedInstance = instance;
|
|
839
|
+
console.log('[BETTER_AUTH] Instance created for', config.clientSlug || config.clientId);
|
|
840
|
+
return instance;
|
|
841
|
+
}));
|
|
842
|
+
const attempt = state.initFlight.get();
|
|
843
|
+
// Kept for callers that inspect it; it always points at the CURRENT attempt, never a settled failure.
|
|
844
|
+
state.initPromise = attempt;
|
|
845
|
+
return attempt;
|
|
814
846
|
}
|
|
815
847
|
/**
|
|
816
848
|
* Get flag-gated auth handler for Next.js route.
|
|
@@ -851,7 +883,7 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
|
|
|
851
883
|
// Read the BA session from Redis
|
|
852
884
|
const baRaw = await (0, redis_1.getRedis)().get(baKey).catch(() => null);
|
|
853
885
|
if (!baRaw) {
|
|
854
|
-
console.warn('[BETTER_AUTH] exchangeOAuthForIdpTokens: session not found in Redis for token',
|
|
886
|
+
console.warn('[BETTER_AUTH] exchangeOAuthForIdpTokens: session not found in Redis for token', (0, token_log_id_1.tokenLogId)(sessionToken));
|
|
855
887
|
return false;
|
|
856
888
|
}
|
|
857
889
|
const baData = JSON.parse(baRaw);
|
|
@@ -922,7 +954,9 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
|
|
|
922
954
|
}),
|
|
923
955
|
});
|
|
924
956
|
const oauthResText = await oauthRes.text();
|
|
925
|
-
|
|
957
|
+
// PAY-1427 (QAPert, PR 154 thread 326): this printed the first 500 chars of the oauth-callback TOKEN
|
|
958
|
+
// envelope on EVERY successful OAuth login (it runs before the !ok check). Status + length only.
|
|
959
|
+
console.log('[BETTER_AUTH] IDP oauth-callback response:', { status: oauthRes.status, bodyLength: oauthResText.length });
|
|
926
960
|
if (!oauthRes.ok) {
|
|
927
961
|
console.error('[BETTER_AUTH] IDP oauth-callback failed:', oauthRes.status);
|
|
928
962
|
/**
|
|
@@ -1154,7 +1188,7 @@ async function storeIdpTokensInSession(sessionToken, idpTokenData, authProvider)
|
|
|
1154
1188
|
// conjured here would carry no Better Auth cookie, so it would be
|
|
1155
1189
|
// unreachable by the browser and invisible to every other consumer —
|
|
1156
1190
|
// failing as "silently signed out" rather than as an error.
|
|
1157
|
-
console.warn('[BETTER_AUTH] storeIdpTokensInSession: no BA session for token',
|
|
1191
|
+
console.warn('[BETTER_AUTH] storeIdpTokensInSession: no BA session for token', (0, token_log_id_1.tokenLogId)(sessionToken));
|
|
1158
1192
|
return false;
|
|
1159
1193
|
}
|
|
1160
1194
|
const baData = JSON.parse(baRaw);
|
|
@@ -1182,12 +1216,76 @@ async function storeIdpTokensInSession(sessionToken, idpTokenData, authProvider)
|
|
|
1182
1216
|
return false;
|
|
1183
1217
|
}
|
|
1184
1218
|
}
|
|
1219
|
+
/**
|
|
1220
|
+
* The `?error=` code the login page receives when an OAuth sign-in is ABORTED because the IdP exchange did not
|
|
1221
|
+
* store tokens (PAY-1733). Hosts map it to ONE neutral line; the card's words are "That sign-in didn't work.
|
|
1222
|
+
* Try again or use another method." The cause stays in the server log, never on the page.
|
|
1223
|
+
*/
|
|
1224
|
+
exports.OAUTH_SIGN_IN_FAILED_ERROR = 'OAUTH_SIGN_IN_FAILED';
|
|
1225
|
+
/**
|
|
1226
|
+
* Re-emit each cookie the callback response SET as an immediate expiry, keeping the attributes that decide
|
|
1227
|
+
* WHICH cookie the browser overwrites (Path, Domain, Secure, SameSite, HttpOnly - a `__Secure-` name is only
|
|
1228
|
+
* accepted with Secure) and dropping Expires/Max-Age in favour of Max-Age=0.
|
|
1229
|
+
*/
|
|
1230
|
+
function expireSetCookies(setCookies) {
|
|
1231
|
+
const out = [];
|
|
1232
|
+
for (const sc of setCookies) {
|
|
1233
|
+
const [pair, ...attrs] = sc.split(';').map((s) => s.trim());
|
|
1234
|
+
const eq = pair.indexOf('=');
|
|
1235
|
+
const name = eq > 0 ? pair.slice(0, eq) : '';
|
|
1236
|
+
if (!name)
|
|
1237
|
+
continue;
|
|
1238
|
+
const kept = attrs.filter((a) => /^(path|domain|secure|samesite|httponly)\b/i.test(a));
|
|
1239
|
+
out.push([`${name}=`, ...kept, 'Max-Age=0'].join('; '));
|
|
1240
|
+
}
|
|
1241
|
+
return out;
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* PAY-1733 (Jon, live on :4797): ABORT an OAuth sign-in whose IdP exchange did not store tokens.
|
|
1245
|
+
*
|
|
1246
|
+
* Before this, a failed exchange was a console.error and the user kept the Better Auth session the callback had
|
|
1247
|
+
* just created: signed in, holding NO IdP tokens. The host then routed that session to 2FA, which painted the
|
|
1248
|
+
* user's email and failed every call - a red "sign in again" screen about an account the app could no longer
|
|
1249
|
+
* authenticate. The IdP's 401 was CORRECT (it fail-closes unverified assertions); keeping the session was the bug.
|
|
1250
|
+
*
|
|
1251
|
+
* So: no session without IdP tokens. The Better Auth session is destroyed (its own signOut, best effort, then the
|
|
1252
|
+
* `ba:<slug>:<token>` record directly - that record is what makes it resolvable), every cookie the callback set is
|
|
1253
|
+
* expired, and the user goes to the login page with OAUTH_SIGN_IN_FAILED. Relative Location, as for the state-error
|
|
1254
|
+
* branch above (328439: an absolute URL built from request.url is a dead tab in a container).
|
|
1255
|
+
*/
|
|
1256
|
+
async function abortOAuthSignIn(callbackResponse, sessionToken, cookieHeader, loginPath) {
|
|
1257
|
+
const headers = new Headers();
|
|
1258
|
+
if (sessionToken) {
|
|
1259
|
+
try {
|
|
1260
|
+
const auth = await getBetterAuthInstance();
|
|
1261
|
+
const out = await auth?.api?.signOut?.({ headers: new Headers({ cookie: cookieHeader }), asResponse: true });
|
|
1262
|
+
for (const sc of out?.headers?.getSetCookie?.() ?? [])
|
|
1263
|
+
headers.append('set-cookie', sc);
|
|
1264
|
+
}
|
|
1265
|
+
catch (err) {
|
|
1266
|
+
console.warn('[BETTER_AUTH] abort: Better Auth signOut failed, deleting the record directly:', err instanceof Error ? err.message : String(err));
|
|
1267
|
+
}
|
|
1268
|
+
try {
|
|
1269
|
+
const config = await (0, idp_client_config_1.getIDPClientConfig)();
|
|
1270
|
+
await (0, redis_1.getRedis)().del(`ba:${config.clientSlug || (0, app_slug_1.getAppSlug)()}:${sessionToken}`);
|
|
1271
|
+
}
|
|
1272
|
+
catch (err) {
|
|
1273
|
+
console.error('[BETTER_AUTH] abort: could not delete the Better Auth session record', (0, token_log_id_1.tokenLogId)(sessionToken), err instanceof Error ? err.message : String(err));
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
for (const sc of expireSetCookies(callbackResponse.headers.getSetCookie?.() ?? []))
|
|
1277
|
+
headers.append('set-cookie', sc);
|
|
1278
|
+
headers.set('location', `${loginPath}${loginPath.includes('?') ? '&' : '?'}error=${exports.OAUTH_SIGN_IN_FAILED_ERROR}`);
|
|
1279
|
+
console.warn('[BETTER_AUTH] OAuth sign-in ABORTED: the IdP exchange stored no tokens, so no session is kept (PAY-1733)', sessionToken ? (0, token_log_id_1.tokenLogId)(sessionToken) : '(no session)');
|
|
1280
|
+
return new Response(null, { status: 302, headers });
|
|
1281
|
+
}
|
|
1185
1282
|
/**
|
|
1186
1283
|
* Create a production-ready GET handler for the auth catch-all route.
|
|
1187
1284
|
*
|
|
1188
1285
|
* Wraps better-auth's GET handler with:
|
|
1189
1286
|
* - OAuth state error recovery (redirects to login instead of error page)
|
|
1190
|
-
* - IDP token exchange after successful OAuth callback
|
|
1287
|
+
* - IDP token exchange after successful OAuth callback - and, when it stores no tokens, the sign-in is ABORTED
|
|
1288
|
+
* (session destroyed, cookies expired, login page with OAUTH_SIGN_IN_FAILED; PAY-1733)
|
|
1191
1289
|
*
|
|
1192
1290
|
* Usage in host app:
|
|
1193
1291
|
* ```ts
|
|
@@ -1256,56 +1354,27 @@ function createAuthGetHandler(loginPath = '/account-auth/login') {
|
|
|
1256
1354
|
return new Response(null, { status: 302, headers: { location: loginPath } });
|
|
1257
1355
|
}
|
|
1258
1356
|
}
|
|
1259
|
-
// After
|
|
1260
|
-
//
|
|
1261
|
-
//
|
|
1262
|
-
// site passed no provider, so it took exchangeOAuthForIdpTokens's
|
|
1263
|
-
// `provider: string = 'google'` default and asserted "google" to
|
|
1264
|
-
// /api/ExternalAuth/oauth-callback for EVERY provider. That was harmless
|
|
1265
|
-
// while Google was the only one wired, and became wrong the moment a second
|
|
1266
|
-
// provider shipped: a GitHub sign-in forwarded a GitHub access_token
|
|
1267
|
-
// labelled `provider: 'google'`, so the IDP tried to verify it as a Google
|
|
1268
|
-
// identity and fail-closed with 401 UNVERIFIED_ASSERTION in
|
|
1269
|
-
// `response_time_ms: 0` — it never had to call anyone to know it was wrong.
|
|
1270
|
-
//
|
|
1271
|
-
// The diagnostics made this maximally confusing: `access_token_forwarded:
|
|
1272
|
-
// true` is correct and the token really did arrive. The FIELD was fine; the
|
|
1273
|
-
// LABEL on it was a lie. Nothing on the client half is broken.
|
|
1274
|
-
//
|
|
1275
|
-
// The provider was never unavailable — it is the path segment this very
|
|
1276
|
-
// `if` matches on (/api/auth/callback/github). Derive it instead of
|
|
1277
|
-
// defaulting, because a default here cannot be right for more than one
|
|
1278
|
-
// provider at a time.
|
|
1357
|
+
// After the OAuth callback: exchange the provider identity for IDP tokens. If that does NOT store tokens - the
|
|
1358
|
+
// IdP refused (401), no session was found, or anything threw - the sign-in is ABORTED (PAY-1733). A session is
|
|
1359
|
+
// kept only when it holds IdP tokens.
|
|
1279
1360
|
const url = new URL(request.url);
|
|
1280
1361
|
if (url.pathname.includes('/callback/') && response.status === 302) {
|
|
1362
|
+
const setCookies = response.headers.getSetCookie?.() || [];
|
|
1363
|
+
const cookieHeader = setCookies
|
|
1364
|
+
.map((c) => c.split(';')[0])
|
|
1365
|
+
.join('; ');
|
|
1366
|
+
let sessionToken = null;
|
|
1367
|
+
let exchanged = false;
|
|
1281
1368
|
try {
|
|
1282
1369
|
const auth = await getBetterAuthInstance();
|
|
1283
1370
|
if (auth?.api?.getSession) {
|
|
1284
|
-
const setCookies = response.headers.getSetCookie?.() || [];
|
|
1285
|
-
const cookieHeader = setCookies
|
|
1286
|
-
.map((c) => c.split(';')[0])
|
|
1287
|
-
.join('; ');
|
|
1288
1371
|
const headers = new Headers();
|
|
1289
1372
|
headers.set('cookie', cookieHeader);
|
|
1290
1373
|
const session = await auth.api.getSession({ headers });
|
|
1291
1374
|
if (session?.session?.token) {
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
?.split('/')[0]
|
|
1296
|
-
?.split('?')[0]
|
|
1297
|
-
?.trim()
|
|
1298
|
-
.toLowerCase() || '';
|
|
1299
|
-
if (!callbackProvider) {
|
|
1300
|
-
// Do NOT quietly fall back to 'google'. That silent default is the
|
|
1301
|
-
// exact defect this block fixes, and re-introducing it here would
|
|
1302
|
-
// reproduce it for whatever unparseable shape got us here.
|
|
1303
|
-
console.error('[BETTER_AUTH] OAuth callback matched /callback/ but no provider could be derived from', url.pathname, '— refusing to assert a guessed provider to the IDP. No exchange performed.');
|
|
1304
|
-
}
|
|
1305
|
-
else {
|
|
1306
|
-
console.log('[BETTER_AUTH] Got session token from callback:', session.session.token.substring(0, 10), '| provider:', callbackProvider, '| email:', session.user?.email);
|
|
1307
|
-
await exchangeOAuthForIdpTokens(session.session.token, callbackProvider);
|
|
1308
|
-
}
|
|
1375
|
+
sessionToken = session.session.token;
|
|
1376
|
+
console.log('[BETTER_AUTH] Got session token from callback:', (0, token_log_id_1.tokenLogId)(session.session.token), '| email:', session.user?.email);
|
|
1377
|
+
exchanged = await exchangeOAuthForIdpTokens(session.session.token);
|
|
1309
1378
|
}
|
|
1310
1379
|
else {
|
|
1311
1380
|
console.warn('[BETTER_AUTH] Could not get session after OAuth callback');
|
|
@@ -1315,6 +1384,8 @@ function createAuthGetHandler(loginPath = '/account-auth/login') {
|
|
|
1315
1384
|
catch (err) {
|
|
1316
1385
|
console.error('[BETTER_AUTH] IDP token exchange failed:', err.message);
|
|
1317
1386
|
}
|
|
1387
|
+
if (!exchanged)
|
|
1388
|
+
return abortOAuthSignIn(response, sessionToken, cookieHeader, loginPath);
|
|
1318
1389
|
}
|
|
1319
1390
|
return response;
|
|
1320
1391
|
});
|
|
@@ -183,7 +183,8 @@ async function idpRefreshToken(refreshToken, mfaContext) {
|
|
|
183
183
|
});
|
|
184
184
|
if (!response.ok) {
|
|
185
185
|
const errorText = await response.text().catch(() => 'Unknown error');
|
|
186
|
-
|
|
186
|
+
// PAY-1427: never the raw upstream body (the refresh.ts:528 twin PR 139 fixed). Status + length only.
|
|
187
|
+
console.error('[IDP_CLIENT] Token refresh failed:', { status: response.status, bodyLength: errorText.length });
|
|
187
188
|
return {
|
|
188
189
|
success: false,
|
|
189
190
|
error: {
|