@payez/next-mvp 4.4.4 → 4.4.5
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/auth/better-auth.js
CHANGED
|
@@ -1256,7 +1256,26 @@ function createAuthGetHandler(loginPath = '/account-auth/login') {
|
|
|
1256
1256
|
return new Response(null, { status: 302, headers: { location: loginPath } });
|
|
1257
1257
|
}
|
|
1258
1258
|
}
|
|
1259
|
-
// After successful OAuth callback: exchange
|
|
1259
|
+
// After successful OAuth callback: exchange the PROVIDER identity for IDP tokens.
|
|
1260
|
+
//
|
|
1261
|
+
// ⚠ WAS "exchange Google identity" — and it meant it literally. This call
|
|
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.
|
|
1260
1279
|
const url = new URL(request.url);
|
|
1261
1280
|
if (url.pathname.includes('/callback/') && response.status === 302) {
|
|
1262
1281
|
try {
|
|
@@ -1270,8 +1289,23 @@ function createAuthGetHandler(loginPath = '/account-auth/login') {
|
|
|
1270
1289
|
headers.set('cookie', cookieHeader);
|
|
1271
1290
|
const session = await auth.api.getSession({ headers });
|
|
1272
1291
|
if (session?.session?.token) {
|
|
1273
|
-
|
|
1274
|
-
|
|
1292
|
+
// /api/auth/callback/<provider>[?query] -> "<provider>"
|
|
1293
|
+
const callbackProvider = url.pathname
|
|
1294
|
+
.split('/callback/')[1]
|
|
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
|
+
}
|
|
1275
1309
|
}
|
|
1276
1310
|
else {
|
|
1277
1311
|
console.warn('[BETTER_AUTH] Could not get session after OAuth callback');
|
|
@@ -673,7 +673,37 @@ async function fetchConfigFromIDP(idpUrl, clientIdStr) {
|
|
|
673
673
|
'X-Client-Id': clientIdStr,
|
|
674
674
|
'X-Correlation-Id': (0, crypto_1.randomUUID)().replace(/-/g, ''),
|
|
675
675
|
},
|
|
676
|
-
|
|
676
|
+
// 217917 PHASE 1: explicit opt-in for provider secrets.
|
|
677
|
+
//
|
|
678
|
+
// This broker is the ONLY consumer of oauth_providers[].client_secret
|
|
679
|
+
// (DotNetPert enumerated it: this file feeds auth/providers/oauth.ts,
|
|
680
|
+
// which performs the server-side code exchange). Every OTHER caller of
|
|
681
|
+
// ExternalAuth/client-config - notably every fielded acp-desktop - is
|
|
682
|
+
// pushed the same secrets today and reads none of them: the desktop is
|
|
683
|
+
// PKCE and dropped clientSecret from OAuthProviderConfig entirely.
|
|
684
|
+
//
|
|
685
|
+
// The IDP IGNORES this field today, so shipping it is a no-op and needs
|
|
686
|
+
// no coordination window. Phase 2 (DotNetPert, IDP side) makes
|
|
687
|
+
// client-config emit the secrets ONLY when the opt-in is present and
|
|
688
|
+
// redact otherwise - which is why phase 1 MUST be live first, or the
|
|
689
|
+
// broker loses the field and tenant OAuth sign-in dies.
|
|
690
|
+
//
|
|
691
|
+
// SCOPE, deliberately narrow - do NOT read this as a partial migration:
|
|
692
|
+
// the sibling path fetchConfigFromInternalIDP() hits a DIFFERENT
|
|
693
|
+
// endpoint (InternalClientConfig/{id}, GET) that is already
|
|
694
|
+
// authenticated with CONTAINERS_KEY, is not reachable by a desktop, and
|
|
695
|
+
// is therefore not part of 217917's exposure. It deliberately sends no
|
|
696
|
+
// opt-in. If phase 2's redaction is implemented in shared config-
|
|
697
|
+
// assembly code rather than in ExternalAuthController.GetClientConfig,
|
|
698
|
+
// it will ALSO strip the internal route and break this broker's other
|
|
699
|
+
// path with no opt-in to save it. Phase 2 must gate on the request.
|
|
700
|
+
//
|
|
701
|
+
// NOT A FIX ON ITS OWN: an attacker can set this flag too. Phase 2 ends
|
|
702
|
+
// the AUTOMATIC delivery of every tenant's provider secrets to every
|
|
703
|
+
// user laptop; it does not close disclosure to a determined caller.
|
|
704
|
+
// That residual is card 175047 (unauthenticated mint, global partner
|
|
705
|
+
// key) and 217917 does not close until 175047 lands or Jon accepts it.
|
|
706
|
+
body: JSON.stringify({ client_assertion, include_provider_secrets: true }),
|
|
677
707
|
cache: 'no-store'
|
|
678
708
|
});
|
|
679
709
|
if (!configResp.ok) {
|
package/package.json
CHANGED
package/src/auth/better-auth.ts
CHANGED
|
@@ -1383,7 +1383,26 @@ export function createAuthGetHandler(loginPath: string = '/account-auth/login')
|
|
|
1383
1383
|
}
|
|
1384
1384
|
}
|
|
1385
1385
|
|
|
1386
|
-
// After successful OAuth callback: exchange
|
|
1386
|
+
// After successful OAuth callback: exchange the PROVIDER identity for IDP tokens.
|
|
1387
|
+
//
|
|
1388
|
+
// ⚠ WAS "exchange Google identity" — and it meant it literally. This call
|
|
1389
|
+
// site passed no provider, so it took exchangeOAuthForIdpTokens's
|
|
1390
|
+
// `provider: string = 'google'` default and asserted "google" to
|
|
1391
|
+
// /api/ExternalAuth/oauth-callback for EVERY provider. That was harmless
|
|
1392
|
+
// while Google was the only one wired, and became wrong the moment a second
|
|
1393
|
+
// provider shipped: a GitHub sign-in forwarded a GitHub access_token
|
|
1394
|
+
// labelled `provider: 'google'`, so the IDP tried to verify it as a Google
|
|
1395
|
+
// identity and fail-closed with 401 UNVERIFIED_ASSERTION in
|
|
1396
|
+
// `response_time_ms: 0` — it never had to call anyone to know it was wrong.
|
|
1397
|
+
//
|
|
1398
|
+
// The diagnostics made this maximally confusing: `access_token_forwarded:
|
|
1399
|
+
// true` is correct and the token really did arrive. The FIELD was fine; the
|
|
1400
|
+
// LABEL on it was a lie. Nothing on the client half is broken.
|
|
1401
|
+
//
|
|
1402
|
+
// The provider was never unavailable — it is the path segment this very
|
|
1403
|
+
// `if` matches on (/api/auth/callback/github). Derive it instead of
|
|
1404
|
+
// defaulting, because a default here cannot be right for more than one
|
|
1405
|
+
// provider at a time.
|
|
1387
1406
|
const url = new URL(request.url);
|
|
1388
1407
|
if (url.pathname.includes('/callback/') && response.status === 302) {
|
|
1389
1408
|
try {
|
|
@@ -1399,8 +1418,32 @@ export function createAuthGetHandler(loginPath: string = '/account-auth/login')
|
|
|
1399
1418
|
|
|
1400
1419
|
const session = await auth.api.getSession({ headers });
|
|
1401
1420
|
if (session?.session?.token) {
|
|
1402
|
-
|
|
1403
|
-
|
|
1421
|
+
// /api/auth/callback/<provider>[?query] -> "<provider>"
|
|
1422
|
+
const callbackProvider = url.pathname
|
|
1423
|
+
.split('/callback/')[1]
|
|
1424
|
+
?.split('/')[0]
|
|
1425
|
+
?.split('?')[0]
|
|
1426
|
+
?.trim()
|
|
1427
|
+
.toLowerCase() || '';
|
|
1428
|
+
|
|
1429
|
+
if (!callbackProvider) {
|
|
1430
|
+
// Do NOT quietly fall back to 'google'. That silent default is the
|
|
1431
|
+
// exact defect this block fixes, and re-introducing it here would
|
|
1432
|
+
// reproduce it for whatever unparseable shape got us here.
|
|
1433
|
+
console.error(
|
|
1434
|
+
'[BETTER_AUTH] OAuth callback matched /callback/ but no provider could be derived from',
|
|
1435
|
+
url.pathname,
|
|
1436
|
+
'— refusing to assert a guessed provider to the IDP. No exchange performed.'
|
|
1437
|
+
);
|
|
1438
|
+
} else {
|
|
1439
|
+
console.log(
|
|
1440
|
+
'[BETTER_AUTH] Got session token from callback:',
|
|
1441
|
+
session.session.token.substring(0, 10),
|
|
1442
|
+
'| provider:', callbackProvider,
|
|
1443
|
+
'| email:', session.user?.email
|
|
1444
|
+
);
|
|
1445
|
+
await exchangeOAuthForIdpTokens(session.session.token, callbackProvider);
|
|
1446
|
+
}
|
|
1404
1447
|
} else {
|
|
1405
1448
|
console.warn('[BETTER_AUTH] Could not get session after OAuth callback');
|
|
1406
1449
|
}
|
|
@@ -783,7 +783,37 @@ async function fetchConfigFromIDP(idpUrl: string, clientIdStr: string): Promise<
|
|
|
783
783
|
'X-Client-Id': clientIdStr,
|
|
784
784
|
'X-Correlation-Id': randomUUID().replace(/-/g, ''),
|
|
785
785
|
},
|
|
786
|
-
|
|
786
|
+
// 217917 PHASE 1: explicit opt-in for provider secrets.
|
|
787
|
+
//
|
|
788
|
+
// This broker is the ONLY consumer of oauth_providers[].client_secret
|
|
789
|
+
// (DotNetPert enumerated it: this file feeds auth/providers/oauth.ts,
|
|
790
|
+
// which performs the server-side code exchange). Every OTHER caller of
|
|
791
|
+
// ExternalAuth/client-config - notably every fielded acp-desktop - is
|
|
792
|
+
// pushed the same secrets today and reads none of them: the desktop is
|
|
793
|
+
// PKCE and dropped clientSecret from OAuthProviderConfig entirely.
|
|
794
|
+
//
|
|
795
|
+
// The IDP IGNORES this field today, so shipping it is a no-op and needs
|
|
796
|
+
// no coordination window. Phase 2 (DotNetPert, IDP side) makes
|
|
797
|
+
// client-config emit the secrets ONLY when the opt-in is present and
|
|
798
|
+
// redact otherwise - which is why phase 1 MUST be live first, or the
|
|
799
|
+
// broker loses the field and tenant OAuth sign-in dies.
|
|
800
|
+
//
|
|
801
|
+
// SCOPE, deliberately narrow - do NOT read this as a partial migration:
|
|
802
|
+
// the sibling path fetchConfigFromInternalIDP() hits a DIFFERENT
|
|
803
|
+
// endpoint (InternalClientConfig/{id}, GET) that is already
|
|
804
|
+
// authenticated with CONTAINERS_KEY, is not reachable by a desktop, and
|
|
805
|
+
// is therefore not part of 217917's exposure. It deliberately sends no
|
|
806
|
+
// opt-in. If phase 2's redaction is implemented in shared config-
|
|
807
|
+
// assembly code rather than in ExternalAuthController.GetClientConfig,
|
|
808
|
+
// it will ALSO strip the internal route and break this broker's other
|
|
809
|
+
// path with no opt-in to save it. Phase 2 must gate on the request.
|
|
810
|
+
//
|
|
811
|
+
// NOT A FIX ON ITS OWN: an attacker can set this flag too. Phase 2 ends
|
|
812
|
+
// the AUTOMATIC delivery of every tenant's provider secrets to every
|
|
813
|
+
// user laptop; it does not close disclosure to a determined caller.
|
|
814
|
+
// That residual is card 175047 (unauthenticated mint, global partner
|
|
815
|
+
// key) and 217917 does not close until 175047 lands or Jon accepts it.
|
|
816
|
+
body: JSON.stringify({ client_assertion, include_provider_secrets: true }),
|
|
787
817
|
cache: 'no-store'
|
|
788
818
|
} as RequestInit);
|
|
789
819
|
|