@better-auth/oauth-provider 1.6.9 → 1.6.11

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.
@@ -1,4 +1,4 @@
1
- import { a as ResourceServerMetadata } from "./oauth-DMjvJjmR.mjs";
1
+ import { s as ResourceServerMetadata } from "./oauth-BqWgUea8.mjs";
2
2
  import { JWTPayload, JWTVerifyOptions } from "jose";
3
3
  import { Auth } from "better-auth/types";
4
4
 
@@ -1,5 +1,5 @@
1
- import { a as getJwtPlugin, o as getOAuthProviderPlugin, y as handleMcpErrors } from "./utils-B9Pj9EPf.mjs";
2
- import { t as PACKAGE_VERSION } from "./version-ittBKKvy.mjs";
1
+ import { S as handleMcpErrors, a as getOAuthProviderPlugin, i as getJwtPlugin } from "./utils-LAthGy-x.mjs";
2
+ import { t as PACKAGE_VERSION } from "./version-CRjaDWWg.mjs";
3
3
  import { verifyAccessToken } from "better-auth/oauth2";
4
4
  import { APIError } from "better-call";
5
5
  import { logger } from "@better-auth/core/env";
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as oauthProvider } from "./oauth-By0LyEmY.mjs";
1
+ import { n as oauthProvider } from "./oauth-C4GaGx2I.mjs";
2
2
  import * as _better_fetch_fetch0 from "@better-fetch/fetch";
3
3
 
4
4
  //#region src/client.d.ts
package/dist/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-ittBKKvy.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-CRjaDWWg.mjs";
2
2
  import { safeJSONParse } from "@better-auth/core/utils/json";
3
3
  //#region src/client.ts
4
4
  function parseSignedQuery(search) {
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as Awaitable, a as ResourceServerMetadata, c as OAuthConsent, d as OAuthRefreshToken, f as Prompt, g as VerificationValue, h as StoreTokenType, i as OIDCMetadata, l as OAuthOpaqueAccessToken, m as Scope, n as GrantType, o as AuthorizePrompt, p as SchemaClient, r as OAuthClient, s as OAuthAuthorizationQuery, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-DMjvJjmR.mjs";
2
- import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-By0LyEmY.mjs";
1
+ import { _ as Scope, a as OAuthClient, b as Awaitable, c as TokenEndpointAuthMethod, d as OAuthConsent, f as OAuthOpaqueAccessToken, g as SchemaClient, h as Prompt, i as GrantType, l as AuthorizePrompt, m as OAuthRefreshToken, n as AuthServerMetadata, o as OIDCMetadata, p as OAuthOptions, r as BearerMethodsSupported, s as ResourceServerMetadata, t as AuthMethod, u as OAuthAuthorizationQuery, v as StoreTokenType, y as VerificationValue } from "./oauth-BqWgUea8.mjs";
2
+ import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-C4GaGx2I.mjs";
3
3
  import { verifyAccessToken } from "better-auth/oauth2";
4
4
  import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
5
5
  import { JWTPayload } from "jose";
@@ -61,4 +61,4 @@ declare const oauthProviderOpenIdConfigMetadata: <Auth extends {
61
61
  headers?: HeadersInit;
62
62
  }) => (request: Request) => Promise<Response>;
63
63
  //#endregion
