@better-auth/oauth-provider 1.6.10 → 1.6.12

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,9 +1,16 @@
1
- import { s as ResourceServerMetadata } from "./oauth-CqgT-XaR.mjs";
1
+ import { s as ResourceServerMetadata } from "./oauth-D74mBkw6.mjs";
2
2
  import { JWTPayload, JWTVerifyOptions } from "jose";
3
- import { Auth } from "better-auth/types";
3
+ import { BetterAuthOptions } from "better-auth/types";
4
4
 
5
5
  //#region src/client-resource.d.ts
6
- declare const oauthProviderResourceClient: <T extends Auth | undefined>(auth?: T) => {
6
+ type ResourceClientAuth = {
7
+ options: {
8
+ baseURL?: BetterAuthOptions["baseURL"];
9
+ basePath?: BetterAuthOptions["basePath"];
10
+ };
11
+ $context: Promise<unknown>;
12
+ };
13
+ declare const oauthProviderResourceClient: <T extends ResourceClientAuth | undefined = undefined>(auth?: T) => {
7
14
  id: "oauth-provider-resource-client";
8
15
  version: string;
9
16
  getActions(): {
@@ -43,7 +50,7 @@ interface VerifyAccessTokenRemote {
43
50
  */
44
51
  force?: boolean;
45
52
  }
46
- type VerifyAccessTokenOutput<T> = T extends Auth ? (token: string | undefined, opts?: VerifyAccessTokenAuthOpts) => Promise<JWTPayload> : (token: string | undefined, opts: VerifyAccessTokenNoAuthOpts) => Promise<JWTPayload>;
53
+ type VerifyAccessTokenOutput<T> = T extends undefined ? (token: string | undefined, opts: VerifyAccessTokenNoAuthOpts) => Promise<JWTPayload> : (token: string | undefined, opts?: VerifyAccessTokenAuthOpts) => Promise<JWTPayload>;
47
54
  type VerifyAccessTokenAuthOpts = {
48
55
  verifyOptions?: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience">>;
49
56
  scopes?: string[];
@@ -64,12 +71,12 @@ type VerifyAccessTokenNoAuthOpts = {
64
71
  remoteVerify: VerifyAccessTokenRemote; /** Maps non-url (ie urn, client) resources to resource_metadata */
65
72
  resourceMetadataMappings?: Record<string, string>;
66
73
  };
67
- type ProtectedResourceMetadataOutput<T> = T extends Auth ? (overrides?: Partial<ResourceServerMetadata>, opts?: {
74
+ type ProtectedResourceMetadataOutput<T> = T extends undefined ? (overrides: ResourceServerMetadata, opts?: {
68
75
  silenceWarnings?: {
69
76
  oidcScopes?: boolean;
70
77
  };
71
78
  externalScopes?: string[];
72
- }) => Promise<ResourceServerMetadata> : (overrides: ResourceServerMetadata, opts?: {
79
+ }) => Promise<ResourceServerMetadata> : (overrides?: Partial<ResourceServerMetadata>, opts?: {
73
80
  silenceWarnings?: {
74
81
  oidcScopes?: boolean;
75
82
  };
@@ -1,5 +1,5 @@
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";
1
+ import { S as handleMcpErrors, a as getOAuthProviderPlugin, i as getJwtPlugin } from "./utils-DoYEeMrg.mjs";
2
+ import { t as PACKAGE_VERSION } from "./version-DZ0lABPc.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-CUqnBdrR.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-DZ0lABPc.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-D74mBkw6.mjs";
2
+ import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-CUqnBdrR.mjs";
3
3
  import { verifyAccessToken } from "better-auth/oauth2";
4
4
  import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
5
5
  import { JWTPayload } from "jose";
@@ -21,6 +21,7 @@ verifyOptions: Parameters<typeof verifyAccessToken>[1], handler: (req: Request,
21
21
  //#region src/metadata.d.ts
22
22
  declare function authServerMetadata(ctx: GenericEndpointContext, opts?: JwtOptions, overrides?: {
23
23
  scopes_supported?: AuthServerMetadata["scopes_supported"];
24
+ dynamic_client_registration_supported?: boolean;
24
25
  public_client_supported?: boolean;
25
26
  grant_types_supported?: GrantType[];
26
27
  jwt_disabled?: boolean;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
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";
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-DoYEeMrg.mjs";
2
+ import { t as PACKAGE_VERSION } from "./version-DZ0lABPc.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,10 @@ 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
- error_description: "Invalid code",
547
- error: "invalid_verification"
548
- });
549
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(await storeToken(opts.storeTokens, code, "authorization_code"));
550
- if (!verification.expiresAt || verification.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("UNAUTHORIZED", {
551
- error_description: "code expired",
552
- error: "invalid_verification"
608
+ error_description: "invalid code",
609
+ error: "invalid_grant"
553
610
  });
554
611
  let rawValue;
555
612
  try {
@@ -557,13 +614,13 @@ async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri)
557
614
  } catch {
558
615
  throw new APIError("UNAUTHORIZED", {
559
616
  error_description: "malformed verification value",
560
- error: "invalid_verification"
617
+ error: "invalid_grant"
561
618
  });
562
619
  }
563
620
  const parsed = verificationValueSchema.safeParse(rawValue);
564
621
  if (!parsed.success) throw new APIError("UNAUTHORIZED", {
565
622
  error_description: "malformed verification value",
566
- error: "invalid_verification"
623
+ error: "invalid_grant"
567
624
  });
568
625
  const verificationValue = parsed.data;
569
626
  if (verificationValue.query.client_id !== client_id) throw new APIError("UNAUTHORIZED", {
@@ -764,16 +821,7 @@ async function handleRefreshTokenGrant(ctx, opts) {
764
821
  error: "invalid_grant"
765
822
  });
766
823
  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
- });
824
+ await invalidateRefreshFamily(ctx, client_id, refreshToken.userId);
777
825
  throw new APIError("BAD_REQUEST", {
778
826
  error_description: "invalid refresh token",
779
827
  error: "invalid_grant"
@@ -2176,12 +2224,12 @@ async function updateConsentEndpoint(ctx, opts) {
2176
2224
  error_description: "no consent",
2177
2225
  error: "not_found"
2178
2226
  });
2227
+ if (consent.userId !== session.user.id) throw new APIError("UNAUTHORIZED");
2179
2228
  const client = await getClient(ctx, opts, consent.clientId);
2180
- if (!consent) throw new APIError("NOT_FOUND", {
2181
- error_description: "no consent",
2229
+ if (!client) throw new APIError("NOT_FOUND", {
2230
+ error_description: "client not found",
2182
2231
  error: "not_found"
2183
2232
  });
2184
- if (consent.userId !== session.user.id) throw new APIError("UNAUTHORIZED");
2185
2233
  const allowedScopes = client?.scopes ?? opts.scopes ?? [];
2186
2234
  const updates = ctx.body.update;
2187
2235
  const scopes = updates.scopes;
@@ -2329,16 +2377,7 @@ async function revokeRefreshToken(ctx, opts, token, clientId) {
2329
2377
  error: "invalid_request"
2330
2378
  });
2331
2379
  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
- });
2380
+ await invalidateRefreshFamily(ctx, clientId, refreshToken.userId);
2342
2381
  throw new APIError$1("BAD_REQUEST", {
2343
2382
  error_description: "refresh token revoked",
2344
2383
  error: "invalid_request"
@@ -2346,20 +2385,31 @@ async function revokeRefreshToken(ctx, opts, token, clientId) {
2346
2385
  }
2347
2386
  if (!refreshToken.clientId || refreshToken.clientId !== clientId) return null;
2348
2387
  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({
2388
+ if (!await ctx.context.adapter.update({
2356
2389
  model: "oauthRefreshToken",
2357
2390
  where: [{
2358
2391
  field: "id",
2359
2392
  value: refreshToken.id
2393
+ }, {
2394
+ field: "revoked",
2395
+ operator: "eq",
2396
+ value: null
2360
2397
  }],
2361
2398
  update: { revoked: /* @__PURE__ */ new Date(iat * 1e3) }
2362
- })]);
2399
+ })) {
2400
+ await invalidateRefreshFamily(ctx, clientId, refreshToken.userId);
2401
+ throw new APIError$1("BAD_REQUEST", {
2402
+ error_description: "refresh token revoked",
2403
+ error: "invalid_request"
2404
+ });
2405
+ }
2406
+ await ctx.context.adapter.deleteMany({
2407
+ model: "oauthAccessToken",
2408
+ where: [{
2409
+ field: "refreshId",
2410
+ value: refreshToken.id
2411
+ }]
2412
+ });
2363
2413
  }
2364
2414
  /**
2365
2415
  * We don't know the access token format so we try to validate it
@@ -2569,7 +2619,8 @@ const schema = {
2569
2619
  oauthRefreshToken: { fields: {
2570
2620
  token: {
2571
2621
  type: "string",
2572
- required: true
2622
+ required: true,
2623
+ unique: true
2573
2624
  },
2574
2625
  clientId: {
2575
2626
  type: "string",
@@ -2776,6 +2827,47 @@ const oauthProvider = (options) => {
2776
2827
  if (opts.grantTypes && opts.grantTypes.includes("refresh_token") && !opts.grantTypes.includes("authorization_code")) throw new BetterAuthError("refresh_token grant requires authorization_code grant");
2777
2828
  if (opts.disableJwtPlugin && (opts.storeClientSecret === "hashed" || typeof opts.storeClientSecret === "object" && "hash" in opts.storeClientSecret)) throw new BetterAuthError("unable to store hashed secrets because id tokens will be signed with secret");
2778
2829
  if (!opts.disableJwtPlugin && (opts.storeClientSecret === "encrypted" || typeof opts.storeClientSecret === "object" && ("encrypt" in opts.storeClientSecret || "decrypt" in opts.storeClientSecret))) throw new BetterAuthError("encryption method not recommended, please use 'hashed' or the 'hash' function");
2830
+ const handleIssuerMetadataRequest = async (request, ctx) => {
2831
+ const requestPathname = new URL(request.url).pathname;
2832
+ const requestPath = ctx.options.advanced?.skipTrailingSlashes ? requestPathname.replace(/\/+$/, "") || "/" : requestPathname;
2833
+ const issuer = opts.disableJwtPlugin ? ctx.baseURL : getJwtPlugin(ctx)?.options?.jwt?.issuer ?? ctx.baseURL;
2834
+ let issuerPath = "/";
2835
+ try {
2836
+ issuerPath = new URL(issuer).pathname.replace(/\/$/, "") || "";
2837
+ } catch {
2838
+ issuerPath = new URL(ctx.baseURL).pathname.replace(/\/$/, "") || "";
2839
+ }
2840
+ const endpointCtx = { context: ctx };
2841
+ const authServerMetadataPaths = new Set([`/.well-known/oauth-authorization-server${issuerPath}`, `${issuerPath}/.well-known/oauth-authorization-server`]);
2842
+ const openIdConfigPath = `${issuerPath}/.well-known/openid-configuration`;
2843
+ const isAuthServerMetadataRequest = authServerMetadataPaths.has(requestPath);
2844
+ const isOpenIdConfigRequest = opts.scopes?.includes("openid") && requestPath === openIdConfigPath;
2845
+ const createMetadataResponse = (metadata) => {
2846
+ const response = metadataResponse(metadata);
2847
+ if (request.method === "HEAD") return new Response(null, {
2848
+ status: response.status,
2849
+ headers: response.headers
2850
+ });
2851
+ return response;
2852
+ };
2853
+ if (isAuthServerMetadataRequest || isOpenIdConfigRequest) {
2854
+ if (request.method !== "GET" && request.method !== "HEAD") return { response: new Response(null, {
2855
+ status: 405,
2856
+ headers: { Allow: "GET, HEAD" }
2857
+ }) };
2858
+ }
2859
+ if (isAuthServerMetadataRequest) {
2860
+ if (opts.scopes?.includes("openid")) return { response: createMetadataResponse(oidcServerMetadata(endpointCtx, opts)) };
2861
+ return { response: createMetadataResponse(authServerMetadata(endpointCtx, opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx)?.options, {
2862
+ scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
2863
+ dynamic_client_registration_supported: opts.allowDynamicClientRegistration,
2864
+ public_client_supported: opts.allowUnauthenticatedClientRegistration,
2865
+ grant_types_supported: opts.grantTypes,
2866
+ jwt_disabled: opts.disableJwtPlugin
2867
+ })) };
2868
+ }
2869
+ if (isOpenIdConfigRequest) return { response: createMetadataResponse(oidcServerMetadata(endpointCtx, opts)) };
2870
+ };
2779
2871
  return {
2780
2872
  id: "oauth-provider",
2781
2873
  version: PACKAGE_VERSION,
@@ -2797,6 +2889,7 @@ const oauthProvider = (options) => {
2797
2889
  if (!opts.silenceWarnings?.openidConfig && ctx.options.basePath !== issuerPath && opts.scopes?.includes("openid")) logger.warn(`Please ensure '${issuerPath}${issuerPath.endsWith("/") ? "" : "/"}.well-known/openid-configuration' exists. Upon completion, clear with silenceWarnings.openidConfig.`);
2798
2890
  }
2799
2891
  },
2892
+ onRequest: handleIssuerMetadataRequest,
2800
2893
  hooks: {
2801
2894
  before: [{
2802
2895
  matcher(ctx) {
@@ -2853,6 +2946,7 @@ const oauthProvider = (options) => {
2853
2946
  if (opts.scopes && opts.scopes.includes("openid")) return oidcServerMetadata(ctx, opts);
2854
2947
  else return authServerMetadata(ctx, opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context)?.options, {
2855
2948
  scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
2949
+ dynamic_client_registration_supported: opts.allowDynamicClientRegistration,
2856
2950
  public_client_supported: opts.allowUnauthenticatedClientRegistration,
2857
2951
  grant_types_supported: opts.grantTypes,
2858
2952
  jwt_disabled: opts.disableJwtPlugin
@@ -3975,7 +4069,7 @@ function authServerMetadata(ctx, opts, overrides) {
3975
4069
  authorization_endpoint: `${baseURL}/oauth2/authorize`,
3976
4070
  token_endpoint: `${baseURL}/oauth2/token`,
3977
4071
  jwks_uri: overrides?.jwt_disabled ? void 0 : opts?.jwks?.remoteUrl ?? `${baseURL}${opts?.jwks?.jwksPath ?? "/jwks"}`,
3978
- registration_endpoint: `${baseURL}/oauth2/register`,
4072
+ registration_endpoint: overrides?.dynamic_client_registration_supported ? `${baseURL}/oauth2/register` : void 0,
3979
4073
  introspection_endpoint: `${baseURL}/oauth2/introspect`,
3980
4074
  revocation_endpoint: `${baseURL}/oauth2/revoke`,
3981
4075
  response_types_supported: overrides?.grant_types_supported && !overrides.grant_types_supported.includes("authorization_code") ? [] : ["code"],
@@ -4002,6 +4096,7 @@ function oidcServerMetadata(ctx, opts) {
4002
4096
  return {
4003
4097
  ...authServerMetadata(ctx, jwtPluginOptions, {
4004
4098
  scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
4099
+ dynamic_client_registration_supported: opts.allowDynamicClientRegistration,
4005
4100
  public_client_supported: opts.allowUnauthenticatedClientRegistration,
4006
4101
  grant_types_supported: opts.grantTypes,
4007
4102
  jwt_disabled: opts.disableJwtPlugin
@@ -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-D74mBkw6.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";
@@ -30,6 +30,11 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
30
30
  version: string;
31
31
  options: NoInfer<O>;
32
32
  init: (ctx: better_auth0.AuthContext) => void;
33
+ onRequest: (request: Request, ctx: better_auth0.AuthContext) => Promise<{
34
+ response: Response;
35
+ } | {
36
+ request: Request;
37
+ } | void>;
33
38
  hooks: {
34
39
  before: {
35
40
  matcher(ctx: better_auth0.HookEndpointContext): any;
@@ -1914,6 +1919,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1914
1919
  token: {
1915
1920
  type: "string";
1916
1921
  required: true;
1922
+ unique: true;
1917
1923
  };
1918
1924
  clientId: {
1919
1925
  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";
@@ -1379,9 +1380,11 @@ interface AuthServerMetadata {
1379
1380
  /**
1380
1381
  * The URL of the dynamic client registration endpoint.
1381
1382
  *
1383
+ * This field is only present when `allowDynamicClientRegistration` is enabled.
1384
+ *
1382
1385
  * @default `/oauth2/register`
1383
1386
  */
1384
- registration_endpoint: string;
1387
+ registration_endpoint?: string;
1385
1388
  /**
1386
1389
  * Supported scopes.
1387
1390
  */
@@ -254,11 +254,13 @@ function basicToClientCredentials(authorization) {
254
254
  if (authorization.startsWith("Basic ")) {
255
255
  const encoded = authorization.replace("Basic ", "");
256
256
  const decoded = new TextDecoder().decode(base64.decode(encoded));
257
- if (!decoded.includes(":")) throw new APIError$1("BAD_REQUEST", {
257
+ const separatorIndex = decoded.indexOf(":");
258
+ if (separatorIndex === -1) throw new APIError$1("BAD_REQUEST", {
258
259
  error_description: "invalid authorization header format",
259
260
  error: "invalid_client"
260
261
  });
261
- const [id, secret] = decoded.split(":", 2);
262
+ const id = decoded.slice(0, separatorIndex);
263
+ const secret = decoded.slice(separatorIndex + 1);
262
264
  if (!id || !secret) throw new APIError$1("BAD_REQUEST", {
263
265
  error_description: "invalid authorization header format",
264
266
  error: "invalid_client"
@@ -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.12";
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.12",
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.12",
68
+ "better-auth": "1.6.12"
69
69
  },
70
70
  "peerDependencies": {
71
- "@better-auth/utils": "0.4.0",
71
+ "@better-auth/utils": "0.4.1",
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.12",
75
+ "better-auth": "^1.6.12"
76
76
  },
77
77
  "scripts": {
78
78
  "build": "tsdown",