@aotter/mantle-cloudflare 0.1.0-alpha.7 → 0.1.0-alpha.8

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.
Files changed (93) hide show
  1. package/README.md +15 -8
  2. package/dist/auth/conventionalAuth.d.ts +3 -0
  3. package/dist/auth/conventionalAuth.d.ts.map +1 -1
  4. package/dist/auth/conventionalAuth.js +23 -6
  5. package/dist/auth/conventionalAuth.js.map +1 -1
  6. package/dist/auth/createAuth.d.ts +30 -13
  7. package/dist/auth/createAuth.d.ts.map +1 -1
  8. package/dist/auth/createAuth.js +173 -154
  9. package/dist/auth/createAuth.js.map +1 -1
  10. package/dist/bindings/AssetsAssetServer.d.ts +4 -4
  11. package/dist/bindings/AssetsAssetServer.d.ts.map +1 -1
  12. package/dist/bindings/AssetsAssetServer.js +2 -2
  13. package/dist/bindings/D1DatabaseDriver.d.ts +3 -4
  14. package/dist/bindings/D1DatabaseDriver.d.ts.map +1 -1
  15. package/dist/bindings/D1DatabaseDriver.js +4 -91
  16. package/dist/bindings/D1DatabaseDriver.js.map +1 -1
  17. package/dist/bindings/WorkersQueueHookDispatcher.d.ts +2 -2
  18. package/dist/bindings/WorkersQueueHookDispatcher.d.ts.map +1 -1
  19. package/dist/bindings/WorkersQueueHookDispatcher.js +1 -1
  20. package/dist/bindings/WorkersQueueHookDispatcher.js.map +1 -1
  21. package/dist/bindings/conventionalBindings.d.ts +2 -3
  22. package/dist/bindings/conventionalBindings.d.ts.map +1 -1
  23. package/dist/bindings/conventionalBindings.js +1 -3
  24. package/dist/bindings/conventionalBindings.js.map +1 -1
  25. package/dist/handlers/turnstile.d.ts +1 -1
  26. package/dist/handlers/turnstile.d.ts.map +1 -1
  27. package/dist/handlers/turnstile.js +3 -1
  28. package/dist/handlers/turnstile.js.map +1 -1
  29. package/dist/mount/bootRuntimeOnce.d.ts +15 -8
  30. package/dist/mount/bootRuntimeOnce.d.ts.map +1 -1
  31. package/dist/mount/bootRuntimeOnce.js +36 -21
  32. package/dist/mount/bootRuntimeOnce.js.map +1 -1
  33. package/dist/mount/cmsConfig.d.ts +17 -9
  34. package/dist/mount/cmsConfig.d.ts.map +1 -1
  35. package/dist/mount/index.d.ts +4 -4
  36. package/dist/mount/index.d.ts.map +1 -1
  37. package/dist/mount/index.js +3 -3
  38. package/dist/mount/index.js.map +1 -1
  39. package/dist/mount/mountAdmin.d.ts +6 -0
  40. package/dist/mount/mountAdmin.d.ts.map +1 -0
  41. package/dist/mount/mountAdmin.js +16 -0
  42. package/dist/mount/mountAdmin.js.map +1 -0
  43. package/dist/mount/mountMcp.d.ts +7 -10
  44. package/dist/mount/mountMcp.d.ts.map +1 -1
  45. package/dist/mount/mountMcp.js +57 -49
  46. package/dist/mount/mountMcp.js.map +1 -1
  47. package/dist/mount/mountPublicRoutes.d.ts +4 -4
  48. package/dist/mount/mountPublicRoutes.d.ts.map +1 -1
  49. package/dist/mount/mountPublicRoutes.js +31 -24
  50. package/dist/mount/mountPublicRoutes.js.map +1 -1
  51. package/dist/mount/mountRuntimeEndpoints.d.ts +7 -0
  52. package/dist/mount/mountRuntimeEndpoints.d.ts.map +1 -0
  53. package/dist/mount/mountRuntimeEndpoints.js +49 -0
  54. package/dist/mount/mountRuntimeEndpoints.js.map +1 -0
  55. package/dist/mount/resolveCaller.d.ts.map +1 -1
  56. package/dist/mount/resolveCaller.js +0 -33
  57. package/dist/mount/resolveCaller.js.map +1 -1
  58. package/dist/oauth/cachePolicy.d.ts.map +1 -1
  59. package/dist/oauth/cachePolicy.js +2 -4
  60. package/dist/oauth/cachePolicy.js.map +1 -1
  61. package/dist/oauth/consentHtml.d.ts +2 -2
  62. package/dist/oauth/consentHtml.d.ts.map +1 -1
  63. package/dist/oauth/consentHtml.js +5 -5
  64. package/dist/oauth/consentHtml.js.map +1 -1
  65. package/dist/oauth/index.d.ts +0 -2
  66. package/dist/oauth/index.d.ts.map +1 -1
  67. package/dist/oauth/index.js +0 -2
  68. package/dist/oauth/index.js.map +1 -1
  69. package/dist/oauth/mountOAuth.d.ts +2 -16
  70. package/dist/oauth/mountOAuth.d.ts.map +1 -1
  71. package/dist/oauth/mountOAuth.js +24 -99
  72. package/dist/oauth/mountOAuth.js.map +1 -1
  73. package/dist/worker/createMantleWorker.d.ts +12 -13
  74. package/dist/worker/createMantleWorker.d.ts.map +1 -1
  75. package/dist/worker/createMantleWorker.js +31 -24
  76. package/dist/worker/createMantleWorker.js.map +1 -1
  77. package/package.json +12 -11
  78. package/dist/mount/mountServerEndpoints.d.ts +0 -9
  79. package/dist/mount/mountServerEndpoints.d.ts.map +0 -1
  80. package/dist/mount/mountServerEndpoints.js +0 -1625
  81. package/dist/mount/mountServerEndpoints.js.map +0 -1
  82. package/dist/mount/runMantleUseCase.d.ts +0 -3
  83. package/dist/mount/runMantleUseCase.d.ts.map +0 -1
  84. package/dist/mount/runMantleUseCase.js +0 -41
  85. package/dist/mount/runMantleUseCase.js.map +0 -1
  86. package/dist/oauth/oauthConstants.d.ts +0 -15
  87. package/dist/oauth/oauthConstants.d.ts.map +0 -1
  88. package/dist/oauth/oauthConstants.js +0 -15
  89. package/dist/oauth/oauthConstants.js.map +0 -1
  90. package/dist/oauth/oauthSingleton.d.ts +0 -33
  91. package/dist/oauth/oauthSingleton.d.ts.map +0 -1
  92. package/dist/oauth/oauthSingleton.js +0 -39
  93. package/dist/oauth/oauthSingleton.js.map +0 -1