64
- export { AuthServerMetadata, AuthorizePrompt, OAuthAuthorizationQuery, OAuthClient, OAuthConsent, OAuthOpaqueAccessToken, OAuthOptions, OAuthRefreshToken, OIDCMetadata, Prompt, ResourceServerMetadata, SchemaClient, Scope, StoreTokenType, VerificationValue, authServerMetadata, getOAuthProviderState, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
64
+ export { AuthMethod, AuthServerMetadata, AuthorizePrompt, BearerMethodsSupported, GrantType, OAuthAuthorizationQuery, OAuthClient, OAuthConsent, OAuthOpaqueAccessToken, OAuthOptions, OAuthRefreshToken, OIDCMetadata, Prompt, ResourceServerMetadata, SchemaClient, Scope, StoreTokenType, TokenEndpointAuthMethod, VerificationValue, authServerMetadata, getOAuthProviderState, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as validateClientCredentials, a as getJwtPlugin, b as mcpHandler, c as isPKCERequired, d as parsePrompt, f as resolveSessionAuthTime, g as storeToken, h as storeClientSecret, i as getClient, l as normalizeTimestampValue, m as searchParamsToQuery, n as decryptStoredClientSecret, p as resolveSubjectIdentifier, r as deleteFromPrompt, s as getStoredToken, t as basicToClientCredentials, u as parseClientMetadata, v as verifyOAuthQueryParams } from "./utils-B9Pj9EPf.mjs";
2
- import { t as PACKAGE_VERSION } from "./version-ittBKKvy.mjs";
1
+ import { C as mcpHandler, _ as signedQueryIssuedAtParam, b as validateClientCredentials, c as isPKCERequired, d as parsePrompt, f as postLoginClearedParam, g as searchParamsToQuery, h as resolveSubjectIdentifier, i as getJwtPlugin, l as normalizeTimestampValue, m as resolveSessionAuthTime, n as decryptStoredClientSecret, o as getSignedQueryIssuedAt, p as removePromptFromQuery, r as getClient, s as getStoredToken, t as basicToClientCredentials, u as parseClientMetadata, v as storeClientSecret, x as verifyOAuthQueryParams, y as storeToken } from "./utils-LAthGy-x.mjs";
2
+ import { t as PACKAGE_VERSION } from "./version-CRjaDWWg.mjs";
3
3
  import { APIError, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
4
4
  import { generateCodeChallenge, getJwks, verifyJwsAccessToken } from "better-auth/oauth2";
5
5
  import { APIError as APIError$1 } from "better-call";
@@ -16,7 +16,8 @@ import { signJWT, toExpJWT } from "better-auth/plugins";
16
16
  import { SignJWT, compactVerify, createLocalJWKSet, decodeJwt } from "jose";
17
17
  //#region src/consent.ts
18
18
  async function consentEndpoint(ctx, opts) {
19
- const _query = (await oAuthState.get())?.query;
19
+ const oauthRequest = await oAuthState.get();
20
+ const _query = oauthRequest?.query;
20
21
  if (!_query) throw new APIError("BAD_REQUEST", {
21
22
  error_description: "missing oauth query",
22
23
  error: "invalid_request"
@@ -40,6 +41,17 @@ async function consentEndpoint(ctx, opts) {
40
41
  url: formatErrorURL(query.get("redirect_uri") ?? "", "access_denied", "User denied access", query.get("state") ?? void 0, getIssuer(ctx, opts))
41
42
  };
42
43
  const session = await getSessionFromCtx(ctx);
44
+ const hasLoginPrompt = parsePrompt(query.get("prompt") ?? "").has("login");
45
+ const hasSatisfiedLoginPrompt = hasLoginPrompt && sessionSatisfiesLoginPrompt(session?.session.createdAt, oauthRequest?.signedQueryIssuedAt);
46
+ if (hasLoginPrompt && !hasSatisfiedLoginPrompt) {
47
+ ctx?.headers?.set("accept", "application/json");
48
+ ctx.query = searchParamsToQuery(query);
49
+ const { url } = await authorizeEndpoint(ctx, opts);
50
+ return {
51
+ redirect: true,
52
+ url
53
+ };
54
+ }
43
55
  const referenceId = await opts.postLogin?.consentReferenceId?.({
44
56
  user: session?.user,
45
57
  session: session?.session,
@@ -90,14 +102,21 @@ async function consentEndpoint(ctx, opts) {
90
102
  });
91
103
  if (requestedScopes) query.set("scope", consent.scopes.join(" "));
92
104
  ctx?.headers?.set("accept", "application/json");
93
- ctx.query = deleteFromPrompt(query, "consent");
94
- ctx.context.postLogin = true;
95
- const { url } = await authorizeEndpoint(ctx, opts);
105
+ let authorizationQuery = removePromptFromQuery(query, "consent");
106
+ if (hasSatisfiedLoginPrompt) authorizationQuery = removePromptFromQuery(authorizationQuery, "login");
107
+ ctx.query = searchParamsToQuery(authorizationQuery);
108
+ const { url } = await authorizeEndpoint(ctx, opts, { postLogin: oauthRequest?.postLoginClearedForSession !== void 0 && oauthRequest.postLoginClearedForSession === session?.session.id });
96
109
  return {
97
110
  redirect: true,
98
111
  url
99
112
  };
100
113
  }
114
+ function sessionSatisfiesLoginPrompt(sessionCreatedAt, signedQueryIssuedAt) {
115
+ if (!signedQueryIssuedAt) return false;
116
+ const normalized = normalizeTimestampValue(sessionCreatedAt);
117
+ if (!normalized) return false;
118
+ return normalized.getTime() >= signedQueryIssuedAt.getTime();
119
+ }
101
120
  //#endregion
102
121
  //#region src/continue.ts
103
122
  async function continueEndpoint(ctx, opts) {
@@ -116,7 +135,7 @@ async function selected(ctx, opts) {
116
135
  error: "invalid_request"
117
136
  });
118
137
  ctx.headers?.set("accept", "application/json");
119
- ctx.query = deleteFromPrompt(new URLSearchParams(_query), "select_account");
138
+ ctx.query = searchParamsToQuery(removePromptFromQuery(new URLSearchParams(_query), "select_account"));
120
139
  const { url } = await authorizeEndpoint(ctx, opts);
121
140
  return {
122
141
  redirect: true,
@@ -131,7 +150,7 @@ async function created(ctx, opts) {
131
150
  });
132
151
  const query = new URLSearchParams(_query);
133
152
  ctx.headers?.set("accept", "application/json");
134
- ctx.query = deleteFromPrompt(query, "create");
153
+ ctx.query = searchParamsToQuery(removePromptFromQuery(query, "create"));
135
154
  const { url } = await authorizeEndpoint(ctx, opts);
136
155
  return {
137
156
  redirect: true,
@@ -418,33 +437,95 @@ async function createOpaqueAccessToken(ctx, opts, user, client, scopes, payload,
418
437
  });
419
438
  return (opts.prefix?.opaqueAccessToken ?? "") + token;
420
439
  }
440
+ /**
441
+ * Tear down the entire refresh-token family for a (client, user) pair, plus
442
+ * any access tokens that reference those refresh rows, per RFC 9700 §4.14.
443
+ * Access tokens are deleted first so the parent rows' foreign-key children
444
+ * do not block the refresh-row delete.
445
+ *
446
+ * TODO(invalidate-family-race): the two `deleteMany` calls are not atomic
447
+ * with respect to each other. Between them, a concurrent rotation in a
448
+ * different worker can `create` a fresh refresh row (and, immediately after,
449
+ * an access-token row referencing it) for the same (client, user) pair,
450
+ * leaving the family partially rebuilt and the new refresh row orphaned of
451
+ * any deletion. Closing this window requires the same transactional adapter
452
+ * contract tracked under FIXME(strict-family-invalidation) in
453
+ * `createRefreshToken`.
454
+ *
455
+ * @internal
456
+ */
457
+ async function invalidateRefreshFamily(ctx, clientId, userId) {
458
+ const refreshTokens = await ctx.context.adapter.findMany({
459
+ model: "oauthRefreshToken",
460
+ where: [{
461
+ field: "clientId",
462
+ value: clientId
463
+ }, {
464
+ field: "userId",
465
+ value: userId
466
+ }]
467
+ });
468
+ if (refreshTokens.length) await ctx.context.adapter.deleteMany({
469
+ model: "oauthAccessToken",
470
+ where: [{
471
+ field: "refreshId",
472
+ operator: "in",
473
+ value: refreshTokens.map((r) => r.id)
474
+ }]
475
+ });
476
+ await ctx.context.adapter.deleteMany({
477
+ model: "oauthRefreshToken",
478
+ where: [{
479
+ field: "clientId",
480
+ value: clientId
481
+ }, {
482
+ field: "userId",
483
+ value: userId
484
+ }]
485
+ });
486
+ }
421
487
  async function createRefreshToken(ctx, opts, user, referenceId, client, scopes, payload, originalRefresh, authTime) {
422
488
  const iat = payload.iat ?? Math.floor(Date.now() / 1e3);
423
489
  const exp = payload?.exp ?? iat + (opts.refreshTokenExpiresIn ?? 2592e3);
424
490
  const token = opts.generateRefreshToken ? await opts.generateRefreshToken() : generateRandomString(32, "A-Z", "a-z");
425
491
  const sessionId = payload?.sid;
426
- if (originalRefresh?.id) await ctx.context.adapter.update({
492
+ const newRow = {
493
+ token: await storeToken(opts.storeTokens, token, "refresh_token"),
494
+ clientId: client.clientId,
495
+ sessionId,
496
+ userId: user.id,
497
+ referenceId,
498
+ authTime,
499
+ scopes,
500
+ createdAt: /* @__PURE__ */ new Date(iat * 1e3),
501
+ expiresAt: /* @__PURE__ */ new Date(exp * 1e3)
502
+ };
503
+ if (!originalRefresh?.id) return {
504
+ id: (await ctx.context.adapter.create({
505
+ model: "oauthRefreshToken",
506
+ data: newRow
507
+ })).id,
508
+ token: await encodeRefreshToken(opts, token, sessionId)
509
+ };
510
+ if (!await ctx.context.adapter.update({
427
511
  model: "oauthRefreshToken",
428
512
  where: [{
429
513
  field: "id",
430
514
  value: originalRefresh.id
515
+ }, {
516
+ field: "revoked",
517
+ operator: "eq",
518
+ value: null
431
519
  }],
432
520
  update: { revoked: /* @__PURE__ */ new Date(iat * 1e3) }
521
+ })) throw new APIError("BAD_REQUEST", {
522
+ error_description: "invalid refresh token",
523
+ error: "invalid_grant"
433
524
  });
434
525
  return {
435
526
  id: (await ctx.context.adapter.create({
436
527
  model: "oauthRefreshToken",
437
- data: {
438
- token: await storeToken(opts.storeTokens, token, "refresh_token"),
439
- clientId: client.clientId,
440
- sessionId,
441
- userId: user.id,
442
- referenceId,
443
- authTime,
444
- scopes,
445
- createdAt: /* @__PURE__ */ new Date(iat * 1e3),
446
- expiresAt: /* @__PURE__ */ new Date(exp * 1e3)
447
- }
528
+ data: newRow
448
529
  })).id,
449
530
  token: await encodeRefreshToken(opts, token, sessionId)
450
531
  };
@@ -522,15 +603,14 @@ async function createUserTokens(ctx, opts, params) {
522
603
  }
523
604
  /** Checks verification value */
524
605
  async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri) {
525
- const verification = await ctx.context.internalAdapter.findVerificationValue(await storeToken(opts.storeTokens, code, "authorization_code"));
606
+ const verification = await ctx.context.internalAdapter.consumeVerificationValue(await storeToken(opts.storeTokens, code, "authorization_code"));
526
607
  if (!verification) throw new APIError("UNAUTHORIZED", {
527
608
  error_description: "Invalid code",
528
- error: "invalid_verification"
609
+ error: "invalid_grant"
529
610
  });
530
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(await storeToken(opts.storeTokens, code, "authorization_code"));
531
611
  if (!verification.expiresAt || verification.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("UNAUTHORIZED", {
532
612
  error_description: "code expired",
533
- error: "invalid_verification"
613
+ error: "invalid_grant"
534
614
  });
535
615
  let rawValue;
536
616
  try {
@@ -538,13 +618,13 @@ async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri)
538
618
  } catch {
539
619
  throw new APIError("UNAUTHORIZED", {
540
620
  error_description: "malformed verification value",
541
- error: "invalid_verification"
621
+ error: "invalid_grant"
542
622
  });
543
623
  }
544
624
  const parsed = verificationValueSchema.safeParse(rawValue);
545
625
  if (!parsed.success) throw new APIError("UNAUTHORIZED", {
546
626
  error_description: "malformed verification value",
547
- error: "invalid_verification"
627
+ error: "invalid_grant"
548
628
  });
549
629
  const verificationValue = parsed.data;
550
630
  if (verificationValue.query.client_id !== client_id) throw new APIError("UNAUTHORIZED", {
@@ -745,16 +825,7 @@ async function handleRefreshTokenGrant(ctx, opts) {
745
825
  error: "invalid_grant"
746
826
  });
747
827
  if (refreshToken.revoked) {
748
- await ctx.context.adapter.deleteMany({
749
- model: "oauthRefreshToken",
750
- where: [{
751
- field: "clientId",
752
- value: client_id
753
- }, {
754
- field: "userId",
755
- value: refreshToken.userId
756
- }]
757
- });
828
+ await invalidateRefreshFamily(ctx, client_id, refreshToken.userId);
758
829
  throw new APIError("BAD_REQUEST", {
759
830
  error_description: "invalid refresh token",
760
831
  error: "invalid_grant"
@@ -936,7 +1007,7 @@ async function validateRefreshToken(ctx, opts, token, clientId) {
936
1007
  model: "session",
937
1008
  where: [{
938
1009
  field: "id",
939
- value: refreshToken.sessionId
1010
+ value: sessionId
940
1011
  }]
941
1012
  });
942
1013
  if (!session || session.expiresAt < /* @__PURE__ */ new Date()) sessionId = void 0;
@@ -2310,16 +2381,7 @@ async function revokeRefreshToken(ctx, opts, token, clientId) {
2310
2381
  error: "invalid_request"
2311
2382
  });
2312
2383
  if (refreshToken.revoked) {
2313
- await ctx.context.adapter.deleteMany({
2314
- model: "oauthRefreshToken",
2315
- where: [{
2316
- field: "clientId",
2317
- value: clientId
2318
- }, {
2319
- field: "userId",
2320
- value: refreshToken.userId
2321
- }]
2322
- });
2384
+ await invalidateRefreshFamily(ctx, clientId, refreshToken.userId);
2323
2385
  throw new APIError$1("BAD_REQUEST", {
2324
2386
  error_description: "refresh token revoked",
2325
2387
  error: "invalid_request"
@@ -2327,20 +2389,31 @@ async function revokeRefreshToken(ctx, opts, token, clientId) {
2327
2389
  }
2328
2390
  if (!refreshToken.clientId || refreshToken.clientId !== clientId) return null;
2329
2391
  const iat = Math.floor(Date.now() / 1e3);
2330
- await Promise.allSettled([ctx.context.adapter.deleteMany({
2331
- model: "oauthAccessToken",
2332
- where: [{
2333
- field: "refreshId",
2334
- value: refreshToken.id
2335
- }]
2336
- }), ctx.context.adapter.update({
2392
+ if (!await ctx.context.adapter.update({
2337
2393
  model: "oauthRefreshToken",
2338
2394
  where: [{
2339
2395
  field: "id",
2340
2396
  value: refreshToken.id
2397
+ }, {
2398
+ field: "revoked",
2399
+ operator: "eq",
2400
+ value: null
2341
2401
  }],
2342
2402
  update: { revoked: /* @__PURE__ */ new Date(iat * 1e3) }
2343
- })]);
2403
+ })) {
2404
+ await invalidateRefreshFamily(ctx, clientId, refreshToken.userId);
2405
+ throw new APIError$1("BAD_REQUEST", {
2406
+ error_description: "refresh token revoked",
2407
+ error: "invalid_request"
2408
+ });
2409
+ }
2410
+ await ctx.context.adapter.deleteMany({
2411
+ model: "oauthAccessToken",
2412
+ where: [{
2413
+ field: "refreshId",
2414
+ value: refreshToken.id
2415
+ }]
2416
+ });
2344
2417
  }
2345
2418
  /**
2346
2419
  * We don't know the access token format so we try to validate it
@@ -2458,7 +2531,8 @@ const schema = {
2458
2531
  references: {
2459
2532
  model: "user",
2460
2533
  field: "id"
2461
- }
2534
+ },
2535
+ index: true
2462
2536
  },
2463
2537
  createdAt: {
2464
2538
  type: "date",
@@ -2549,7 +2623,8 @@ const schema = {
2549
2623
  oauthRefreshToken: { fields: {
2550
2624
  token: {
2551
2625
  type: "string",
2552
- required: true
2626
+ required: true,
2627
+ unique: true
2553
2628
  },
2554
2629
  clientId: {
2555
2630
  type: "string",
@@ -2557,7 +2632,8 @@ const schema = {
2557
2632
  references: {
2558
2633
  model: "oauthClient",
2559
2634
  field: "clientId"
2560
- }
2635
+ },
2636
+ index: true
2561
2637
  },
2562
2638
  sessionId: {
2563
2639
  type: "string",
@@ -2566,7 +2642,8 @@ const schema = {
2566
2642
  model: "session",
2567
2643
  field: "id",
2568
2644
  onDelete: "set null"
2569
- }
2645
+ },
2646
+ index: true
2570
2647
  },
2571
2648
  userId: {
2572
2649
  type: "string",
@@ -2574,7 +2651,8 @@ const schema = {
2574
2651
  references: {
2575
2652
  model: "user",
2576
2653
  field: "id"
2577
- }
2654
+ },
2655
+ index: true
2578
2656
  },
2579
2657
  referenceId: {
2580
2658
  type: "string",
@@ -2608,7 +2686,8 @@ const schema = {
2608
2686
  references: {
2609
2687
  model: "oauthClient",
2610
2688
  field: "clientId"
2611
- }
2689
+ },
2690
+ index: true
2612
2691
  },
2613
2692
  sessionId: {
2614
2693
  type: "string",
@@ -2617,7 +2696,8 @@ const schema = {
2617
2696
  model: "session",
2618
2697
  field: "id",
2619
2698
  onDelete: "set null"
2620
- }
2699
+ },
2700
+ index: true
2621
2701
  },
2622
2702
  userId: {
2623
2703
  type: "string",
@@ -2625,7 +2705,8 @@ const schema = {
2625
2705
  references: {
2626
2706
  model: "user",
2627
2707
  field: "id"
2628
- }
2708
+ },
2709
+ index: true
2629
2710
  },
2630
2711
  referenceId: {
2631
2712
  type: "string",
@@ -2637,7 +2718,8 @@ const schema = {
2637
2718
  references: {
2638
2719
  model: "oauthRefreshToken",
2639
2720
  field: "id"
2640
- }
2721
+ },
2722
+ index: true
2641
2723
  },
2642
2724
  expiresAt: { type: "date" },
2643
2725
  createdAt: { type: "date" },
@@ -2656,7 +2738,8 @@ const schema = {
2656
2738
  references: {
2657
2739
  model: "oauthClient",
2658
2740
  field: "clientId"
2659
- }
2741
+ },
2742
+ index: true
2660
2743
  },
2661
2744
  userId: {
2662
2745
  type: "string",
@@ -2664,7 +2747,8 @@ const schema = {
2664
2747
  references: {
2665
2748
  model: "user",
2666
2749
  field: "id"
2667
- }
2750
+ },
2751
+ index: true
2668
2752
  },
2669
2753
  referenceId: {
2670
2754
  type: "string",
@@ -2776,10 +2860,18 @@ const oauthProvider = (options) => {
2776
2860
  handler: createAuthMiddleware(async (ctx) => {
2777
2861
  const query = ctx.body.oauth_query;
2778
2862
  if (!await verifyOAuthQueryParams(query, ctx.context.secret)) throw new APIError("BAD_REQUEST", { error: "invalid_signature" });
2863
+ const signedQueryIssuedAt = getSignedQueryIssuedAt(query);
2779
2864
  const queryParams = new URLSearchParams(query);
2865
+ const postLoginClearedForSession = queryParams.get("ba_pl") ?? void 0;
2780
2866
  queryParams.delete("sig");
2781
2867
  queryParams.delete("exp");
2782
- await oAuthState.set({ query: queryParams.toString() });
2868
+ queryParams.delete(signedQueryIssuedAtParam);
2869
+ queryParams.delete(postLoginClearedParam);
2870
+ await oAuthState.set({
2871
+ query: queryParams.toString(),
2872
+ signedQueryIssuedAt: signedQueryIssuedAt ?? void 0,
2873
+ postLoginClearedForSession
2874
+ });
2783
2875
  if (ctx.path === "/sign-in/social" || ctx.path === "/sign-in/oauth2") {
2784
2876
  if (ctx.body.additionalData?.query) return;
2785
2877
  if (!ctx.body.additionalData) ctx.body.additionalData = {};
@@ -2803,7 +2895,7 @@ const oauthProvider = (options) => {
2803
2895
  const secFetchMode = ctx.request?.headers?.get("sec-fetch-mode")?.toLowerCase();
2804
2896
  const acceptHeader = ctx.request?.headers?.get("accept")?.toLowerCase() ?? "";
2805
2897
  if (!(secFetchMode === "navigate" || !secFetchMode && (acceptHeader.includes("text/html") || acceptHeader.includes("application/xhtml+xml")))) ctx.headers?.set("accept", "application/json");
2806
- ctx.query = deleteFromPrompt(query, "login");
2898
+ ctx.query = searchParamsToQuery(removePromptFromQuery(query, "login"));
2807
2899
  return await authorizeEndpoint(ctx, opts);
2808
2900
  })
2809
2901
  }]
@@ -3811,7 +3903,7 @@ async function authorizeEndpoint(ctx, opts, settings) {
3811
3903
  });
3812
3904
  if (signupRedirect) {
3813
3905
  if (promptNone) return redirectWithPromptNoneError(ctx, opts, query, "interaction_required", "End-User interaction is required");
3814
- return redirectWithPromptCode(ctx, opts, "create", typeof signupRedirect === "string" ? signupRedirect : void 0);
3906
+ return redirectWithPromptCode(ctx, opts, "create", { page: typeof signupRedirect === "string" ? signupRedirect : void 0 });
3815
3907
  }
3816
3908
  }
3817
3909
  if (!settings?.postLogin && opts.postLogin) {
@@ -3825,7 +3917,7 @@ async function authorizeEndpoint(ctx, opts, settings) {
3825
3917
  return redirectWithPromptCode(ctx, opts, "post_login");
3826
3918
  }
3827
3919
  }
3828
- if (promptSet?.has("consent")) return redirectWithPromptCode(ctx, opts, "consent");
3920
+ if (promptSet?.has("consent")) return redirectWithPromptCode(ctx, opts, "consent", { sessionId: session.session.id });
3829
3921
  const referenceId = await opts.postLogin?.consentReferenceId?.({
3830
3922
  user: session.user,
3831
3923
  session: session.session,
@@ -3858,7 +3950,7 @@ async function authorizeEndpoint(ctx, opts, settings) {
3858
3950
  });
3859
3951
  if (!consent || !requestedScopes.every((val) => consent.scopes.includes(val))) {
3860
3952
  if (promptNone) return redirectWithPromptNoneError(ctx, opts, query, "consent_required", "End-User consent is required");
3861
- return redirectWithPromptCode(ctx, opts, "consent");
3953
+ return redirectWithPromptCode(ctx, opts, "consent", { sessionId: session.session.id });
3862
3954
  }
3863
3955
  return redirectWithAuthorizationCode(ctx, opts, {
3864
3956
  query,
@@ -3905,8 +3997,8 @@ async function redirectWithAuthorizationCode(ctx, opts, verificationValue) {
3905
3997
  redirectUriWithCode.searchParams.set("iss", getIssuer(ctx, opts));
3906
3998
  return handleRedirect(ctx, redirectUriWithCode.toString());
3907
3999
  }
3908
- async function redirectWithPromptCode(ctx, opts, type, page) {
3909
- const queryParams = await signParams(ctx, opts);
4000
+ async function redirectWithPromptCode(ctx, opts, type, options) {
4001
+ const queryParams = await signParams(ctx, opts, { postLoginClearedForSession: type === "consent" && opts.postLogin ? options?.sessionId : void 0 });
3910
4002
  let path = opts.loginPage;
3911
4003
  if (type === "select_account") path = opts.selectAccount?.page ?? opts.loginPage;
3912
4004
  else if (type === "post_login") {
@@ -3914,12 +4006,16 @@ async function redirectWithPromptCode(ctx, opts, type, page) {
3914
4006
  path = opts.postLogin?.page;
3915
4007
  } else if (type === "consent") path = opts.consentPage;
3916
4008
  else if (type === "create") path = opts.signup?.page ?? opts.loginPage;
3917
- return handleRedirect(ctx, `${page ?? path}?${queryParams}`);
4009
+ return handleRedirect(ctx, `${options?.page ?? path}?${queryParams}`);
3918
4010
  }
3919
- async function signParams(ctx, opts) {
3920
- const exp = Math.floor(Date.now() / 1e3) + (opts.codeExpiresIn ?? 600);
4011
+ async function signParams(ctx, opts, flags) {
4012
+ const issuedAt = Date.now();
4013
+ const exp = Math.floor(issuedAt / 1e3) + (opts.codeExpiresIn ?? 600);
3921
4014
  const params = serializeAuthorizationQuery(ctx.query);
3922
4015
  params.set("exp", String(exp));
4016
+ params.set(signedQueryIssuedAtParam, String(issuedAt));
4017
+ params.delete(postLoginClearedParam);
4018
+ if (flags?.postLoginClearedForSession) params.set(postLoginClearedParam, flags.postLoginClearedForSession);
3923
4019
  const signature = await makeSignature(params.toString(), ctx.context.secret);
3924
4020
  params.append("sig", signature);
3925
4021
  return params.toString();
@@ -45,6 +45,7 @@ declare const schema: {
45
45
  model: string;
46
46
  field: string;
47
47
  };
48
+ index: true;
48
49
  };
49
50
  createdAt: {
50
51
  type: "date";
@@ -142,6 +143,7 @@ declare const schema: {
142
143
  token: {
143
144
  type: "string";
144
145
  required: true;
146
+ unique: true;
145
147
  };
146
148
  clientId: {
147
149
  type: "string";
@@ -150,6 +152,7 @@ declare const schema: {
150
152
  model: string;
151
153
  field: string;
152
154
  };
155
+ index: true;
153
156
  };
154
157
  sessionId: {
155
158
  type: "string";
@@ -159,6 +162,7 @@ declare const schema: {
159
162
  field: string;
160
163
  onDelete: "set null";
161
164
  };
165
+ index: true;
162
166
  };
163
167
  userId: {
164
168
  type: "string";
@@ -167,6 +171,7 @@ declare const schema: {
167
171
  model: string;
168
172
  field: string;
169
173
  };
174
+ index: true;
170
175
  };
171
176
  referenceId: {
172
177
  type: "string";
@@ -218,6 +223,7 @@ declare const schema: {
218
223
  model: string;
219
224
  field: string;
220
225
  };
226
+ index: true;
221
227
  };
222
228
  sessionId: {
223
229
  type: "string";
@@ -227,6 +233,7 @@ declare const schema: {
227
233
  field: string;
228
234
  onDelete: "set null";
229
235
  };
236
+ index: true;
230
237
  };
231
238
  userId: {
232
239
  type: "string";
@@ -235,6 +242,7 @@ declare const schema: {
235
242
  model: string;
236
243
  field: string;
237
244
  };
245
+ index: true;
238
246
  };
239
247
  referenceId: {
240
248
  type: "string";
@@ -247,6 +255,7 @@ declare const schema: {
247
255
  model: string;
248
256
  field: string;
249
257
  };
258
+ index: true;
250
259
  };
251
260
  expiresAt: {
252
261
  type: "date";
@@ -270,6 +279,7 @@ declare const schema: {
270
279
  model: string;
271
280
  field: string;
272
281
  };
282
+ index: true;
273
283
  };
274
284
  userId: {
275
285
  type: "string";
@@ -278,6 +288,7 @@ declare const schema: {
278
288
  model: string;
279
289
  field: string;
280
290
  };
291
+ index: true;
281
292
  };
282
293
  referenceId: {
283
294
  type: "string";
@@ -1287,7 +1298,7 @@ interface OAuthOpaqueAccessToken<Scopes extends readonly Scope[] = InternallySup
1287
1298
  */
1288
1299
  interface OAuthRefreshToken<Scopes extends readonly Scope[] = InternallySupportedScopes[]> {
1289
1300
  token: string;
1290
- sessionId: string;
1301
+ sessionId?: string;
1291
1302
  userId: string;
1292
1303
  referenceId?: string;
1293
1304
  clientId?: string;
@@ -1649,4 +1660,4 @@ interface ResourceServerMetadata {
1649
1660
  dpop_bound_access_tokens_required?: boolean;
1650
1661
  }
1651
1662
  //#endregion
1652
- export { Awaitable as _, ResourceServerMetadata as a, OAuthConsent as c, OAuthRefreshToken as d, Prompt as f, VerificationValue as g, StoreTokenType as h, OIDCMetadata as i, OAuthOpaqueAccessToken as l, Scope as m, GrantType as n, AuthorizePrompt as o, SchemaClient as p, OAuthClient as r, OAuthAuthorizationQuery as s, AuthServerMetadata as t, OAuthOptions as u };
1663
+ export { Scope as _, OAuthClient as a, Awaitable as b, TokenEndpointAuthMethod as c, OAuthConsent as d, OAuthOpaqueAccessToken as f, SchemaClient as g, Prompt as h, GrantType as i, AuthorizePrompt as l, OAuthRefreshToken as m, AuthServerMetadata as n, OIDCMetadata as o, OAuthOptions as p, BearerMethodsSupported as r, ResourceServerMetadata as s, AuthMethod as t, OAuthAuthorizationQuery as u, StoreTokenType as v, VerificationValue as y };
@@ -1,4 +1,4 @@
1
- import { c as OAuthConsent, i as OIDCMetadata, m as Scope, r as OAuthClient, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-DMjvJjmR.mjs";
1
+ import { _ as Scope, a as OAuthClient, d as OAuthConsent, n as AuthServerMetadata, o as OIDCMetadata, p as OAuthOptions } from "./oauth-BqWgUea8.mjs";
2
2
  import * as better_call0 from "better-call";
3
3
  import * as z from "zod";
4
4
  import * as better_auth_plugins0 from "better-auth/plugins";
@@ -15,6 +15,8 @@ declare module "@better-auth/core" {
15
15
  }
16
16
  declare const getOAuthProviderState: () => Promise<{
17
17
  query?: string;
18
+ signedQueryIssuedAt?: Date;
19
+ postLoginClearedForSession?: string;
18
20
  } | null>;
19
21
  /**
20
22
  * oAuth 2.1 provider plugin for Better Auth.
@@ -1819,6 +1821,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1819
1821
  model: string;
1820
1822
  field: string;
1821
1823
  };
1824
+ index: true;
1822
1825
  };
1823
1826
  createdAt: {
1824
1827
  type: "date";
@@ -1911,6 +1914,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1911
1914
  token: {
1912
1915
  type: "string";
1913
1916
  required: true;
1917
+ unique: true;
1914
1918
  };
1915
1919
  clientId: {
1916
1920
  type: "string";
@@ -1919,6 +1923,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1919
1923
  model: string;
1920
1924
  field: string;
1921
1925
  };
1926
+ index: true;
1922
1927
  };
1923
1928
  sessionId: {
1924
1929
  type: "string";
@@ -1928,6 +1933,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1928
1933
  field: string;
1929
1934
  onDelete: "set null";
1930
1935
  };
1936
+ index: true;
1931
1937
  };
1932
1938
  userId: {
1933
1939
  type: "string";
@@ -1936,6 +1942,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1936
1942
  model: string;
1937
1943
  field: string;
1938
1944
  };
1945
+ index: true;
1939
1946
  };
1940
1947
  referenceId: {
1941
1948
  type: "string";
@@ -1975,6 +1982,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1975
1982
  model: string;
1976
1983
  field: string;
1977
1984
  };
1985
+ index: true;
1978
1986
  };
1979
1987
  sessionId: {
1980
1988
  type: "string";
@@ -1984,6 +1992,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1984
1992
  field: string;
1985
1993
  onDelete: "set null";
1986
1994
  };
1995
+ index: true;
1987
1996
  };
1988
1997
  userId: {
1989
1998
  type: "string";
@@ -1992,6 +2001,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1992
2001
  model: string;
1993
2002
  field: string;
1994
2003
  };
2004
+ index: true;
1995
2005
  };
1996
2006
  referenceId: {
1997
2007
  type: "string";
@@ -2004,6 +2014,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
2004
2014
  model: string;
2005
2015
  field: string;
2006
2016
  };
2017
+ index: true;
2007
2018
  };
2008
2019
  expiresAt: {
2009
2020
  type: "date";
@@ -2027,6 +2038,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
2027
2038
  model: string;
2028
2039
  field: string;
2029
2040
  };
2041
+ index: true;
2030
2042
  };
2031
2043
  userId: {
2032
2044
  type: "string";
@@ -2035,6 +2047,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
2035
2047
  model: string;
2036
2048
  field: string;
2037
2049
  };
2050
+ index: true;
2038
2051
  };
2039
2052
  referenceId: {
2040
2053
  type: "string";
@@ -369,20 +369,24 @@ function searchParamsToQuery(params) {
369
369
  }
370
370
  return result;
371
371
  }
372
- /**
373
- * Deletes a prompt value
374
- *
375
- * @param ctx
376
- * @param prompt - the prompt value to delete
377
- */
378
- function deleteFromPrompt(query, prompt) {
379
- const prompts = query.get("prompt")?.split(" ");
372
+ const signedQueryIssuedAtParam = "ba_iat";
373
+ const postLoginClearedParam = "ba_pl";
374
+ function getSignedQueryIssuedAt(oauthQuery) {
375
+ const raw = new URLSearchParams(oauthQuery).get(signedQueryIssuedAtParam);
376
+ if (!raw) return null;
377
+ const issuedAt = Number(raw);
378
+ if (!Number.isFinite(issuedAt) || issuedAt <= 0) return null;
379
+ return new Date(issuedAt);
380
+ }
381
+ function removePromptFromQuery(query, prompt) {
382
+ const nextQuery = new URLSearchParams(query);
383
+ const prompts = nextQuery.get("prompt")?.split(" ");
380
384
  const foundPrompt = prompts?.findIndex((v) => v === prompt) ?? -1;
381
385
  if (foundPrompt >= 0) {
382
386
  prompts?.splice(foundPrompt, 1);
383
- prompts?.length ? query.set("prompt", prompts.join(" ")) : query.delete("prompt");
387
+ prompts?.length ? nextQuery.set("prompt", prompts.join(" ")) : nextQuery.delete("prompt");
384
388
  }
385
- return searchParamsToQuery(query);
389
+ return nextQuery;
386
390
  }
387
391
  var PKCERequirementErrors = /* @__PURE__ */ function(PKCERequirementErrors) {
388
392
  PKCERequirementErrors["PUBLIC_CLIENT"] = "pkce is required for public clients";
@@ -411,4 +415,4 @@ function isPKCERequired(client, requestedScopes) {
411
415
  return false;
412
416
  }
413
417
  //#endregion
414
- export { validateClientCredentials as _, getJwtPlugin as a, mcpHandler as b, isPKCERequired as c, parsePrompt as d, resolveSessionAuthTime as f, storeToken as g, storeClientSecret as h, getClient as i, normalizeTimestampValue as l, searchParamsToQuery as m, decryptStoredClientSecret as n, getOAuthProviderPlugin as o, resolveSubjectIdentifier as p, deleteFromPrompt as r, getStoredToken as s, basicToClientCredentials as t, parseClientMetadata as u, verifyOAuthQueryParams as v, handleMcpErrors as y };
418
+ export { mcpHandler as C, handleMcpErrors as S, signedQueryIssuedAtParam as _, getOAuthProviderPlugin as a, validateClientCredentials as b, isPKCERequired as c, parsePrompt as d, postLoginClearedParam as f, searchParamsToQuery as g, resolveSubjectIdentifier as h, getJwtPlugin as i, normalizeTimestampValue as l, resolveSessionAuthTime as m, decryptStoredClientSecret as n, getSignedQueryIssuedAt as o, removePromptFromQuery as p, getClient as r, getStoredToken as s, basicToClientCredentials as t, parseClientMetadata as u, storeClientSecret as v, verifyOAuthQueryParams as x, storeToken as y };
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.6.9";
3
+ const PACKAGE_VERSION = "1.6.11";
4
4
  //#endregion
5
5
  export { PACKAGE_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/oauth-provider",
3
- "version": "1.6.9",
3
+ "version": "1.6.11",
4
4
  "description": "An oauth provider plugin for Better Auth",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -64,15 +64,15 @@
64
64
  "@modelcontextprotocol/sdk": "^1.27.1",
65
65
  "listhen": "^1.9.0",
66
66
  "tsdown": "0.21.1",
67
- "@better-auth/core": "1.6.9",
68
- "better-auth": "1.6.9"
67
+ "@better-auth/core": "1.6.11",
68
+ "better-auth": "1.6.11"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@better-auth/utils": "0.4.0",
72
72
  "@better-fetch/fetch": "1.1.21",
73
73
  "better-call": "1.3.5",
74
- "@better-auth/core": "^1.6.9",
75
- "better-auth": "^1.6.9"
74
+ "@better-auth/core": "^1.6.11",
75
+ "better-auth": "^1.6.11"
76
76
  },
77
77
  "scripts": {
78
78
  "build": "tsdown",