@better-auth/oauth-provider 1.6.10 → 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 { s as ResourceServerMetadata } from "./oauth-CqgT-XaR.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
1
  import { S as handleMcpErrors, a as getOAuthProviderPlugin, i as getJwtPlugin } from "./utils-LAthGy-x.mjs";
2
- import { t as PACKAGE_VERSION } from "./version-Cg-c01N0.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-DBCeGXT3.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-Cg-c01N0.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 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-CqgT-XaR.mjs";
2
- import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-DBCeGXT3.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";
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
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-Cg-c01N0.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";
@@ -437,33 +437,95 @@ async function createOpaqueAccessToken(ctx, opts, user, client, scopes, payload,
437
437
  });
438
438
  return (opts.prefix?.opaqueAccessToken ?? "") + token;
439
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
+ }
440
487
  async function createRefreshToken(ctx, opts, user, referenceId, client, scopes, payload, originalRefresh, authTime) {
441
488
  const iat = payload.iat ?? Math.floor(Date.now() / 1e3);
442
489
  const exp = payload?.exp ?? iat + (opts.refreshTokenExpiresIn ?? 2592e3);
443
490
  const token = opts.generateRefreshToken ? await opts.generateRefreshToken() : generateRandomString(32, "A-Z", "a-z");
444
491
  const sessionId = payload?.sid;
445
- 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({
446
511
  model: "oauthRefreshToken",
447
512
  where: [{
448
513
  field: "id",
449
514
  value: originalRefresh.id
515
+ }, {
516
+ field: "revoked",
517
+ operator: "eq",
518
+ value: null
450
519
  }],
451
520
  update: { revoked: /* @__PURE__ */ new Date(iat * 1e3) }
521
+ })) throw new APIError("BAD_REQUEST", {
522
+ error_description: "invalid refresh token",
523
+ error: "invalid_grant"
452
524
  });
453
525
  return {
454
526
  id: (await ctx.context.adapter.create({
455
527
  model: "oauthRefreshToken",
456
- data: {
457
- token: await storeToken(opts.storeTokens, token, "refresh_token"),
458
- clientId: client.clientId,
459
- sessionId,
460
- userId: user.id,
461
- referenceId,
462
- authTime,
463
- scopes,
464
- createdAt: /* @__PURE__ */ new Date(iat * 1e3),
465
- expiresAt: /* @__PURE__ */ new Date(exp * 1e3)
466
- }
528
+ data: newRow
467
529
  })).id,
468
530
  token: await encodeRefreshToken(opts, token, sessionId)
469
531
  };
@@ -541,15 +603,14 @@ async function createUserTokens(ctx, opts, params) {
541
603
  }
542
604
  /** Checks verification value */
