@better-auth/oauth-provider 1.7.7 → 1.7.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.
@@ -1,8 +1,8 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
2
- import { A as filterClaimsRequestUserInfoClaims, C as resolveResourcePolicy, D as getSupportedClaims, E as STANDARD_CLAIM_NAMES, M as isValidOidcClaimsRequest, O as canSatisfyEssentialAcrRequest, S as logEnforcePerClientResourcesResolution, T as STANDARD_CLAIMS, _ as assertIdentifierValid, a as invalidateRefreshFamily, b as invalidateResourceCache, c as ResourceUriSchema, d as clientJwksSchema, f as clientRegistrationRequestSchema, g as JWS_ALGORITHMS, h as validateClientCredentialsScopes, j as getRequestedUserInfoClaims, k as claimsRequestParameterSchema, l as SafeUrlSchema, m as normalizeClientCredentialsScopes, o as tokenEndpoint, r as decodeRefreshToken, s as userInfoEndpoint, t as introspectEndpoint, u as authorizationQuerySchema, v as extractRepeatedResourceFromForm, w as seedResources, x as isAudienceClaimAllowed, y as getResource } from "./introspect-CNR06Oy3.mjs";
3
- import { B as validateOAuthProviderExtensions, C as storeToken, E as validateClientCredentials, I as getSupportedAuthMethods, L as getSupportedGrantTypes, O as verifyOAuthQueryParams, P as getClientDiscoveries, S as storeClientSecret, T as toResourceList, _ as removeMaxAgeFromQuery, a as getClient, b as resolveSubjectIdentifier, c as getStoredToken, d as mergeDiscoveryMetadata, g as parsePrompt, h as parseClientMetadata, i as extractClientCredentials, k as applyOAuthProviderMetadataExtensions, l as isPKCERequired, m as parseBearerToken, n as decryptStoredClientSecret, o as getJwtPlugin, r as destructureCredentials, t as clientAllowsGrant, u as isSessionFreshForSignedQuery, v as removePromptFromQuery, x as searchParamsToQuery, z as isExtensionTokenEndpointAuthMethod } from "./utils-D6Hp4Qd_.mjs";
2
+ import { A as claimsRequestParameterSchema, C as clientJwksSchema, D as STANDARD_CLAIM_NAMES, E as STANDARD_CLAIMS, M as getRequestedUserInfoClaims, N as isValidOidcClaimsRequest, O as getSupportedClaims, S as authorizationQuerySchema, _ as logStartupSeedFailure, a as invalidateRefreshFamily, b as ResourceUriSchema, c as normalizeClientCredentialsScopes, d as assertIdentifierValid, f as extractRepeatedResourceFromForm, g as logEnforcePerClientResourcesResolution, h as isAudienceClaimAllowed, j as filterClaimsRequestUserInfoClaims, k as canSatisfyEssentialAcrRequest, l as validateClientCredentialsScopes, m as invalidateResourceCache, o as tokenEndpoint, p as getResource, r as decodeRefreshToken, s as userInfoEndpoint, t as introspectEndpoint, u as JWS_ALGORITHMS, v as resolveResourcePolicy, w as clientRegistrationRequestSchema, x as SafeUrlSchema, y as seedResourcesOnce } from "./introspect-H3uoUiuS.mjs";
3
+ import { B as validateOAuthProviderExtensions, C as storeToken, E as validateClientCredentials, I as getSupportedAuthMethods, L as getSupportedGrantTypes, O as verifyOAuthQueryParams, P as getClientDiscoveries, S as storeClientSecret, T as toResourceList, _ as removeMaxAgeFromQuery, a as getClient, b as resolveSubjectIdentifier, c as getStoredToken, d as mergeDiscoveryMetadata, g as parsePrompt, h as parseClientMetadata, i as extractClientCredentials, k as applyOAuthProviderMetadataExtensions, l as isPKCERequired, m as parseBearerToken, n as decryptStoredClientSecret, o as getJwtPlugin, r as destructureCredentials, t as clientAllowsGrant, u as isSessionFreshForSignedQuery, v as removePromptFromQuery, x as searchParamsToQuery, z as isExtensionTokenEndpointAuthMethod } from "./utils-CGIZS-Da.mjs";
4
4
  import { a as setSignedOAuthQueryParameterNames, i as postLoginClearedParam, n as canonicalizeOAuthQueryParams, o as signedQueryIssuedAtParam, r as getSignedQueryIssuedAt } from "./signed-query-Df1MNiSH.mjs";
5
- import { t as PACKAGE_VERSION } from "./version-C2j2jEFL.mjs";
5
+ import { t as PACKAGE_VERSION } from "./version-BPS0os17.mjs";
6
6
  import { isBrowserFetchRequest } from "@better-auth/core/utils/fetch-metadata";
7
7
  import { isLoopbackHost, isLoopbackIP, isPublicRoutableHost } from "@better-auth/core/utils/host";
8
8
  import { appendQueryParams } from "@better-auth/core/utils/url";
@@ -16,12 +16,12 @@ import { BetterAuthError } from "@better-auth/core/error";
16
16
  import { deleteSessionCookie, parseSetCookieHeader } from "better-auth/cookies";
17
17
  import { mergeSchema } from "better-auth/db";
18
18
  import { CLIENT_ASSERTION_TYPE, DPOP_SIGNING_ALGORITHMS, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS } from "@better-auth/core/oauth2";
19
- import { base64Url } from "@better-auth/utils/base64";
20
19
  import { createHash } from "@better-auth/utils/hash";
21
20
  import { getJwks, stripAccessTokenAuthorizationScheme } from "better-auth/oauth2";
22
21
  import { compactVerify, createLocalJWKSet, decodeJwt, decodeProtectedHeader, jwtVerify } from "jose";
22
+ import { isNativePrivateUseRedirectUri } from "@better-auth/core/utils/redirect-uri";
23
23
  import { resolveSigningKey, signJWT, toExpJWT } from "better-auth/plugins";
24
- import { isReverseDomainPrivateUseRedirectUri } from "@better-auth/core/utils/redirect-uri";
24
+ import { encodeDeterministicId } from "@better-auth/core/db/internal";
25
25
  //#region src/consent.ts
26
26
  async function consentEndpoint(ctx, opts, authorize) {
27
27
  const oauthRequest = await oAuthState.get();
@@ -1172,7 +1172,7 @@ function stripReservedOAuthClientMetadataExtensions(metadata) {
1172
1172
  * @throws APIError UNAUTHORIZED when there is no session or the hook denies the action.
1173
1173
  * @throws APIError BAD_REQUEST when the request carries no headers.
1174
1174
  */
1175
- async function assertClientPrivileges(ctx, session, opts, action) {
1175
+ async function assertClientPrivileges(ctx, session, opts, action, clientId) {
1176
1176
  if (!session) throw new APIError("UNAUTHORIZED");
1177
1177
  if (!ctx.headers) throw new APIError("BAD_REQUEST");
1178
1178
  if (action === "configure-client-credentials-scopes" && !opts.clientPrivileges) throw new APIError("UNAUTHORIZED");
@@ -1180,7 +1180,36 @@ async function assertClientPrivileges(ctx, session, opts, action) {
1180
1180
  headers: ctx.headers,
1181
1181
  action,
1182
1182
  session: session.session,
1183
- user: session.user
1183
+ user: session.user,
1184
+ clientId
1185
+ })) throw new APIError("UNAUTHORIZED");
1186
+ }
1187
+ //#endregion
1188
+ //#region src/oauthResource/privileges.ts
1189
+ /**
1190
+ * Authorizes a resource action against the configured `resourcePrivileges`
1191
+ * hook. Gates every admin resource endpoint and the resource links selected
1192
+ * during managed client registration. Mirrors `assertClientPrivileges`:
1193
+ * a missing session → 401; a defined `resourcePrivileges` callback that
1194
+ * returns falsy → 401 with the original action context preserved.
1195
+ *
1196
+ * When `resourcePrivileges` is undefined, the gate degrades to "any
1197
+ * authenticated session can manage resources" — same forgiving default
1198
+ * as `clientPrivileges`. Operators who care about RBAC must define the
1199
+ * callback.
1200
+ *
1201
+ * @internal
1202
+ */
1203
+ async function assertResourcePrivileges(ctx, session, opts, action, resourceId) {
1204
+ if (!session) throw new APIError("UNAUTHORIZED");
1205
+ if (!ctx.headers) throw new APIError("BAD_REQUEST");
1206
+ if (!opts.resourcePrivileges) return;
1207
+ if (!await opts.resourcePrivileges({
1208
+ headers: ctx.headers,
1209
+ action,
1210
+ session: session.session,
1211
+ user: session.user,
1212
+ resourceId
1184
1213
  })) throw new APIError("UNAUTHORIZED");
1185
1214
  }
1186
1215
  //#endregion
@@ -1426,7 +1455,7 @@ async function consumeClientAssertion(ctx, opts, params) {
1426
1455
  error: "invalid_client"
1427
1456
  });
1428
1457
  const jtiDigest = await createHash("SHA-256").digest(new TextEncoder().encode(`${namespace}:${payload.jti}`));