@@ -1,12 +1,16 @@
1
1
  import { betterAuth } from "better-auth";
2
- import { applyDefaultAccessTokenExpiry, refreshAccessToken, validateAuthorizationCode, verifyJwsAccessToken, } from "better-auth/oauth2";
2
+ import { createDpopReplayStore, enforceDpopBinding, isDpopBindingError, parseAccessTokenAuthorization, verifyJwsAccessToken, } from "better-auth/oauth2";
3
3
  import { admin, emailOTP, jwt, magicLink } from "better-auth/plugins";
4
4
  import { createAccessControl } from "better-auth/plugins/access";
5
5
  import { defaultStatements } from "better-auth/plugins/admin/access";
6
6
  import { genericOAuth, } from "better-auth/plugins/generic-oauth";
7
7
  import { splitSetCookieHeader } from "better-auth/cookies";
8
8
  import { oauthProvider } from "@better-auth/oauth-provider";
9
+ import { mcp } from "@better-auth/mcp";
10
+ import { cimd } from "@better-auth/cimd";
11
+ import { decodeMemberCursor, encodeMemberCursor } from "@aotter/mantle-admin";
9
12
  import { STAFF_ROLES } from "@aotter/mantle-spec";
13
+ export { decodeMemberCursor, encodeMemberCursor };
10
14
  export { STAFF_ROLES };