543
605
  async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri) {
544
- 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"));
545
607
  if (!verification) throw new APIError("UNAUTHORIZED", {
546
608
  error_description: "Invalid code",
547
- error: "invalid_verification"
609
+ error: "invalid_grant"
548
610
  });
549
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(await storeToken(opts.storeTokens, code, "authorization_code"));
550
611
  if (!verification.expiresAt || verification.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("UNAUTHORIZED", {
551
612
  error_description: "code expired",
552
- error: "invalid_verification"
613
+ error: "invalid_grant"
553
614
  });
554
615
  let rawValue;
555
616
  try {
@@ -557,13 +618,13 @@ async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri)
557
618
  } catch {
558
619
  throw new APIError("UNAUTHORIZED", {
559
620
  error_description: "malformed verification value",
560
- error: "invalid_verification"
621
+ error: "invalid_grant"
561
622
  });
562
623
  }
563
624
  const parsed = verificationValueSchema.safeParse(rawValue);
564
625
  if (!parsed.success) throw new APIError("UNAUTHORIZED", {
565
626
  error_description: "malformed verification value",
566
- error: "invalid_verification"
627
+ error: "invalid_grant"
567
628
  });
568
629
  const verificationValue = parsed.data;
569
630
  if (verificationValue.query.client_id !== client_id) throw new APIError("UNAUTHORIZED", {
@@ -764,16 +825,7 @@ async function handleRefreshTokenGrant(ctx, opts) {
764
825
  error: "invalid_grant"
765
826
  });
766
827
  if (refreshToken.revoked) {
767
- await ctx.context.adapter.deleteMany({
768
- model: "oauthRefreshToken",
769
- where: [{
770
- field: "clientId",
771
- value: client_id
772
- }, {
773
- field: "userId",
774
- value: refreshToken.userId
775
- }]
776
- });
828
+ await invalidateRefreshFamily(ctx, client_id, refreshToken.userId);
777
829
  throw new APIError("BAD_REQUEST", {
778
830
  error_description: "invalid refresh token",
779
831
  error: "invalid_grant"
@@ -2329,16 +2381,7 @@ async function revokeRefreshToken(ctx, opts, token, clientId) {
2329
2381
  error: "invalid_request"
2330
2382
  });
2331
2383
  if (refreshToken.revoked) {
2332
- await ctx.context.adapter.deleteMany({
2333
- model: "oauthRefreshToken",
2334
- where: [{
2335
- field: "clientId",
2336
- value: clientId
2337
- }, {
2338
- field: "userId",
2339
- value: refreshToken.userId
2340
- }]
2341
- });
2384
+ await invalidateRefreshFamily(ctx, clientId, refreshToken.userId);
2342
2385
  throw new APIError$1("BAD_REQUEST", {
2343
2386
  error_description: "refresh token revoked",
2344
2387
  error: "invalid_request"
@@ -2346,20 +2389,31 @@ async function revokeRefreshToken(ctx, opts, token, clientId) {
2346
2389
  }
2347
2390
  if (!refreshToken.clientId || refreshToken.clientId !== clientId) return null;
2348
2391
  const iat = Math.floor(Date.now() / 1e3);
2349
- await Promise.allSettled([ctx.context.adapter.deleteMany({
2350
- model: "oauthAccessToken",
2351
- where: [{
2352
- field: "refreshId",
2353
- value: refreshToken.id
2354
- }]
2355
- }), ctx.context.adapter.update({
2392
+ if (!await ctx.context.adapter.update({
2356
2393
  model: "oauthRefreshToken",
2357
2394
  where: [{
2358
2395
  field: "id",
2359
2396
  value: refreshToken.id
2397
+ }, {
2398
+ field: "revoked",
2399
+ operator: "eq",
2400
+ value: null
2360
2401
  }],
2361
2402
  update: { revoked: /* @__PURE__ */ new Date(iat * 1e3) }
2362
- })]);
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
+ });
2363
2417
  }
2364
2418
  /**
2365
2419
  * We don't know the access token format so we try to validate it
@@ -2569,7 +2623,8 @@ const schema = {
2569
2623
  oauthRefreshToken: { fields: {
2570
2624
  token: {
2571
2625
  type: "string",
2572
- required: true
2626
+ required: true,
2627
+ unique: true
2573
2628
  },
2574
2629
  clientId: {
2575
2630
  type: "string",
@@ -143,6 +143,7 @@ declare const schema: {
143
143
  token: {
144
144
  type: "string";
145
145
  required: true;
146
+ unique: true;
146
147
  };
147
148
  clientId: {
148
149
  type: "string";
@@ -1,4 +1,4 @@
1
- import { _ as Scope, a as OAuthClient, d as OAuthConsent, n as AuthServerMetadata, o as OIDCMetadata, p as OAuthOptions } from "./oauth-CqgT-XaR.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";
@@ -1914,6 +1914,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1914
1914
  token: {
1915
1915
  type: "string";
1916
1916
  required: true;
1917
+ unique: true;
1917
1918
  };
1918
1919
  clientId: {
1919
1920
  type: "string";
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.6.10";
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.10",
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.10",
68
- "better-auth": "1.6.10"
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.10",
75
- "better-auth": "^1.6.10"
74
+ "@better-auth/core": "^1.6.11",
75
+ "better-auth": "^1.6.11"
76
76
  },
77
77
  "scripts": {
78
78
  "build": "tsdown",