1429
- const jtiId = base64Url.encode(new Uint8Array(jtiDigest).slice(0, 24), { padding: false });
1458
+ const jtiId = encodeDeterministicId(new Uint8Array(jtiDigest).slice(0, 24), ctx.context.options);
1430
1459
  try {
1431
1460
  await ctx.context.adapter.create({
1432
1461
  model: "oauthClientAssertion",
@@ -1637,14 +1666,31 @@ function applyOAuthClientRegistrationDefaults(client, defaultApplicationType = "
1637
1666
  response_types: resolveRegistrationResponseTypes(client, grantTypes)
1638
1667
  };
1639
1668
  }
1640
- const FORBIDDEN_NATIVE_REDIRECT_SCHEMES = new Set([
1641
- "file:",
1642
- "ftp:",
1643
- "mailto:",
1644
- "javascript:",
1645
- "data:",
1646
- "vbscript:"
1647
- ]);
1669
+ function isCustomSchemeWithHost(redirectUri) {
1670
+ const url = new URL(redirectUri);
1671
+ return url.protocol !== "http:" && url.protocol !== "https:" && url.host !== "";
1672
+ }
1673
+ function isNonHttpRedirectUri(redirectUri) {
1674
+ try {
1675
+ const protocol = new URL(redirectUri).protocol;
1676
+ return protocol !== "http:" && protocol !== "https:";
1677
+ } catch {
1678
+ return false;
1679
+ }
1680
+ }
1681
+ /**
1682
+ * Server-owned default for dynamic registrations that omit `application_type`.
1683
+ * `"infer"` classifies as native when any redirect URI uses a non-http(s)
1684
+ * scheme, because a web client can never register one. Every URI is then
1685
+ * validated against the native rules. A sent `application_type` is never
1686
+ * overridden because `applyOAuthClientRegistrationDefaults` only fills the
1687
+ * absent field.
1688
+ */
1689
+ function resolveDynamicRegistrationDefaultApplicationType(opts, client) {
1690
+ const configured = opts.clientRegistrationDefaultApplicationType ?? "web";
1691
+ if (configured !== "infer") return configured;
1692
+ return (client.redirect_uris ?? []).some(isNonHttpRedirectUri) ? "native" : "web";
1693
+ }
1648
1694
  function invalidRedirectUri(description) {
1649
1695
  throw new APIError("BAD_REQUEST", {
1650
1696
  error: "invalid_redirect_uri",
@@ -1661,13 +1707,15 @@ function getRawHttpHostname(redirectUri) {
1661
1707
  }
1662
1708
  return (hostAndPort.split(":")[0] ?? "").toLowerCase();
1663
1709
  }
1664
- async function resolveClientRegistrationResources(ctx, opts, requestedResources) {
1665
- const defaultResources = opts.clientRegistrationDefaultResources ?? [];
1666
- const allowedResources = new Set([...defaultResources, ...opts.clientRegistrationAllowedResources ?? []]);
1710
+ function assertDynamicRegistrationResourcesAllowed(opts, requestedResources) {
1711
+ const allowedResources = new Set([...opts.clientRegistrationDefaultResources ?? [], ...opts.clientRegistrationAllowedResources ?? []]);
1667
1712
  for (const identifier of requestedResources) if (!allowedResources.has(identifier)) throw new APIError("BAD_REQUEST", {
1668
1713
  error: "invalid_target",
1669
1714
  error_description: `requested resource ${identifier} is not allowed for client registration`
1670
1715
  });
1716
+ }
1717
+ async function resolveClientRegistrationResources(ctx, opts, requestedResources) {
1718
+ const defaultResources = opts.clientRegistrationDefaultResources ?? [];
1671
1719
  const resources = [...new Set([...defaultResources, ...requestedResources])];
1672
1720
  for (const identifier of resources) {
1673
1721
  const row = await getResource(ctx, opts, identifier);
@@ -1708,7 +1756,7 @@ function validateClientRedirectUri(redirectUri, applicationType) {
1708
1756
  if (!isAllowedNativeHttpLoopback) invalidRedirectUri(`native clients may use http only on the exact loopback hosts localhost, 127.0.0.1, or [::1]: ${redirectUri}`);
1709
1757
  return;
1710
1758
  }
1711
- if (FORBIDDEN_NATIVE_REDIRECT_SCHEMES.has(url.protocol) || !isReverseDomainPrivateUseRedirectUri(url)) invalidRedirectUri(`native private-use redirect URI schemes must be well-formed reverse-domain names, omit the naming authority, and must not use a reserved scheme: ${redirectUri}`);
1759
+ if (!isNativePrivateUseRedirectUri(url)) invalidRedirectUri(`native private-use redirect URI schemes must not use a reserved scheme; they must be an authority-free reverse-domain URI or a custom-scheme URI with a host and a path: ${redirectUri}`);
1712
1760
  }
1713
1761
  function assertValidRegistrationJwks(jwks) {
1714
1762
  const result = validatePublicClientJwks(jwks);
@@ -1754,7 +1802,7 @@ async function registerEndpoint(ctx, opts) {
1754
1802
  }
1755
1803
  async function checkOAuthClient(client, opts, settings) {
1756
1804
  const isClientMetadataDocument = settings?.registrationSource === "clientMetadataDocument";
1757
- const clientWithDefaults = applyOAuthClientRegistrationDefaults(client, isClientMetadataDocument ? null : "web");
1805
+ const clientWithDefaults = applyOAuthClientRegistrationDefaults(client, isClientMetadataDocument ? null : settings?.registrationSource === "dynamic" ? resolveDynamicRegistrationDefaultApplicationType(opts, client) : "web");
1758
1806
  const tokenEndpointAuthMethod = clientWithDefaults.token_endpoint_auth_method ?? "client_secret_basic";
1759
1807
  if (!new Set(getSupportedAuthMethods(opts, { includeNone: true })).has(tokenEndpointAuthMethod)) throw new APIError("BAD_REQUEST", {
1760
1808
  error: "invalid_client_metadata",
@@ -1803,6 +1851,10 @@ async function checkOAuthClient(client, opts, settings) {
1803
1851
  error: "invalid_client_metadata",
1804
1852
  error_description: "pairwise subject_type requires server pairwiseSecret configuration"
1805
1853
  });
1854
+ if (clientWithDefaults.subject_type === "pairwise" && clientWithDefaults.redirect_uris?.some(isCustomSchemeWithHost)) throw new APIError("BAD_REQUEST", {
1855
+ error: "invalid_client_metadata",
1856
+ error_description: "pairwise clients cannot use custom-scheme redirect URIs that include a host"
1857
+ });
1806
1858
  if (clientWithDefaults.subject_type === "pairwise" && clientWithDefaults.redirect_uris && clientWithDefaults.redirect_uris.length > 1) {
1807
1859
  if (new Set(clientWithDefaults.redirect_uris.map((uri) => new URL(uri).host)).size > 1) throw new APIError("BAD_REQUEST", {
1808
1860
  error: "invalid_client_metadata",
@@ -1914,10 +1966,11 @@ function isClientRegistrationCollision(error) {
1914
1966
  * boundary.
1915
1967
  */
1916
1968
  async function persistOAuthClientRegistration(ctx, opts, input) {
1917
- const body = applyOAuthClientRegistrationDefaults(input.registrationSource === "dynamic" && !input.metadata.scope ? {
1969
+ const registrationMetadata = input.registrationSource === "dynamic" && !input.metadata.scope ? {
1918
1970
  ...input.metadata,
1919
1971
  scope: (opts.clientRegistrationDefaultScopes ?? opts.scopes)?.join(" ")
1920
- } : input.metadata, input.registrationSource === "clientMetadataDocument" ? null : "web");
1972
+ } : input.metadata;
1973
+ const body = applyOAuthClientRegistrationDefaults(registrationMetadata, input.registrationSource === "clientMetadataDocument" ? null : input.registrationSource === "dynamic" ? resolveDynamicRegistrationDefaultApplicationType(opts, registrationMetadata) : "web");
1921
1974
  const isPublic = body.token_endpoint_auth_method === "none";
1922
1975
  const isPrivateKeyJwt = body.token_endpoint_auth_method === "private_key_jwt";
1923
1976
  const isExtensionAuthMethod = isExtensionTokenEndpointAuthMethod(opts, body.token_endpoint_auth_method);
@@ -1954,7 +2007,9 @@ async function persistOAuthClientRegistration(ctx, opts, input) {
1954
2007
  schema.clientCredentialsScopes = input.registrationSource === "clientMetadataDocument" ? input.existingClient?.clientCredentialsScopes ?? [] : input.registrationSource === "managed" ? input.clientCredentialsScopes ?? [] : [];
1955
2008
  schema.clientDiscoveryId = input.registrationSource === "clientMetadataDocument" ? input.clientDiscoveryId : null;
1956
2009
  if (input.registrationSource === "clientMetadataDocument" && body.application_type === void 0) schema.applicationType = null;
1957
- const resources = await resolveClientRegistrationResources(ctx, opts, input.registrationSource === "dynamic" ? input.requestedResources ?? [] : []);
2010
+ const requestedResources = input.registrationSource === "clientMetadataDocument" ? [] : input.requestedResources ?? [];
2011
+ if (input.registrationSource === "dynamic") assertDynamicRegistrationResourcesAllowed(opts, requestedResources);
2012
+ const resources = await resolveClientRegistrationResources(ctx, opts, requestedResources);
1958
2013
  const clientModel = opts.schema?.oauthClient?.modelName ?? "oauthClient";
1959
2014
  const clientResourceModel = opts.schema?.oauthClientResource?.modelName ?? "oauthClientResource";
1960
2015
  const existingClient = input.registrationSource === "clientMetadataDocument" ? input.existingClient : void 0;
@@ -2175,12 +2230,15 @@ async function createOAuthClientEndpoint(ctx, opts, settings) {
2175
2230
  const clientCredentialsScopes = settings?.admin ? normalizeClientCredentialsScopes(rawClientCredentialsScopes ?? []) : [];
2176
2231
  validateClientCredentialsScopes(clientCredentialsScopes, resolveRegistrationGrantTypes(metadata), metadata.token_endpoint_auth_method, opts);
2177
2232
  if (clientCredentialsScopes.length > 0) await assertClientPrivileges(ctx, session, opts, "configure-client-credentials-scopes");
2233
+ const requestedResources = settings?.admin ? metadata.resources : void 0;
2234
+ for (const identifier of new Set(requestedResources ?? [])) await assertResourcePrivileges(ctx, session, opts, "link", identifier);
2178
2235
  const registrationInput = {
2179
2236
  metadata,
2180
2237
  registrationSource: "managed",
2181
2238
  userId: referenceId ? void 0 : session.session.userId,
2182
2239
  referenceId,
2183
- clientCredentialsScopes
2240
+ clientCredentialsScopes,
2241
+ ...requestedResources ? { requestedResources } : {}
2184
2242
  };
2185
2243
  const responseBody = settings?.admin ? await createOAuthClientAdministrativeRegistration(ctx, opts, registrationInput) : await createOAuthClientRegistration(ctx, opts, registrationInput);
2186
2244
  ctx.setStatus(201);
@@ -2280,7 +2338,7 @@ function schemaToOAuth(input) {
2280
2338
  //#region src/oauthClient/endpoints.ts
2281
2339
  async function getClientEndpoint(ctx, opts) {
2282
2340
  const session = await getSessionFromCtx(ctx);
2283
- await assertClientPrivileges(ctx, session, opts, "read");
2341
+ await assertClientPrivileges(ctx, session, opts, "read", ctx.query.client_id);
2284
2342
  if (!session) throw new APIError("UNAUTHORIZED");
2285
2343
  const client = await getClient(ctx, opts, ctx.query.client_id);
2286
2344
  if (!client) throw new APIError("NOT_FOUND", {
@@ -2357,7 +2415,7 @@ async function getClientsEndpoint(ctx, opts) {
2357
2415
  }
2358
2416
  async function deleteClientEndpoint(ctx, opts) {
2359
2417
  const session = await getSessionFromCtx(ctx);
2360
- await assertClientPrivileges(ctx, session, opts, "delete");
2418
+ await assertClientPrivileges(ctx, session, opts, "delete", ctx.body.client_id);
2361
2419
  if (!session) throw new APIError("UNAUTHORIZED");
2362
2420
  const clientId = ctx.body.client_id;
2363
2421
  if (opts.cachedTrustedClients?.has(clientId)) throw new APIError("INTERNAL_SERVER_ERROR", {
@@ -2384,7 +2442,7 @@ async function deleteClientEndpoint(ctx, opts) {
2384
2442
  }
2385
2443
  async function updateClientEndpoint(ctx, opts, settings) {
2386
2444
  const session = await getSessionFromCtx(ctx);
2387
- await assertClientPrivileges(ctx, session, opts, "update");
2445
+ await assertClientPrivileges(ctx, session, opts, "update", ctx.body.client_id);
2388
2446
  if (!session) throw new APIError("UNAUTHORIZED");
2389
2447
  const clientId = ctx.body.client_id;
2390
2448
  if (opts.cachedTrustedClients?.has(clientId)) throw new APIError("INTERNAL_SERVER_ERROR", {
@@ -2400,7 +2458,7 @@ async function updateClientEndpoint(ctx, opts, settings) {
2400
2458
  const ownsClient = client.userId ? client.userId === session.user.id : client.referenceId && opts.clientReference ? client.referenceId === await opts.clientReference(session) : false;
2401
2459
  const isCrossOwnerScopeConfiguration = !ownsClient && settings?.admin && rawClientCredentialsScopes !== void 0 && Object.keys(updates).length === 0;
2402
2460
  if (!ownsClient && !isCrossOwnerScopeConfiguration) throw new APIError("UNAUTHORIZED");
2403
- if (isCrossOwnerScopeConfiguration) await assertClientPrivileges(ctx, session, opts, "configure-client-credentials-scopes");
2461
+ if (isCrossOwnerScopeConfiguration) await assertClientPrivileges(ctx, session, opts, "configure-client-credentials-scopes", clientId);
2404
2462
  if (Object.keys(updates).length === 0 && rawClientCredentialsScopes === void 0) {
2405
2463
  const res = schemaToOAuth(client);
2406
2464
  res.client_secret = void 0;
@@ -2415,7 +2473,7 @@ async function updateClientEndpoint(ctx, opts, settings) {
2415
2473
  const clientCredentialsScopes = rawClientCredentialsScopes === void 0 ? void 0 : normalizeClientCredentialsScopes(rawClientCredentialsScopes);
2416
2474
  if (clientCredentialsScopes !== void 0) {
2417
2475
  validateClientCredentialsScopes(clientCredentialsScopes, finalGrantTypes, finalTokenEndpointAuthMethod, opts);
2418
- if (clientCredentialsScopes.length > 0 && !isCrossOwnerScopeConfiguration) await assertClientPrivileges(ctx, session, opts, "configure-client-credentials-scopes");
2476
+ if (clientCredentialsScopes.length > 0 && !isCrossOwnerScopeConfiguration) await assertClientPrivileges(ctx, session, opts, "configure-client-credentials-scopes", clientId);
2419
2477
  }
2420
2478
  await checkOAuthClient({
2421
2479
  ...schemaToOAuth(client),
@@ -2455,7 +2513,7 @@ async function updateClientEndpoint(ctx, opts, settings) {
2455
2513
  }
2456
2514
  async function rotateClientSecretEndpoint(ctx, opts) {
2457
2515
  const session = await getSessionFromCtx(ctx);
2458
- await assertClientPrivileges(ctx, session, opts, "rotate");
2516
+ await assertClientPrivileges(ctx, session, opts, "rotate", ctx.body.client_id);
2459
2517
  if (!session) throw new APIError("UNAUTHORIZED");
2460
2518
  const clientId = ctx.body.client_id;
2461
2519
  if (opts.cachedTrustedClients?.has(clientId)) throw new APIError("INTERNAL_SERVER_ERROR", {
@@ -2525,6 +2583,7 @@ const adminCreateOAuthClient = (opts) => createAuthEndpoint("/admin/oauth2/creat
2525
2583
  jwks_uri: z.string().optional(),
2526
2584
  grant_types: grantTypesSchema.optional(),
2527
2585
  client_credentials_scopes: z.array(z.string().trim().min(1)).optional(),
2586
+ resources: z.array(ResourceUriSchema).optional(),
2528
2587
  response_types: z.array(z.enum(["code"])).optional(),
2529
2588
  client_secret_expires_at: z.union([z.string(), z.number()]).optional().default(0),
2530
2589
  skip_consent: z.boolean().optional(),
@@ -2889,6 +2948,7 @@ const adminUpdateOAuthClient = (opts) => createAuthEndpoint("/admin/oauth2/updat
2889
2948
  client_secret_expires_at: z.union([z.string(), z.number()]).optional(),
2890
2949
  skip_consent: z.boolean().optional(),
2891
2950
  enable_end_session: z.boolean().optional(),
2951
+ disabled: z.boolean().optional(),
2892
2952
  dpop_bound_access_tokens: z.boolean().optional(),
2893
2953
  metadata: z.record(z.string(), z.unknown()).optional()
2894
2954
  })
@@ -3085,30 +3145,6 @@ const deleteOAuthConsent = (opts) => createAuthEndpoint("/oauth2/delete-consent"
3085
3145
  });
3086
3146
  //#endregion
3087
3147
  //#region src/oauthResource/endpoints.ts
3088
- /**
3089
- * Gate every admin resource endpoint. Mirrors `assertClientPrivileges`:
3090
- * a missing session → 401; a defined `resourcePrivileges` callback that
3091
- * returns falsy → 401 with the original action context preserved.
3092
- *
3093
- * When `resourcePrivileges` is undefined, the gate degrades to "any
3094
- * authenticated session can manage resources" — same forgiving default
3095
- * as `clientPrivileges`. Operators who care about RBAC must define the
3096
- * callback.
3097
- *
3098
- * @internal
3099
- */
3100
- async function assertResourcePrivileges(ctx, session, opts, action, resourceId) {
3101
- if (!session) throw new APIError("UNAUTHORIZED");
3102
- if (!ctx.headers) throw new APIError("BAD_REQUEST");
3103
- if (!opts.resourcePrivileges) return;
3104
- if (!await opts.resourcePrivileges({
3105
- headers: ctx.headers,
3106
- action,
3107
- session: session.session,
3108
- user: session.user,
3109
- resourceId
3110
- })) throw new APIError("UNAUTHORIZED");
3111
- }
3112
3148
  const resourceModel = (opts) => opts.schema?.oauthResource?.modelName ?? "oauthResource";
3113
3149
  const linkModel = (opts) => opts.schema?.oauthClientResource?.modelName ?? "oauthClientResource";
3114
3150
  const clientModel = (opts) => opts.schema?.oauthClient?.modelName ?? "oauthClient";
@@ -4404,7 +4440,7 @@ const oauthProvider = (options) => {
4404
4440
  onRequest: handleIssuerMetadataRequest,
4405
4441
  init: async (ctx) => {
4406
4442
  if (ctx.options.secondaryStorage && ctx.options.session?.storeSessionInDatabase !== true) throw new BetterAuthError("OAuth Provider requires `session.storeSessionInDatabase: true` when using secondaryStorage");
4407
- await seedResources(ctx, opts);
4443
+ await seedResourcesOnce(ctx, opts).catch((error) => logStartupSeedFailure(ctx, error));
4408
4444
  logEnforcePerClientResourcesResolution(opts);
4409
4445
  if (!opts.disableJwtPlugin) {
4410
4446
  const jwtPluginOptions = getJwtPlugin(ctx)?.options;
@@ -1,4 +1,4 @@
1
- import { c as ResourceServerMetadata } from "./oauth-C_7TvkRL.mjs";
1
+ import { c as ResourceServerMetadata } from "./oauth-C_RnL2nt.mjs";
2
2
  import { ResourceRequestInput, VerifyAccessTokenRequestOptions } from "better-auth/oauth2";
3
3
  import { JWTPayload, JWTVerifyOptions } from "jose";
4
4
  import { BetterAuthOptions } from "better-auth/types";
@@ -1,5 +1,5 @@
1
- import { o as getJwtPlugin, s as getOAuthProviderPlugin } from "./utils-D6Hp4Qd_.mjs";
2
- import { t as PACKAGE_VERSION } from "./version-C2j2jEFL.mjs";
1
+ import { o as getJwtPlugin, s as getOAuthProviderPlugin } from "./utils-CGIZS-Da.mjs";
2
+ import { t as PACKAGE_VERSION } from "./version-BPS0os17.mjs";
3
3
  import { t as createResourceServerChallenge } from "./resource-challenge-Damwi0Or.mjs";
4
4
  import { APIError } from "better-call";
5
5
  import { BetterAuthError } from "@better-auth/core/error";
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { p as oauthDeviceAuthorization, r as oauthProvider } from "./oauth-Dtt-caL2.mjs";
1
+ import { p as oauthDeviceAuthorization, r as oauthProvider } from "./oauth-pKAoqtlo.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,5 +1,5 @@
1
1
  import { t as buildSignedOAuthQuery } from "./signed-query-Df1MNiSH.mjs";
2
- import { t as PACKAGE_VERSION } from "./version-C2j2jEFL.mjs";
2
+ import { t as PACKAGE_VERSION } from "./version-BPS0os17.mjs";
3
3
  import { safeJSONParse } from "@better-auth/core/utils/json";
4
4
  import { deviceAuthorizationClient } from "better-auth/client/plugins";
5
5
  //#region src/client.ts
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as OAuthOpaqueAccessToken, B as Prompt, C as OAuthClientAuthenticationStrategy, D as OAuthExtensionGrantHandler, E as OAuthConsent, F as OAuthResource, G as VerificationValue, H as Scope, I as OAuthResourceInput, J as ResourceUriSchema, K as ClientRegistrationRequest, L as OAuthTokenIssueParams, M as OAuthProviderApi, N as OAuthProviderExtension, O as OAuthExtensionGrantHandlerInput, P as OAuthRefreshToken, R as OAuthTokenResponse, S as OAuthClientAuthenticationResult, T as OAuthClientResource, U as StoreTokenType, V as SchemaClient, W as StoredAuthorizationQuery, Y as oauthClientMetadataSchema, _ as OAuthAuthorizationQuery, a as GrantType, b as OAuthClientAuthenticationInput, c as ResourceServerMetadata, d as ActiveAccessTokenPayload, f as AuthorizePrompt, g as OAuthAuthenticatedClient, h as InitialAccessTokenAuthorization, i as Confirmation, j as OAuthOptions, k as OAuthMetadataExtensionInput, l as TokenEndpointAuthMethod, m as ClientMetadataResourceFetch, n as AuthServerMetadata, o as OAuthClient, p as ClientDiscovery, q as OAuthClientMetadata, r as BearerMethodsSupported, s as OIDCMetadata, t as AuthMethod, u as TokenType, v as OAuthClaimExtensionInput, w as OAuthClientRegistrationResponse, x as OAuthClientAuthenticationRequest, y as OAuthClientAdministrativeResponse, z as OAuthUserInfoExtensionInput } from "./oauth-C_7TvkRL.mjs";
2
- import { a as OAuthEndpointErrorResult, c as OAuthFieldErrorCode, d as DEVICE_CODE_GRANT_TYPE, f as OAuthDeviceAuthorizationOptions, i as getIssuer, l as OAuthFieldErrorCodeMap, n as getOAuthProviderState, o as OAuthEndpointRedirectContext, p as oauthDeviceAuthorization, r as oauthProvider, s as OAuthErrorCode, t as DEFAULT_OAUTH_SCOPES, u as OAuthRedirectOnError } from "./oauth-Dtt-caL2.mjs";
1
+ import { A as OAuthOpaqueAccessToken, B as Prompt, C as OAuthClientAuthenticationStrategy, D as OAuthExtensionGrantHandler, E as OAuthConsent, F as OAuthResource, G as VerificationValue, H as Scope, I as OAuthResourceInput, J as ResourceUriSchema, K as ClientRegistrationRequest, L as OAuthTokenIssueParams, M as OAuthProviderApi, N as OAuthProviderExtension, O as OAuthExtensionGrantHandlerInput, P as OAuthRefreshToken, R as OAuthTokenResponse, S as OAuthClientAuthenticationResult, T as OAuthClientResource, U as StoreTokenType, V as SchemaClient, W as StoredAuthorizationQuery, Y as oauthClientMetadataSchema, _ as OAuthAuthorizationQuery, a as GrantType, b as OAuthClientAuthenticationInput, c as ResourceServerMetadata, d as ActiveAccessTokenPayload, f as AuthorizePrompt, g as OAuthAuthenticatedClient, h as InitialAccessTokenAuthorization, i as Confirmation, j as OAuthOptions, k as OAuthMetadataExtensionInput, l as TokenEndpointAuthMethod, m as ClientMetadataResourceFetch, n as AuthServerMetadata, o as OAuthClient, p as ClientDiscovery, q as OAuthClientMetadata, r as BearerMethodsSupported, s as OIDCMetadata, t as AuthMethod, u as TokenType, v as OAuthClaimExtensionInput, w as OAuthClientRegistrationResponse, x as OAuthClientAuthenticationRequest, y as OAuthClientAdministrativeResponse, z as OAuthUserInfoExtensionInput } from "./oauth-C_RnL2nt.mjs";
2
+ import { a as OAuthEndpointErrorResult, c as OAuthFieldErrorCode, d as DEVICE_CODE_GRANT_TYPE, f as OAuthDeviceAuthorizationOptions, i as getIssuer, l as OAuthFieldErrorCodeMap, n as getOAuthProviderState, o as OAuthEndpointRedirectContext, p as oauthDeviceAuthorization, r as oauthProvider, s as OAuthErrorCode, t as DEFAULT_OAUTH_SCOPES, u as OAuthRedirectOnError } from "./oauth-pKAoqtlo.mjs";
3
3
  import { APIError } from "better-call";
4
4
  import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
5
5
  import { AuthContext, GenericEndpointContext } from "@better-auth/core";
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { C as resolveResourcePolicy, c as ResourceUriSchema, i as getOAuthProviderApi, p as oauthClientMetadataSchema, v as extractRepeatedResourceFromForm } from "./introspect-CNR06Oy3.mjs";
2
- import { D as validateClientScopes, N as extendOAuthProvider, O as verifyOAuthQueryParams, T as toResourceList, a as getClient, f as normalizeClientAuthenticationParameters, s as getOAuthProviderPlugin, w as toAudienceClaim } from "./utils-D6Hp4Qd_.mjs";
3
- import { d as metadataResponse, f as oauthAuthorizationServerMetadata, h as oidcServerMetadata, i as oauthProvider, m as oauthProviderOpenIdConfigMetadata, n as DEFAULT_OAUTH_SCOPES, p as oauthProviderAuthServerMetadata, r as getOAuthProviderState, s as consumeClientAssertion, t as getIssuer, u as authServerMetadata } from "./authorize-K1WHCNih.mjs";
1
+ import { T as oauthClientMetadataSchema, b as ResourceUriSchema, f as extractRepeatedResourceFromForm, i as getOAuthProviderApi, v as resolveResourcePolicy } from "./introspect-H3uoUiuS.mjs";
2
+ import { D as validateClientScopes, N as extendOAuthProvider, O as verifyOAuthQueryParams, T as toResourceList, a as getClient, f as normalizeClientAuthenticationParameters, s as getOAuthProviderPlugin, w as toAudienceClaim } from "./utils-CGIZS-Da.mjs";
3
+ import { d as metadataResponse, f as oauthAuthorizationServerMetadata, h as oidcServerMetadata, i as oauthProvider, m as oauthProviderOpenIdConfigMetadata, n as DEFAULT_OAUTH_SCOPES, p as oauthProviderAuthServerMetadata, r as getOAuthProviderState, s as consumeClientAssertion, t as getIssuer, u as authServerMetadata } from "./authorize-C95F-9E1.mjs";
4
4
  import { t as createResourceServerChallenge } from "./resource-challenge-Damwi0Or.mjs";
5
5
  import { APIError } from "better-auth/api";
6
6
  import * as z from "zod";
@@ -1,4 +1,4 @@
1
- import { H as Scope, V as SchemaClient, j as OAuthOptions, o as OAuthClient } from "./oauth-C_7TvkRL.mjs";
1
+ import { H as Scope, V as SchemaClient, j as OAuthOptions, o as OAuthClient } from "./oauth-C_RnL2nt.mjs";
2
2
  import { JSONWebKeySet } from "jose";
3
3
  import { GenericEndpointContext } from "@better-auth/core";
4
4
 
package/dist/internal.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as registerClientMetadataDocument, c as isForbiddenCimdClientMetadataField, l as validatePublicClientJwks } from "./authorize-K1WHCNih.mjs";
1
+ import { a as registerClientMetadataDocument, c as isForbiddenCimdClientMetadataField, l as validatePublicClientJwks } from "./authorize-C95F-9E1.mjs";
2
2
  export { isForbiddenCimdClientMetadataField, registerClientMetadataDocument, validatePublicClientJwks };
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
2
- import { A as collectExtensionAccessTokenClaims, C as storeToken, E as validateClientCredentials, F as getExtensionGrantHandler, L as getSupportedGrantTypes, M as collectExtensionUserInfoClaims, R as hasUserInfoClaimExtension, T as toResourceList, a as getClient, b as resolveSubjectIdentifier, c as getStoredToken, h as parseClientMetadata, i as extractClientCredentials, j as collectExtensionIdTokenClaims, l as isPKCERequired, n as decryptStoredClientSecret, o as getJwtPlugin, p as normalizeTimestampValue, r as destructureCredentials, t as clientAllowsGrant, w as toAudienceClaim, y as resolveSessionAuthTime } from "./utils-D6Hp4Qd_.mjs";
2
+ import { A as collectExtensionAccessTokenClaims, C as storeToken, E as validateClientCredentials, F as getExtensionGrantHandler, L as getSupportedGrantTypes, M as collectExtensionUserInfoClaims, R as hasUserInfoClaimExtension, T as toResourceList, a as getClient, b as resolveSubjectIdentifier, c as getStoredToken, h as parseClientMetadata, i as extractClientCredentials, j as collectExtensionIdTokenClaims, l as isPKCERequired, n as decryptStoredClientSecret, o as getJwtPlugin, p as normalizeTimestampValue, r as destructureCredentials, t as clientAllowsGrant, w as toAudienceClaim, y as resolveSessionAuthTime } from "./utils-CGIZS-Da.mjs";
3
3
  import { APIError } from "better-auth/api";
4
4
  import { generateRandomString, symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
5
5
  import { APIError as APIError$1 } from "better-call";
@@ -7,8 +7,8 @@ import { logger } from "@better-auth/core/env";
7
7
  import * as z from "zod";
8
8
  import { createDpopReplayStore, enforceDpopBinding, generateCodeChallenge, getConfirmationJkt, getDpopJktFromPayload, getJwks, isDpopBindingError, isDpopProofError, parseAccessTokenAuthorization, stripAccessTokenAuthorizationScheme, verifyDpopProof } from "better-auth/oauth2";
9
9
  import { SignJWT, base64url, createLocalJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
10
- import { resolveSigningKey, signJWT, toExpJWT } from "better-auth/plugins";
11
10
  import { SafeUrlSchema } from "@better-auth/core/utils/redirect-uri";
11
+ import { resolveSigningKey, signJWT, toExpJWT } from "better-auth/plugins";
12
12
  //#region src/authentication-context.ts
13
13
  const RESERVED_ID_TOKEN_CLAIMS = new Set([
14
14
  "iss",
@@ -265,6 +265,187 @@ async function resolveAccessTokenClaims(input) {
265
265
  });
266
266
  }
267
267
  //#endregion
268
+ //#region src/types/zod.ts
269
+ const DANGEROUS_SCHEMES = [
270
+ "javascript:",
271
+ "data:",
272
+ "vbscript:"
273
+ ];
274
+ /**
275
+ * Validates an RFC 8707 resource indicator. The value must be an absolute URI
276
+ * with no fragment (RFC 8707 §2). Unlike a redirect URI it is not restricted to
277
+ * HTTPS, because a resource server identifier may use any absolute URI scheme;
278
+ * configured OAuth resources are the authoritative control over
279
+ * which resources a token may target.
280
+ */
281
+ const ResourceUriSchema = z.string().superRefine((val, ctx) => {
282
+ if (!URL.canParse(val)) {
283
+ ctx.addIssue({
284
+ code: "custom",
285
+ message: "resource must be an absolute URI",
286
+ fatal: true
287
+ });
288
+ return z.NEVER;
289
+ }
290
+ if (val.includes("#")) {
291
+ ctx.addIssue({
292
+ code: "custom",
293
+ message: "resource must not contain a fragment"
294
+ });
295
+ return;
296
+ }
297
+ if (DANGEROUS_SCHEMES.includes(new URL(val).protocol)) ctx.addIssue({
298
+ code: "custom",
299
+ message: "resource cannot use javascript:, data:, or vbscript: scheme"
300
+ });
301
+ });
302
+ const authorizationPromptTokenSchema = z.enum([
303
+ "none",
304
+ "consent",
305
+ "login",
306
+ "create",
307
+ "select_account"
308
+ ]);
309
+ const authorizationPromptSchema = z.string().superRefine((value, ctx) => {
310
+ const promptTokens = value.split(" ").map((token) => token.trim()).filter(Boolean);
311
+ const promptSet = /* @__PURE__ */ new Set();
312
+ if (!promptTokens.length) {
313
+ ctx.addIssue({
314
+ code: "custom",
315
+ message: "prompt must include at least one value"
316
+ });
317
+ return;
318
+ }
319
+ for (const token of promptTokens) {
320
+ const result = authorizationPromptTokenSchema.safeParse(token);
321
+ if (!result.success) {
322
+ ctx.addIssue({
323
+ code: "custom",
324
+ message: `unsupported prompt value: ${token}`
325
+ });
326
+ continue;
327
+ }
328
+ promptSet.add(result.data);
329
+ }
330
+ if (promptSet.has("none") && promptSet.size > 1) ctx.addIssue({
331
+ code: "custom",
332
+ message: "prompt=none cannot be combined with other prompt values"
333
+ });
334
+ });
335
+ /** Canonical RFC 7517 JWK Set metadata for an OAuth client. */
336
+ const clientJwksSchema = z.object({ keys: z.array(z.record(z.string(), z.unknown())).min(1) });
337
+ const maxAgeSchema = z.union([z.number(), z.string().trim().min(1)]).transform((value, ctx) => {
338
+ const maxAge = typeof value === "number" ? value : Number(value);
339
+ if (!Number.isInteger(maxAge) || maxAge < 0) {
340
+ ctx.addIssue({
341
+ code: "custom",
342
+ message: "max_age must be a non-negative integer"
343
+ });
344
+ return z.NEVER;
345
+ }
346
+ return maxAge;
347
+ });
348
+ const dpopJktSchema = z.string().regex(/^[A-Za-z0-9_-]{43}$/, "dpop_jkt must be a base64url-encoded SHA-256 JWK thumbprint");
349
+ /**
350
+ * Runtime schema for OAuthAuthorizationQuery.
351
+ * Uses passthrough to tolerate fields added by future extensions (PAR, FPA, etc.)
352
+ */
353
+ const authorizationQuerySchema = z.object({
354
+ response_type: z.string().pipe(z.enum(["code"])).optional(),
355
+ request: z.string().optional(),
356
+ request_uri: z.string().optional(),
357
+ redirect_uri: SafeUrlSchema.optional(),
358
+ scope: z.string().optional(),
359
+ state: z.string().optional(),
360
+ client_id: z.string().min(1, "client_id is required"),
361
+ prompt: authorizationPromptSchema.optional(),
362
+ display: z.string().optional(),
363
+ ui_locales: z.string().optional(),
364
+ max_age: maxAgeSchema.optional(),
365
+ acr_values: z.string().optional(),
366
+ login_hint: z.string().optional(),
367
+ id_token_hint: z.string().optional(),
368
+ code_challenge: z.string().optional(),
369
+ code_challenge_method: z.string().pipe(z.enum(["S256"])).optional(),
370
+ nonce: z.string().optional(),
371
+ claims: claimsRequestInputSchema.optional(),
372
+ dpop_jkt: dpopJktSchema.optional(),
373
+ resource: z.union([ResourceUriSchema, z.array(ResourceUriSchema).min(1)]).optional()
374
+ }).passthrough();
375
+ const storedAuthorizationQuerySchema = authorizationQuerySchema.extend({
376
+ redirect_uri: SafeUrlSchema.optional(),
377
+ claims: claimsRequestParameterSchema.optional()
378
+ });
379
+ /**
380
+ * Runtime schema for the authorization code verification value.
381
+ * Validates structure on deserialization from the JSON blob stored in the DB.
382
+ * Uses passthrough so future fields (e.g. from authorization challenge) don't break parsing.
383
+ */
384
+ const verificationValueSchema = z.object({
385
+ type: z.literal("authorization_code"),
386
+ query: storedAuthorizationQuerySchema,
387
+ sessionId: z.string(),
388
+ userId: z.string(),
389
+ referenceId: z.string().optional(),
390
+ authTime: z.number().optional(),
391
+ resource: z.array(z.string()).optional()
392
+ }).passthrough();
393
+ /**
394
+ * Request body accepted at `POST /oauth2/register` (RFC 7591 §2 client
395
+ * metadata). This is the single source of truth for the registration contract:
396
+ * the endpoint validates against it and {@link ClientRegistrationRequest} is
397
+ * inferred from it, so the type a `validateInitialAccessToken` callback receives
398
+ * always matches what is actually validated. `grant_types` and
399
+ * `token_endpoint_auth_method` are open strings because extensions can register
400
+ * custom values. Server-assigned fields (`client_id`, `client_secret`, the
401
+ * issued/expiry timestamps) and internal state (`disabled`, `reference_id`) are
402
+ * never part of a registration request.
403
+ *
404
+ * @see https://datatracker.ietf.org/doc/html/rfc7591#section-2
405
+ */
406
+ const clientRegistrationRequestSchema = z.object({
407
+ redirect_uris: z.array(SafeUrlSchema).min(1).optional(),
408
+ scope: z.string().optional(),
409
+ client_name: z.string().refine((value) => value.trim().length > 0, "client_name cannot be empty").optional(),
410
+ client_uri: z.string().optional(),
411
+ logo_uri: z.string().optional(),
412
+ contacts: z.array(z.string().min(1)).min(1).optional(),
413
+ tos_uri: z.string().optional(),
414
+ policy_uri: z.string().optional(),
415
+ software_id: z.string().optional(),
416
+ software_version: z.string().optional(),
417
+ software_statement: z.string().optional(),
418
+ post_logout_redirect_uris: z.array(SafeUrlSchema).min(1).optional(),
419
+ backchannel_logout_uri: SafeUrlSchema.optional(),
420
+ backchannel_logout_session_required: z.boolean().optional(),
421
+ token_endpoint_auth_method: z.string().trim().min(1).optional(),
422
+ jwks: clientJwksSchema.optional(),
423
+ jwks_uri: z.string().optional(),
424
+ grant_types: z.array(z.string().trim().min(1)).min(1).optional(),
425
+ response_types: z.array(z.enum(["code"])).optional(),
426
+ application_type: z.enum(["web", "native"]).optional(),
427
+ subject_type: z.enum(["public", "pairwise"]).optional(),
428
+ dpop_bound_access_tokens: z.boolean().optional(),
429
+ resources: z.array(ResourceUriSchema).optional(),
430
+ skip_consent: z.never({ error: "skip_consent cannot be set during dynamic client registration" }).optional()
431
+ });
432
+ /**
433
+ * Complete OAuth client metadata document schema shared by registration and
434
+ * verified client-discovery integrations.
435
+ *
436
+ * Unlike {@link clientRegistrationRequestSchema}, a metadata document carries
437
+ * its own identifier and may contain the server-assigned secret fields that a
438
+ * discovery profile must explicitly prohibit. Keeping those fields in the
439
+ * parsed shape lets profile validation distinguish a forbidden value from an
440
+ * unknown extension without unsafe casts or ad-hoc allowlists.
441
+ */
442
+ const oauthClientMetadataSchema = clientRegistrationRequestSchema.omit({ skip_consent: true }).extend({
443
+ client_id: z.string().refine((value) => value.trim().length > 0, "client_id cannot be empty"),
444
+ client_secret: z.string().optional(),
445
+ client_secret_expires_at: z.number().int().nonnegative().optional(),
446
+ client_id_issued_at: z.number().int().nonnegative().optional()
447
+ }).loose();
448
+ //#endregion
268
449
  //#region src/resources.ts
269
450
  /**
270
451
  * Source-of-truth list of asymmetric JWS algorithms supported by the JWT
@@ -317,18 +498,10 @@ async function checkIdentifier(opts, identifier) {
317
498
  };
318
499
  return { ok: true };
319
500
  }
320
- let url;
321
- try {
322
- url = new URL(identifier);
323
- } catch {
324
- return {
325
- ok: false,
326
- reason: `resource identifier ${identifier} must be an absolute URI (RFC 8707 §2)`
327
- };
328
- }
329
- if (url.hash) return {
501
+ const parsed = ResourceUriSchema.safeParse(identifier);
502
+ if (!parsed.success) return {
330
503
  ok: false,
331
- reason: `resource identifier ${identifier} must not contain a URI fragment (RFC 8707 §2)`
504
+ reason: `resource identifier ${identifier} is invalid: ${parsed.error.issues[0]?.message ?? "not an absolute URI"}`
332
505
  };
333
506
  return { ok: true };
334
507
  }
@@ -654,10 +827,10 @@ function invalidateResourceCache(identifier) {
654
827
  * Looks up an {@link OAuthResource} by `identifier`, consulting the in-process
655
828
  * cache when the identifier is in {@link OAuthOptions.cachedResources}.
656
829
  *
657
- * Triggers the lazy seed via {@link seedResourcesOnce} on first call — this
658
- * is the safety net for deployments where migrations run after plugin init
659
- * (so seeding at init couldn't see the table yet). The seed is idempotent
660
- * and coalesced, so the cost is paid only once per process.
830
+ * Triggers the lazy seed via {@link seedResourcesOnce} on first call — the
831
+ * retry path when the startup seed failed (database unreachable, or
832
+ * migrations run after plugin init). The seed is idempotent and coalesced,
833
+ * so the cost is paid only once per process.
661
834
  *
662
835
  * Returns a defensive copy so callers can't mutate cached state.
663
836
  *
@@ -748,29 +921,56 @@ function buildSeedUpdate(input, mode, now) {
748
921
  }
749
922
  /**
750
923
  * Pattern matched against adapter errors to detect the "table not yet
751
- * created" case — i.e. migrations haven't been run. When matched, the
752
- * caller treats the seed as deferred (will retry on first resource access).
924
+ * created" case — i.e. migrations or `auth generate` haven't run. Only
925
+ * used to keep the expected startup seed failure quiet.
753
926
  *
754
927
  * Covers SQLite ("no such table"), Postgres ("relation X does not exist"),
755
- * and MySQL ("Table X does not exist" / contracted form).
928
+ * MySQL ("Table X does not exist" / contracted form), and the Drizzle and
929
+ * Prisma adapters' missing-model errors.
930
+ */
931
+ const MISSING_TABLE_PATTERN = /no such table|relation.*does not exist|table.*does(?: not|n'?t) exist|model.*(?:does not exist|was not found in the schema)/i;
932
+ /**
933
+ * Logs a failed startup seed. Missing tables are expected before migrations
934
+ * and stay quiet; anything else is an error worth seeing.
935
+ *
936
+ * @internal
756
937
  */
757
- const MISSING_TABLE_PATTERN = /no such table|relation.*does not exist|table.*does(?: not|n[''']?t) exist/i;
938
+ function logStartupSeedFailure(ctx, error) {
939
+ let missingTable = false;
940
+ let current = error;
941
+ for (let depth = 0; current && !missingTable && depth < 5; depth++) {
942
+ missingTable = MISSING_TABLE_PATTERN.test(current instanceof Error ? current.message : String(current));
943
+ current = current instanceof Error ? current.cause : void 0;
944
+ }
945
+ if (missingTable) {
946
+ ctx.logger.debug("oauth-provider: oauthResource table not yet created; deferring resource seed to first access.");
947
+ return;
948
+ }
949
+ ctx.logger.error("oauth-provider: could not seed `resources` at startup; retrying on first resource access.", error);
950
+ }
758
951
  /**
759
- * Per-adapter state for the lazy-seed path. A module-level boolean would let
760
- * one Better Auth instance suppress seeding for every later instance in the same
761
- * process. Endpoint `AuthContext` objects are not guaranteed to be stable across
762
- * requests, so keying by the adapter keeps one seed state per backing store.
952
+ * Lazy-seed state per adapter and plugin options. A module-level boolean would
953
+ * let one Better Auth instance suppress seeding for every later instance in the
954
+ * same process. Endpoint `AuthContext` objects are not guaranteed to be stable
955
+ * across requests, so the adapter identifies the backing store, and the options
956
+ * object separates instances that share an adapter but configure different
957
+ * `resources`.
763
958
  */
764
959
  let seedStates = /* @__PURE__ */ new WeakMap();
765
- function getSeedState(ctx) {
766
- const key = ctx.adapter;
767
- const existing = seedStates.get(key);
960
+ function getSeedState(ctx, opts) {
961
+ const adapterKey = ctx.adapter;
962
+ let byOptions = seedStates.get(adapterKey);
963
+ if (!byOptions) {
964
+ byOptions = /* @__PURE__ */ new WeakMap();
965
+ seedStates.set(adapterKey, byOptions);
966
+ }
967
+ const existing = byOptions.get(opts);
768
968
  if (existing) return existing;
769
969
  const created = {
770
970
  completed: false,
771
971
  promise: null
772
972
  };
773
- seedStates.set(key, created);
973
+ byOptions.set(opts, created);
774
974
  return created;
775
975
  }
776
976
  /**
@@ -790,13 +990,11 @@ function getSeedState(ctx) {
790
990
  *
791
991
  * Race-safety: the `identifier` column carries a UNIQUE constraint, so two
792
992
  * processes booting simultaneously can each attempt the insert — one wins,
793
- * the other catches the constraint error and treats it as a no-op.
993
+ * the other re-reads the row and treats the failed insert as a no-op.
794
994
  *
795
- * Migration ordering: at plugin `init` time, tables may not exist yet
796
- * (Better Auth's test harness, and many deployment setups, run migrations
797
- * after auth construction). Seeding tolerates "no such table" errors and
798
- * defers — the lazy {@link seedResourcesOnce} path picks up the work on
799
- * the first resource access.
995
+ * Throws on storage errors, including a missing table. Callers go through
996
+ * {@link seedResourcesOnce}, which resets so the next resource access
997
+ * retries (e.g. after migrations run).
800
998
  *
801
999
  * Idempotent: safe to call multiple times.
802
1000
  *
@@ -819,69 +1017,45 @@ async function seedResources(ctx, opts) {
819
1017
  const { signingAlgorithm: _, ...rest } = input;
820
1018
  input = rest;
821
1019
  }
822
- let existing;
823
- try {
824
- existing = await ctx.adapter.findOne({
1020
+ const where = [{
1021
+ field: "identifier",
1022
+ value: input.identifier
1023
+ }];
1024
+ const existing = await ctx.adapter.findOne({
1025
+ model: modelName,
1026
+ where
1027
+ });
1028
+ const now = /* @__PURE__ */ new Date();
1029
+ if (!existing) try {
1030
+ await ctx.adapter.create({
825
1031
  model: modelName,
826
- where: [{
827
- field: "identifier",
828
- value: input.identifier
829
- }]
1032
+ data: buildSeedRow(input, now)
830
1033
  });
831
- } catch (err) {
832
- const message = err instanceof Error ? err.message : String(err);
833
- if (MISSING_TABLE_PATTERN.test(message)) {
834
- logger.debug("oauth-provider: oauthResource table not yet created; deferring resource seed to first access.");
835
- return;
836
- }
837
- throw err;
838
- }
839
- const now = /* @__PURE__ */ new Date();
840
- if (!existing) {
841
- try {
842
- await ctx.adapter.create({
843
- model: modelName,
844
- data: buildSeedRow(input, now)
845
- });
846
- } catch (err) {
847
- const message = err instanceof Error ? err.message : String(err);
848
- if (/unique|duplicate|UNIQUE/i.test(message)) {
849
- logger.debug(`oauth-provider: resource ${input.identifier} already inserted by a concurrent process — skipping.`);
850
- continue;
851
- }
852
- if (MISSING_TABLE_PATTERN.test(message)) {
853
- logger.debug("oauth-provider: oauthResource table not yet created; deferring resource seed to first access.");
854
- return;
855
- }
856
- throw err;
857
- }
858
1034
  continue;
1035
+ } catch (err) {
1036
+ if (!await ctx.adapter.findOne({
1037
+ model: modelName,
1038
+ where
1039
+ }).catch(() => null)) throw err;
859
1040
  }
860
1041
  if (mode === "insertOnly") continue;
861
1042
  await ctx.adapter.update({
862
1043
  model: modelName,
863
- where: [{
864
- field: "identifier",
865
- value: input.identifier
866
- }],
1044
+ where,
867
1045
  update: buildSeedUpdate(input, mode, now)
868
1046
  });
869
1047
  }
870
1048
  }
871
1049
  /**
872
- * Idempotent, coalesced wrapper around {@link seedResources} for the
873
- * lazy-seed path. Safe to call from every resource lookup — the first call
874
- * runs the seed, concurrent calls await the same promise, and subsequent
875
- * calls become no-ops.
876
- *
877
- * The endpoint context shape (`{ context: AuthContext }`) is what
878
- * `getResource` receives, so this is a convenience overload that unwraps
879
- * for callers.
1050
+ * Idempotent, coalesced wrapper around {@link seedResources}, called from
1051
+ * plugin `init` and from every resource lookup. The first call runs the
1052
+ * seed, concurrent calls await the same promise, and calls after a success
1053
+ * are no-ops. A failure resets the state so the next call retries.
880
1054
  *
881
1055
  * @internal
882
1056
  */
883
1057
  async function seedResourcesOnce(ctx, opts) {
884
- const state = getSeedState(ctx);
1058
+ const state = getSeedState(ctx, opts);
885
1059
  if (state.completed === true) return;
886
1060
  if (state.promise !== null) return state.promise;
887
1061
  state.promise = seedResources(ctx, opts).then(() => {
@@ -944,187 +1118,6 @@ function validateClientCredentialsScopes(scopes, grantTypes, tokenEndpointAuthMe
944
1118
  });
945
1119
  }
946
1120
  //#endregion
947
- //#region src/types/zod.ts
948
- const DANGEROUS_SCHEMES = [
949
- "javascript:",
950
- "data:",
951
- "vbscript:"
952
- ];
953
- /**
954
- * Validates an RFC 8707 resource indicator. The value must be an absolute URI
955
- * with no fragment (RFC 8707 §2). Unlike a redirect URI it is not restricted to
956
- * HTTPS, because a resource server identifier may use any absolute URI scheme;
957
- * configured OAuth resources are the authoritative control over
958
- * which resources a token may target.
959
- */
960
- const ResourceUriSchema = z.string().superRefine((val, ctx) => {
961
- if (!URL.canParse(val)) {
962
- ctx.addIssue({
963
- code: "custom",
964
- message: "resource must be an absolute URI",
965
- fatal: true
966
- });
967
- return z.NEVER;
968
- }
969
- if (val.includes("#")) {
970
- ctx.addIssue({
971
- code: "custom",
972
- message: "resource must not contain a fragment"
973
- });
974
- return;
975
- }
976
- if (DANGEROUS_SCHEMES.includes(new URL(val).protocol)) ctx.addIssue({
977
- code: "custom",
978
- message: "resource cannot use javascript:, data:, or vbscript: scheme"
979
- });
980
- });
981
- const authorizationPromptTokenSchema = z.enum([
982
- "none",
983
- "consent",
984
- "login",
985
- "create",
986
- "select_account"
987
- ]);
988
- const authorizationPromptSchema = z.string().superRefine((value, ctx) => {
989
- const promptTokens = value.split(" ").map((token) => token.trim()).filter(Boolean);
990
- const promptSet = /* @__PURE__ */ new Set();
991
- if (!promptTokens.length) {
992
- ctx.addIssue({
993
- code: "custom",
994
- message: "prompt must include at least one value"
995
- });
996
- return;
997
- }
998
- for (const token of promptTokens) {
999
- const result = authorizationPromptTokenSchema.safeParse(token);
1000
- if (!result.success) {
1001
- ctx.addIssue({
1002
- code: "custom",
1003
- message: `unsupported prompt value: ${token}`
1004
- });
1005
- continue;
1006
- }
1007
- promptSet.add(result.data);
1008
- }
1009
- if (promptSet.has("none") && promptSet.size > 1) ctx.addIssue({
1010
- code: "custom",
1011
- message: "prompt=none cannot be combined with other prompt values"
1012
- });
1013
- });
1014
- /** Canonical RFC 7517 JWK Set metadata for an OAuth client. */
1015
- const clientJwksSchema = z.object({ keys: z.array(z.record(z.string(), z.unknown())).min(1) });
1016
- const maxAgeSchema = z.union([z.number(), z.string().trim().min(1)]).transform((value, ctx) => {
1017
- const maxAge = typeof value === "number" ? value : Number(value);
1018
- if (!Number.isInteger(maxAge) || maxAge < 0) {
1019
- ctx.addIssue({
1020
- code: "custom",
1021
- message: "max_age must be a non-negative integer"
1022
- });
1023
- return z.NEVER;
1024
- }
1025
- return maxAge;
1026
- });
1027
- const dpopJktSchema = z.string().regex(/^[A-Za-z0-9_-]{43}$/, "dpop_jkt must be a base64url-encoded SHA-256 JWK thumbprint");
1028
- /**
1029
- * Runtime schema for OAuthAuthorizationQuery.
1030
- * Uses passthrough to tolerate fields added by future extensions (PAR, FPA, etc.)
1031
- */
1032
- const authorizationQuerySchema = z.object({
1033
- response_type: z.string().pipe(z.enum(["code"])).optional(),
1034
- request: z.string().optional(),
1035
- request_uri: z.string().optional(),
1036
- redirect_uri: SafeUrlSchema.optional(),
1037
- scope: z.string().optional(),
1038
- state: z.string().optional(),
1039
- client_id: z.string().min(1, "client_id is required"),
1040
- prompt: authorizationPromptSchema.optional(),
1041
- display: z.string().optional(),
1042
- ui_locales: z.string().optional(),
1043
- max_age: maxAgeSchema.optional(),
1044
- acr_values: z.string().optional(),
1045
- login_hint: z.string().optional(),
1046
- id_token_hint: z.string().optional(),
1047
- code_challenge: z.string().optional(),
1048
- code_challenge_method: z.string().pipe(z.enum(["S256"])).optional(),
1049
- nonce: z.string().optional(),
1050
- claims: claimsRequestInputSchema.optional(),
1051
- dpop_jkt: dpopJktSchema.optional(),
1052
- resource: z.union([ResourceUriSchema, z.array(ResourceUriSchema).min(1)]).optional()
1053
- }).passthrough();
1054
- const storedAuthorizationQuerySchema = authorizationQuerySchema.extend({
1055
- redirect_uri: SafeUrlSchema.optional(),
1056
- claims: claimsRequestParameterSchema.optional()
1057
- });
1058
- /**
1059
- * Runtime schema for the authorization code verification value.
1060
- * Validates structure on deserialization from the JSON blob stored in the DB.
1061
- * Uses passthrough so future fields (e.g. from authorization challenge) don't break parsing.
1062
- */
1063
- const verificationValueSchema = z.object({
1064
- type: z.literal("authorization_code"),
1065
- query: storedAuthorizationQuerySchema,
1066
- sessionId: z.string(),
1067
- userId: z.string(),
1068
- referenceId: z.string().optional(),
1069
- authTime: z.number().optional(),
1070
- resource: z.array(z.string()).optional()
1071
- }).passthrough();
1072
- /**
1073
- * Request body accepted at `POST /oauth2/register` (RFC 7591 §2 client
1074
- * metadata). This is the single source of truth for the registration contract:
1075
- * the endpoint validates against it and {@link ClientRegistrationRequest} is
1076
- * inferred from it, so the type a `validateInitialAccessToken` callback receives
1077
- * always matches what is actually validated. `grant_types` and
1078
- * `token_endpoint_auth_method` are open strings because extensions can register
1079
- * custom values. Server-assigned fields (`client_id`, `client_secret`, the
1080
- * issued/expiry timestamps) and internal state (`disabled`, `reference_id`) are
1081
- * never part of a registration request.
1082
- *
1083
- * @see https://datatracker.ietf.org/doc/html/rfc7591#section-2
1084
- */
1085
- const clientRegistrationRequestSchema = z.object({
1086
- redirect_uris: z.array(SafeUrlSchema).min(1).optional(),
1087
- scope: z.string().optional(),
1088
- client_name: z.string().refine((value) => value.trim().length > 0, "client_name cannot be empty").optional(),
1089
- client_uri: z.string().optional(),
1090
- logo_uri: z.string().optional(),
1091
- contacts: z.array(z.string().min(1)).min(1).optional(),
1092
- tos_uri: z.string().optional(),
1093
- policy_uri: z.string().optional(),
1094
- software_id: z.string().optional(),
1095
- software_version: z.string().optional(),
1096
- software_statement: z.string().optional(),
1097
- post_logout_redirect_uris: z.array(SafeUrlSchema).min(1).optional(),
1098
- backchannel_logout_uri: SafeUrlSchema.optional(),
1099
- backchannel_logout_session_required: z.boolean().optional(),
1100
- token_endpoint_auth_method: z.string().trim().min(1).optional(),
1101
- jwks: clientJwksSchema.optional(),
1102
- jwks_uri: z.string().optional(),
1103
- grant_types: z.array(z.string().trim().min(1)).min(1).optional(),
1104
- response_types: z.array(z.enum(["code"])).optional(),
1105
- application_type: z.enum(["web", "native"]).optional(),
1106
- subject_type: z.enum(["public", "pairwise"]).optional(),
1107
- dpop_bound_access_tokens: z.boolean().optional(),
1108
- resources: z.array(ResourceUriSchema).optional(),
1109
- skip_consent: z.never({ error: "skip_consent cannot be set during dynamic client registration" }).optional()
1110
- });
1111
- /**
1112
- * Complete OAuth client metadata document schema shared by registration and
1113
- * verified client-discovery integrations.
1114
- *
1115
- * Unlike {@link clientRegistrationRequestSchema}, a metadata document carries
1116
- * its own identifier and may contain the server-assigned secret fields that a
1117
- * discovery profile must explicitly prohibit. Keeping those fields in the
1118
- * parsed shape lets profile validation distinguish a forbidden value from an
1119
- * unknown extension without unsafe casts or ad-hoc allowlists.
1120
- */
1121
- const oauthClientMetadataSchema = clientRegistrationRequestSchema.omit({ skip_consent: true }).extend({
1122
- client_id: z.string().refine((value) => value.trim().length > 0, "client_id cannot be empty"),
1123
- client_secret: z.string().optional(),
1124
- client_secret_expires_at: z.number().int().nonnegative().optional(),
1125
- client_id_issued_at: z.number().int().nonnegative().optional()
1126
- }).loose();
1127
- //#endregion
1128
1121
  //#region src/userinfo.ts
1129
1122
  /**
1130
1123
  * Builds the standard OIDC claims (OIDC Core §5.1) for the UserInfo response.
@@ -2548,4 +2541,4 @@ async function introspectEndpoint(ctx, opts) {
2548
2541
  }
2549
2542
  }
2550
2543
  //#endregion
2551
- export { filterClaimsRequestUserInfoClaims as A, resolveResourcePolicy as C, getSupportedClaims as D, STANDARD_CLAIM_NAMES as E, isValidOidcClaimsRequest as M, canSatisfyEssentialAcrRequest as O, logEnforcePerClientResourcesResolution as S, STANDARD_CLAIMS as T, assertIdentifierValid as _, invalidateRefreshFamily as a, invalidateResourceCache as b, ResourceUriSchema as c, clientJwksSchema as d, clientRegistrationRequestSchema as f, JWS_ALGORITHMS as g, validateClientCredentialsScopes as h, getOAuthProviderApi as i, getRequestedUserInfoClaims as j, claimsRequestParameterSchema as k, SafeUrlSchema as l, normalizeClientCredentialsScopes as m, introspect_exports as n, tokenEndpoint as o, oauthClientMetadataSchema as p, decodeRefreshToken as r, userInfoEndpoint as s, introspectEndpoint as t, authorizationQuerySchema as u, extractRepeatedResourceFromForm as v, seedResources as w, isAudienceClaimAllowed as x, getResource as y };
2544
+ export { claimsRequestParameterSchema as A, clientJwksSchema as C, STANDARD_CLAIM_NAMES as D, STANDARD_CLAIMS as E, getRequestedUserInfoClaims as M, isValidOidcClaimsRequest as N, getSupportedClaims as O, authorizationQuerySchema as S, oauthClientMetadataSchema as T, logStartupSeedFailure as _, invalidateRefreshFamily as a, ResourceUriSchema as b, normalizeClientCredentialsScopes as c, assertIdentifierValid as d, extractRepeatedResourceFromForm as f, logEnforcePerClientResourcesResolution as g, isAudienceClaimAllowed as h, getOAuthProviderApi as i, filterClaimsRequestUserInfoClaims as j, canSatisfyEssentialAcrRequest as k, validateClientCredentialsScopes as l, invalidateResourceCache as m, introspect_exports as n, tokenEndpoint as o, getResource as p, decodeRefreshToken as r, userInfoEndpoint as s, introspectEndpoint as t, JWS_ALGORITHMS as u, resolveResourcePolicy as v, clientRegistrationRequestSchema as w, SafeUrlSchema as x, seedResourcesOnce as y };
@@ -1429,6 +1429,27 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
1429
1429
  * @default true
1430
1430
  */
1431
1431
  clientRegistrationRequirePKCE?: boolean;
1432
+ /**
1433
+ * Default `application_type` for dynamic client registrations that omit the
1434
+ * field.
1435
+ *
1436
+ * - `"web"`: the OIDC Registration §2 default. Omitted `application_type` is
1437
+ * treated as `web`, so custom-scheme redirect URIs are rejected.
1438
+ * - `"native"`: omitted `application_type` is treated as `native`.
1439
+ * - `"infer"`: omitted `application_type` is treated as `native` when any
1440
+ * `redirect_uris` entry uses a non-http(s) scheme; otherwise `web`. Every
1441
+ * redirect URI is then validated against the native rules. Lets native MCP
1442
+ * clients that omit `application_type` (for example Cursor, which also
1443
+ * registers https and loopback callbacks) register without loosening
1444
+ * web-client redirect policy.
1445
+ *
1446
+ * An `application_type` the client actually sends is never overridden.
1447
+ * Applies only to dynamic client registration; managed clients and Client ID
1448
+ * Metadata Documents are unaffected.
1449
+ *
1450
+ * @default "web"
1451
+ */
1452
+ clientRegistrationDefaultApplicationType?: "web" | "native" | "infer";
1432
1453
  /**
1433
1454
  * How long a dynamically created confidential client
1434
1455
  * should last for.
@@ -1480,6 +1501,16 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
1480
1501
  action: "create" | "read" | "update" | "delete" | "list" | "rotate" | "configure-client-credentials-scopes";
1481
1502
  user?: User & Record<string, unknown>;
1482
1503
  session?: Session & Record<string, unknown>;
1504
+ /**
1505
+ * The `client_id` from the request, for actions on an existing client.
1506
+ * Absent for `create`, `list`, and scope configuration during creation.
1507
+ * The client may not exist, and this value does not establish ownership.
1508
+ * Client CRUD enforces ownership except when `adminUpdateOAuthClient`
1509
+ * changes only `client_credentials_scopes`: the `update` and
1510
+ * `configure-client-credentials-scopes` decisions can authorize access
1511
+ * to another owner's client.
1512
+ */
1513
+ clientId?: string;
1483
1514
  }) => Awaitable<boolean | undefined>;
1484
1515
  /**
1485
1516
  * Grant types supported by the token endpoint
@@ -1,4 +1,4 @@
1
- import { B as Prompt, E as OAuthConsent, F as OAuthResource, H as Scope, O as OAuthExtensionGrantHandlerInput, R as OAuthTokenResponse, a as GrantType, j as OAuthOptions, k as OAuthMetadataExtensionInput, l as TokenEndpointAuthMethod, n as AuthServerMetadata, o as OAuthClient, u as TokenType, w as OAuthClientRegistrationResponse, y as OAuthClientAdministrativeResponse } from "./oauth-C_7TvkRL.mjs";
1
+ import { B as Prompt, E as OAuthConsent, F as OAuthResource, H as Scope, O as OAuthExtensionGrantHandlerInput, R as OAuthTokenResponse, a as GrantType, j as OAuthOptions, k as OAuthMetadataExtensionInput, l as TokenEndpointAuthMethod, n as AuthServerMetadata, o as OAuthClient, u as TokenType, w as OAuthClientRegistrationResponse, y as OAuthClientAdministrativeResponse } from "./oauth-C_RnL2nt.mjs";
2
2
  import * as _$better_call0 from "better-call";
3
3
  import * as z from "zod";
4
4
  import * as _$jose from "jose";
@@ -1785,6 +1785,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1785
1785
  jwks_uri: z.ZodOptional<z.ZodString>;
1786
1786
  grant_types: z.ZodOptional<z.ZodArray<z.ZodString>>;
1787
1787
  client_credentials_scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
1788
+ resources: z.ZodOptional<z.ZodArray<z.ZodString>>;
1788
1789
  response_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
1789
1790
  code: "code";
1790
1791
  }>>>;
@@ -2280,6 +2281,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
2280
2281
  client_secret_expires_at: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
2281
2282
  skip_consent: z.ZodOptional<z.ZodBoolean>;
2282
2283
  enable_end_session: z.ZodOptional<z.ZodBoolean>;
2284
+ disabled: z.ZodOptional<z.ZodBoolean>;
2283
2285
  dpop_bound_access_tokens: z.ZodOptional<z.ZodBoolean>;
2284
2286
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
2285
2287
  }, z.core.$strip>;
@@ -712,7 +712,7 @@ async function extractClientCredentials(ctx, opts, expectedAudience) {
712
712
  confirmation: result.confirmation
713
713
  };
714
714
  }
715
- const { verifyClientAssertion: verify } = await import("./authorize-K1WHCNih.mjs").then((n) => n.o);
715
+ const { verifyClientAssertion: verify } = await import("./authorize-C95F-9E1.mjs").then((n) => n.o);
716
716
  return {
717
717
  kind: "pre_verified",
718
718
  method: "private_key_jwt",
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.7.7";
3
+ const PACKAGE_VERSION = "1.7.8";
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.7.7",
3
+ "version": "1.7.8",
4
4
  "description": "An oauth provider plugin for Better Auth",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -71,16 +71,16 @@
71
71
  "devDependencies": {
72
72
  "listhen": "^1.9.0",
73
73
  "tsdown": "0.21.10",
74
- "@better-auth/core": "1.7.7",
75
- "@better-auth/memory-adapter": "1.7.7",
76
- "better-auth": "1.7.7"
74
+ "@better-auth/core": "1.7.8",
75
+ "@better-auth/memory-adapter": "1.7.8",
76
+ "better-auth": "1.7.8"
77
77
  },
78
78
  "peerDependencies": {
79
- "@better-auth/utils": "0.4.2",
79
+ "@better-auth/utils": "^0.4.2 || ^0.5.0",
80
80
  "@better-fetch/fetch": "1.3.2",
81
- "better-call": "1.4.0",
82
- "@better-auth/core": "^1.7.7",
83
- "better-auth": "^1.7.7"
81
+ "better-call": "1.4.2",
82
+ "@better-auth/core": "^1.7.8",
83
+ "better-auth": "^1.7.8"
84
84
  },
85
85
  "scripts": {
86
86
  "build": "tsdown",