11
15
  /**
12
16
  * Set lookup for "is this role string a staff role?" — handlers/MCP
@@ -164,14 +168,9 @@ export function buildGenericOAuthProviders(methods) {
164
168
  }
165
169
  out.push({
166
170
  providerId: method.providerId,
167
- ...(method.displayName ? { displayName: method.displayName } : {}),
168
171
  clientId: method.clientId,
169
172
  ...(method.clientSecret ? { clientSecret: method.clientSecret } : {}),
170
173
  ...(method.discoveryUrl ? { discoveryUrl: method.discoveryUrl } : {}),
171
- ...(method.issuer ? { issuer: method.issuer } : {}),
172
- ...(method.requireIssuerValidation !== undefined
173
- ? { requireIssuerValidation: method.requireIssuerValidation }
174
- : {}),
175
174
  ...(method.authorizationUrl ? { authorizationUrl: method.authorizationUrl } : {}),
176
175
  ...(method.tokenUrl ? { tokenUrl: method.tokenUrl } : {}),
177
176
  ...(method.userInfoUrl ? { userInfoUrl: method.userInfoUrl } : {}),
@@ -188,6 +187,7 @@ export function buildGenericOAuthProviders(methods) {
188
187
  resource: method.resource,
189
188
  authorizationUrlParams: { resource: method.resource },
190
189
  tokenUrlParams: { resource: method.resource },
190
+ refreshTokenParams: { resource: method.resource },
191
191
  }
192
192
  : {}),
193
193
  });
@@ -304,15 +304,11 @@ export function shouldPromoteToOwner(rule, user) {
304
304
  export function guardGithubLoginProfile(user, context, methods) {
305
305
  if (!("githubLogin" in user))
306
306
  return undefined;
307
- const providerId = context?.path === "/callback/:id"
308
- ? context.params?.id
309
- : context?.path === "/oauth2/callback/:providerId"
310
- ? context.params?.providerId
311
- : null;
307
+ const providerId = context?.path === "/callback/:id" ? context.params?.id : null;
312
308
  const trusted = typeof providerId === "string" && methods.some((method) => method.kind === "social"
313
309
  ? context?.path === "/callback/:id" && method.provider === "github" && providerId === "github"
314
310
  : method.kind === "oauth" &&
315
- context?.path === "/oauth2/callback/:providerId" &&
311
+ context?.path === "/callback/:id" &&
316
312
  method.providerId === providerId &&
317
313
  Boolean(method.mapProfileToUser));
318
314
  return trusted ? undefined : { data: { githubLogin: null } };
@@ -356,107 +352,20 @@ export function buildTrustedOriginsFor(methods, configured = []) {
356
352
  function methodsRequireSameSiteNone(methods) {
357
353
  return methods.some((m) => m.kind === "social" && m.provider === "apple");
358
354
  }
359
- /**
360
- * Better Auth 1.6.x declares generic-OAuth URL parameter options but
361
- * its provider implementation does not replay them across every token
362
- * lifecycle step. Patch only resource-configured generic providers at
363
- * the provider boundary, using Better Auth's public OAuth helpers.
364
- */
365
- /** @internal exported for lifecycle regression tests. */
366
- export function buildOAuthResourceLifecyclePlugin(methods) {
367
- const configured = new Map(methods
368
- .filter((method) => method.kind === "oauth" && typeof method.resource === "string")
369
- .map((method) => [method.providerId, method]));
370
- if (configured.size === 0)
371
- return null;
372
- return {
373
- id: "mantle-oauth-resource-lifecycle",
374
- init(ctx) {
375
- const tokenEndpoints = new Map();
376
- const resolveTokenEndpoint = (method) => {
377
- if (method.tokenUrl)
378
- return Promise.resolve(method.tokenUrl);
379
- const existing = tokenEndpoints.get(method.providerId);
380
- if (existing)
381
- return existing;
382
- const pending = (async () => {
383
- if (!method.discoveryUrl) {
384
- throw new Error(`OAuth provider '${method.providerId}' has no token endpoint.`);
385
- }
386
- const response = await fetch(method.discoveryUrl, {
387
- headers: { accept: "application/json" },
388
- redirect: "error",
389
- });
390
- if (!response.ok) {
391
- throw new Error(`OAuth discovery for '${method.providerId}' returned ${response.status}.`);
392
- }
393
- const discovery = (await response.json());
394
- if (typeof discovery.token_endpoint !== "string") {
395
- throw new Error(`OAuth discovery for '${method.providerId}' omitted token_endpoint.`);
396
- }
397
- return discovery.token_endpoint;
398
- })();
399
- tokenEndpoints.set(method.providerId, pending);
400
- return pending;
401
- };
402
- return {
403
- context: {
404
- socialProviders: ctx.socialProviders.map((provider) => {
405
- const method = configured.get(provider.id);
406
- if (!method?.resource)
407
- return provider;
408
- const createAuthorizationURL = provider.createAuthorizationURL.bind(provider);
409
- return {
410
- ...provider,
411
- async createAuthorizationURL(data) {
412
- const url = await createAuthorizationURL(data);
413
- url.searchParams.set("resource", method.resource);
414
- return url;
415
- },
416
- async validateAuthorizationCode(data) {
417
- const tokens = await validateAuthorizationCode({
418
- code: data.code,
419
- codeVerifier: data.codeVerifier,
420
- redirectURI: data.redirectURI,
421
- options: {
422
- clientId: method.clientId,
423
- clientSecret: method.clientSecret,
424
- redirectURI: method.redirectURI,
425
- },
426
- tokenEndpoint: await resolveTokenEndpoint(method),
427
- authentication: method.authentication,
428
- resource: method.resource,
429
- });
430
- return applyDefaultAccessTokenExpiry(tokens, undefined);
431
- },
432
- async refreshAccessToken(refreshTokenValue) {
433
- const tokens = await refreshAccessToken({
434
- refreshToken: refreshTokenValue,
435
- options: {
436
- clientId: method.clientId,
437
- clientSecret: method.clientSecret,
438
- },
439
- tokenEndpoint: await resolveTokenEndpoint(method),
440
- authentication: method.authentication,
441
- extraParams: { resource: method.resource },
442
- });
443
- return applyDefaultAccessTokenExpiry(tokens, undefined);
444
- },
445
- };
446
- }),
447
- },
448
- };
449
- },
450
- };
451
- }
452
355
  /** @internal exported for provider-option mapping tests. */
453
356
  export function buildOAuthProviderOptions(config) {
454
357
  return {
455
358
  loginPage: config.loginPage,
456
359
  consentPage: config.consentPage,
457
360
  ...(config.scopes ? { scopes: [...config.scopes] } : {}),
458
- ...(config.validAudiences
459
- ? { validAudiences: [...config.validAudiences] }
361
+ ...(config.resources
362
+ ? { resources: [...config.resources] }
363
+ : {}),
364
+ ...(config.mcpResource
365
+ ? {
366
+ clientRegistrationClientSecretExpiration: "90d",
367
+ allowPublicClientPrelogin: true,
368
+ }
460
369
  : {}),
461
370
  ...(config.allowDynamicClientRegistration !== undefined
462
371
  ? { allowDynamicClientRegistration: config.allowDynamicClientRegistration }
@@ -500,7 +409,9 @@ function buildAuth(config) {
500
409
  const bootstrap = config.bootstrapOwner;
501
410
  const emailOtpMethod = pickSingleton(config.methods, "email-otp");
502
411
  const magicLinkMethod = pickSingleton(config.methods, "magic-link");
503
- const resourceLifecyclePlugin = buildOAuthResourceLifecyclePlugin(config.methods);
412
+ const providerOptions = config.oauthProvider
413
+ ? buildOAuthProviderOptions(config.oauthProvider)
414
+ : null;
504
415
  // Rate limit: Better Auth's per-route limits gate on
505
416
  // `process.env.NODE_ENV === "production"`, which is unset on
506
417
  // Cloudflare Workers — leaving the limits silently off. When any
@@ -533,17 +444,33 @@ function buildAuth(config) {
533
444
  ...(genericOAuthProviders.length > 0
534
445
  ? [
535
446
  genericOAuth({
536
- config: genericOAuthProviders.map(({ displayName: _displayName, ...provider }) => provider),
447
+ config: genericOAuthProviders,
537
448
  }),
538
449
  ]
539
450
  : []),
540
- ...(resourceLifecyclePlugin ? [resourceLifecyclePlugin] : []),
541
451
  ...(emailOtpMethod ? [buildEmailOTPPlugin(emailOtpMethod)] : []),
542
452
  ...(magicLinkMethod ? [buildMagicLinkPlugin(magicLinkMethod)] : []),
543
- ...(config.oauthProvider
453
+ ...(config.oauthProvider && providerOptions
544
454
  ? [
545
455
  jwt(),
546
- oauthProvider(buildOAuthProviderOptions(config.oauthProvider)),
456
+ config.oauthProvider.mcpResource
457
+ ? mcp({
458
+ ...providerOptions,
459
+ resource: config.oauthProvider.mcpResource,
460
+ })
461
+ : oauthProvider(providerOptions),
462
+ ...(config.oauthProvider.mcpResource
463
+ ? [
464
+ cimd({
465
+ // Cloudflare's `global_fetch_strictly_public` flag is the
466
+ // runtime network boundary: resolution and connection stay
467
+ // on the public Internet. Better Auth owns timeout, limits,
468
+ // validation, caching, and redirect rejection above it.
469
+ fetchClientMetadataResource: (input, init) => fetch(input, { ...init, redirect: "error" }),
470
+ metadataProfile: "mcp-2026-07-28",
471
+ }),
472
+ ]
473
+ : []),
547
474
  ]
548
475
  : []),
549
476
  ];
@@ -638,47 +565,63 @@ function buildAuth(config) {
638
565
  databaseHooks,
639
566
  });
640
567
  }
641
- const MEMBER_CURSOR_PREFIX = "m:";
642
- /** @internal Exported for endpoint and regression-test validation. */
643
- export function encodeMemberCursor(createdAt, id) {
644
- return `${MEMBER_CURSOR_PREFIX}${encodeURIComponent(JSON.stringify([createdAt, id]))}`;
645
- }
646
- /** @internal Exported for endpoint and regression-test validation. */
647
- export function decodeMemberCursor(cursor) {
648
- if (!cursor.startsWith(MEMBER_CURSOR_PREFIX))
649
- return null;
650
- try {
651
- const value = JSON.parse(decodeURIComponent(cursor.slice(MEMBER_CURSOR_PREFIX.length)));
652
- return Array.isArray(value) && value.length === 2 &&
653
- typeof value[0] === "string" && !Number.isNaN(Date.parse(value[0])) &&
654
- typeof value[1] === "string" && value[1]
655
- ? [value[0], value[1]]
656
- : null;
657
- }
658
- catch {
659
- return null;
660
- }
661
- }
662
568
  const SETUP_INCOMPLETE_AUTHS = new WeakSet();
663
569
  /** True only for the fail-closed facade returned by createSetupIncompleteAuth. */
664
570
  export function isSetupIncompleteAuth(auth) {
665
571
  return SETUP_INCOMPLETE_AUTHS.has(auth);
666
572
  }
573
+ const LEGACY_DCR_TTL_MS = 90 * 24 * 60 * 60 * 1_000;
574
+ const LEGACY_DCR_CLEANUP_INTERVAL_MS = 60 * 60 * 1_000;
667
575
  export function createAuth(config) {
668
576
  const auth = buildAuth(config);
669
577
  const basePath = normalizeAuthBasePath(config.basePath);
670
578
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
671
579
  const api = auth.api;
672
580
  const localJwksCacheKey = {};
581
+ let dpopReplayStore = null;
582
+ const getDpopReplayStore = async () => {
583
+ if (dpopReplayStore)
584
+ return dpopReplayStore;
585
+ const context = await auth.$context;
586
+ dpopReplayStore = createDpopReplayStore(context.internalAdapter);
587
+ return dpopReplayStore;
588
+ };
673
589
  const verifyAccessToken = config.oauthProvider
674
590
  ? async (token, audience) => {
675
591
  const context = await auth.$context;
676
592
  return verifyOAuthJwtWithLocalJwks(token, audience, context.baseURL, async () => api.getJwks(), localJwksCacheKey);
677
593
  }
678
594
  : null;
595
+ let nextDcrCleanupAt = 0;
596
+ const pruneExpiredDynamicClients = async () => {
597
+ const now = Date.now();
598
+ if (!config.oauthProvider?.mcpResource || now < nextDcrCleanupAt)
599
+ return;
600
+ // Set before awaiting so concurrent OAuth requests do not fan out writes.
601
+ nextDcrCleanupAt = now + LEGACY_DCR_CLEANUP_INTERVAL_MS;
602
+ try {
603
+ await config.database
604
+ .prepare("DELETE FROM oauthClient WHERE clientDiscoveryId IS NULL AND userId IS NULL AND referenceId IS NULL AND createdAt < ?")
605
+ .bind(new Date(now - LEGACY_DCR_TTL_MS).toISOString())
606
+ .run();
607
+ }
608
+ catch (error) {
609
+ // Cleanup is bounded storage hygiene, not an authorization decision.
610
+ console.error("[better-auth] legacy DCR cleanup failed", error);
611
+ }
612
+ };
679
613
  return {
680
614
  basePath,
681
- handler: async (request) => normalizeAuthResponseCookies(await auth.handler(request)),
615
+ ...(config.oauthProvider?.mcpResource
616
+ ? { mcpResource: config.oauthProvider.mcpResource }
617
+ : {}),
618
+ handler: async (request) => {
619
+ const pathname = new URL(request.url).pathname;
620
+ if (pathname.startsWith(`${basePath}/oauth2/`)) {
621
+ await pruneExpiredDynamicClients();
622
+ }
623
+ return normalizeAuthResponseCookies(await auth.handler(request));
624
+ },
682
625
  getSession: (request) => api.getSession({ headers: request.headers }).then((r) => r ?? null),
683
626
  getUserRole: async (userId) => {
684
627
  const row = await config.database
@@ -709,9 +652,76 @@ export function createAuth(config) {
709
652
  createdAt,
710
653
  };
711
654
  },
712
- getProviderAccessToken: (request, providerId) => getProviderAccessTokenForRequest(api, request, providerId),
655
+ getProviderAccessToken: async (request, providerId) => {
656
+ const session = await api.getSession({ headers: request.headers });
657
+ const userId = session?.user?.id;
658
+ const account = userId
659
+ ? await config.database
660
+ .prepare("SELECT id FROM account WHERE userId = ? AND providerId = ? LIMIT 1")
661
+ .bind(userId, providerId)
662
+ .first()
663
+ : null;
664
+ if (!account) {
665
+ throw new Error(`getProviderAccessToken: provider '${providerId}' is not linked to the current user.`);
666
+ }
667
+ return getProviderAccessTokenForRequest(api, request, account.id, providerId);
668
+ },
713
669
  verifyOAuthAccessToken: async (tokenOrRequest, options) => {
714
- return verifyOAuthJwt(tokenOrRequest, options, verifyAccessToken);
670
+ return verifyOAuthJwt(tokenOrRequest, options, verifyAccessToken, getDpopReplayStore);
671
+ },
672
+ getOAuthConsentRequest: async (request) => {
673
+ if (!config.oauthProvider)
674
+ return null;
675
+ const url = new URL(request.url);
676
+ const clientId = url.searchParams.get("client_id");
677
+ if (!clientId || !url.search)
678
+ return null;
679
+ const oauthQuery = url.search.slice(1);
680
+ const client = await api.getOAuthClientPublicPrelogin({
681
+ headers: request.headers,
682
+ body: { client_id: clientId, oauth_query: oauthQuery },
683
+ });
684
+ const redirectUri = url.searchParams.get("redirect_uri") ??
685
+ (Array.isArray(client?.redirect_uris) &&
686
+ typeof client.redirect_uris[0] === "string"
687
+ ? client.redirect_uris[0]
688
+ : "");
689
+ return {
690
+ clientName: typeof client?.client_name === "string"
691
+ ? client.client_name
692
+ : clientId,
693
+ redirectUri,
694
+ scopes: (url.searchParams.get("scope") ?? "")
695
+ .split(/\s+/u)
696
+ .filter(Boolean),
697
+ oauthQuery,
698
+ };
699
+ },
700
+ completeOAuthConsent: async (request, accept) => {
701
+ if (!config.oauthProvider) {
702
+ throw new Error("completeOAuthConsent: oauthProvider is not configured.");
703
+ }
704
+ const form = await request.formData();
705
+ const oauthQuery = form.get("oauth_query");
706
+ if (typeof oauthQuery !== "string" || oauthQuery.length === 0) {
707
+ throw new Error("completeOAuthConsent: oauth_query is missing.");
708
+ }
709
+ const headers = new Headers(request.headers);
710
+ headers.set("content-type", "application/json");
711
+ headers.delete("content-length");
712
+ const response = await auth.handler(new Request(new URL(`${basePath}/oauth2/consent`, request.url), {
713
+ method: "POST",
714
+ headers,
715
+ body: JSON.stringify({ accept, oauth_query: oauthQuery }),
716
+ }));
717
+ if (!response.ok) {
718
+ throw new Error(`completeOAuthConsent: Better Auth returned ${response.status}.`);
719
+ }
720
+ const result = await response.json();
721
+ if (!result || typeof result.url !== "string") {
722
+ throw new Error("completeOAuthConsent: Better Auth omitted the redirect URL.");
723
+ }
724
+ return result.url;
715
725
  },
716
726
  methods: config.methods.map((m) => {
717
727
  switch (m.kind) {
@@ -870,7 +880,7 @@ export function createAuth(config) {
870
880
  : {}),
871
881
  ...(input.grantTypes ? { grant_types: [...input.grantTypes] } : {}),
872
882
  ...(input.responseTypes ? { response_types: [...input.responseTypes] } : {}),
873
- ...(input.type ? { type: input.type } : {}),
883
+ ...(input.applicationType ? { application_type: input.applicationType } : {}),
874
884
  ...(input.skipConsent !== undefined ? { skip_consent: input.skipConsent } : {}),
875
885
  ...(input.enableEndSession !== undefined
876
886
  ? { enable_end_session: input.enableEndSession }
@@ -909,6 +919,10 @@ export function createSetupIncompleteAuth(options = {}) {
909
919
  status: 401,
910
920
  reason: "invalid-token",
911
921
  }),
922
+ getOAuthConsentRequest: async () => null,
923
+ completeOAuthConsent: async () => {
924
+ throw new Error(message);
925
+ },
912
926
  methods: [],
913
927
  listLinkedAccounts: async () => [],
914
928
  unlinkAccount: async () => false,
@@ -926,21 +940,12 @@ export function createSetupIncompleteAuth(options = {}) {
926
940
  SETUP_INCOMPLETE_AUTHS.add(auth);
927
941
  return auth;
928
942
  }
929
- function bearerTokenFrom(tokenOrRequest) {
930
- if (typeof tokenOrRequest === "string")
931
- return tokenOrRequest.trim() || null;
932
- const header = tokenOrRequest.headers.get("authorization");
933
- if (!header)
934
- return null;
935
- const match = /^Bearer ([^\s]+)$/i.exec(header);
936
- return match?.[1] ?? null;
937
- }
938
943
  /** @internal exported to pin the session-bound Better Auth request and
939
944
  * secret-minimizing response mapping. */
940
- export async function getProviderAccessTokenForRequest(api, request, providerId) {
945
+ export async function getProviderAccessTokenForRequest(api, request, accountId, providerId) {
941
946
  const value = (await api.getAccessToken({
942
947
  headers: request.headers,
943
- body: { providerId },
948
+ body: { accountId },
944
949
  }));
945
950
  if (typeof value?.accessToken !== "string") {
946
951
  throw new Error(`getProviderAccessToken: provider '${providerId}' returned no access token.`);
@@ -973,8 +978,10 @@ export function verifyOAuthJwtWithLocalJwks(token, audience, issuer, jwksFetch,
973
978
  }
974
979
  /** @internal exported to pin the stable facade's normalization and
975
980
  * 401/403 contract independently of Better Auth network/JWKS I/O. */
976
- export async function verifyOAuthJwt(tokenOrRequest, options, verify) {
977
- const token = bearerTokenFrom(tokenOrRequest);
981
+ export async function verifyOAuthJwt(tokenOrRequest, options, verify, getDpopReplayStore) {
982
+ const request = typeof tokenOrRequest === "string" ? null : tokenOrRequest;
983
+ const authorization = parseAccessTokenAuthorization(request?.headers.get("authorization") ?? `Bearer ${tokenOrRequest}`);
984
+ const token = authorization?.token;
978
985
  // JWT compact serialization has exactly three non-empty parts.
979
986
  // Reject opaque tokens before any network/JWKS work.
980
987
  if (!verify ||
@@ -985,6 +992,16 @@ export async function verifyOAuthJwt(tokenOrRequest, options, verify) {
985
992
  }
986
993
  try {
987
994
  const claims = await verify(token, options.audience);
995
+ await enforceDpopBinding({
996
+ payload: claims,
997
+ authorization,
998
+ proofJwt: request?.headers.get("dpop"),
999
+ method: request?.method ?? "GET",
1000
+ url: request?.url ?? options.audience,
1001
+ ...(request && authorization.scheme === "DPoP" && getDpopReplayStore
1002
+ ? { replayStore: await getDpopReplayStore() }
1003
+ : {}),
1004
+ });
988
1005
  if (typeof claims["sub"] !== "string" || claims["sub"].length === 0) {
989
1006
  return { ok: false, status: 401, reason: "invalid-token" };
990
1007
  }
@@ -1006,7 +1023,10 @@ export async function verifyOAuthJwt(tokenOrRequest, options, verify) {
1006
1023
  scopes,
1007
1024
  };
1008
1025
  }
1009
- catch {
1026
+ catch (error) {
1027
+ if (isDpopBindingError(error)) {
1028
+ return { ok: false, status: 401, reason: "invalid-dpop-proof" };
1029
+ }
1010
1030
  return { ok: false, status: 401, reason: "invalid-token" };
1011
1031
  }
1012
1032
  }
@@ -1018,7 +1038,7 @@ export function mapRegisteredOAuthClient(value) {
1018
1038
  }
1019
1039
  return {
1020
1040
  clientId: row.client_id,
1021
- ...(row.client_secret && row.public !== true && row.token_endpoint_auth_method !== "none"
1041
+ ...(row.client_secret && row.token_endpoint_auth_method !== "none"
1022
1042
  ? { clientSecret: row.client_secret }
1023
1043
  : {}),
1024
1044
  redirectUris: row.redirect_uris,
@@ -1028,8 +1048,7 @@ export function mapRegisteredOAuthClient(value) {
1028
1048
  ...(row.token_endpoint_auth_method
1029
1049
  ? { tokenEndpointAuthMethod: row.token_endpoint_auth_method }
1030
1050
  : {}),
1031
- ...(row.type ? { type: row.type } : {}),
1032
- ...(row.public !== undefined ? { public: row.public } : {}),
1051
+ ...(row.application_type ? { applicationType: row.application_type } : {}),
1033
1052
  };
1034
1053
  }
1035
1054
  /** Random 32-char alphanumeric id, shaped like Better Auth's own user