@better-auth/oauth-provider 1.7.6 → 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-CbYi2MJT.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-CoqjzHuU.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-9KCSx144.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;
@@ -5445,11 +5481,19 @@ function findRegisteredRedirectUri(registered, requested) {
5445
5481
  return stripLoopbackRedirectPort(url) === requestedWithoutPort;
5446
5482
  });
5447
5483
  }
5484
+ /** Keep URI syntax and credential checks outside the custom trust decision. */
5485
+ function isSafeCustomRedirectUri(uri) {
5486
+ if (!SafeUrlSchema.safeParse(uri).success) return false;
5487
+ const parsed = new URL(uri);
5488
+ return parsed.username.length === 0 && parsed.password.length === 0;
5489
+ }
5448
5490
  /**
5449
5491
  * Loads the client, verifies it's enabled, and returns the requested
5450
- * redirect_uri when it matches a registered entry. Returns null whenever the
5451
- * RP cannot be safely reached, so callers can fall back to the server error
5452
- * page (avoiding open-redirect risk on validation failures).
5492
+ * redirect_uri when it matches a registered entry (or is accepted by the
5493
+ * custom `validateRedirectUri` option, mirroring the authorize endpoint's
5494
+ * validation). Returns null whenever the RP cannot be safely reached, so
5495
+ * callers can fall back to the server error page (avoiding open-redirect
5496
+ * risk on validation failures).
5453
5497
  */
5454
5498
  async function resolveTrustedRedirectUri(ctx, opts, clientId, redirectUri) {
5455
5499
  if (!clientId || !redirectUri) return null;
@@ -5460,7 +5504,17 @@ async function resolveTrustedRedirectUri(ctx, opts, clientId, redirectUri) {
5460
5504
  return null;
5461
5505
  }
5462
5506
  if (!client || client.disabled) return null;
5463
- return findRegisteredRedirectUri(client.redirectUris, redirectUri) ? redirectUri : null;
5507
+ const registeredUris = client.redirectUris ?? [];
5508
+ const matched = findRegisteredRedirectUri(registeredUris, redirectUri);
5509
+ if (opts.validateRedirectUri) {
5510
+ if (!isSafeCustomRedirectUri(redirectUri)) return null;
5511
+ try {
5512
+ return await opts.validateRedirectUri(redirectUri, registeredUris, Boolean(matched)) ? redirectUri : null;
5513
+ } catch {
5514
+ return null;
5515
+ }
5516
+ }
5517
+ return matched ? redirectUri : null;
5464
5518
  }
5465
5519
  /**
5466
5520
  * `redirectOnError` callback for `/oauth2/authorize`. Per RFC 6749 §4.1.2.1,
@@ -5552,7 +5606,18 @@ async function authorizeEndpoint(ctx, opts, settings) {
5552
5606
  if (!client) return handleRedirect(ctx, getErrorURL(ctx, "invalid_client", "client_id is required"));
5553
5607
  if (client.disabled) return handleRedirect(ctx, getErrorURL(ctx, "client_disabled", "client is disabled"));
5554
5608
  if (!clientAllowsGrant(client, "authorization_code")) return handleRedirect(ctx, getErrorURL(ctx, "unauthorized_client", "client is not authorized to use the authorization_code grant"));
5555
- if (!findRegisteredRedirectUri(client.redirectUris, query.redirect_uri) || !query.redirect_uri) return handleRedirect(ctx, getErrorURL(ctx, "invalid_redirect", "invalid redirect uri"));
5609
+ const registeredUris = client.redirectUris ?? [];
5610
+ let isValidRedirectUri = false;
5611
+ if (query.redirect_uri) {
5612
+ const defaultResult = Boolean(findRegisteredRedirectUri(registeredUris, query.redirect_uri));
5613
+ if (opts.validateRedirectUri && isSafeCustomRedirectUri(query.redirect_uri)) try {
5614
+ isValidRedirectUri = await opts.validateRedirectUri(query.redirect_uri, registeredUris, defaultResult);
5615
+ } catch {
5616
+ isValidRedirectUri = false;
5617
+ }
5618
+ else if (!opts.validateRedirectUri) isValidRedirectUri = defaultResult;
5619
+ }
5620
+ if (!isValidRedirectUri) return handleRedirect(ctx, getErrorURL(ctx, "invalid_redirect", "invalid redirect uri"));
5556
5621
  let requestedScopes = query.scope?.split(" ").filter((s) => s);
5557
5622
  if (requestedScopes) {
5558
5623
  const validScopes = new Set(client.scopes ?? opts.scopes);
@@ -1,4 +1,4 @@
1
- import { c as ResourceServerMetadata } from "./oauth-1Ud-hvZY.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-CoqjzHuU.mjs";
2
- import { t as PACKAGE_VERSION } from "./version-9KCSx144.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-CJ85_W0W.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-9KCSx144.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,10 +1,9 @@
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-1Ud-hvZY.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-CJ85_W0W.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";
6
6
  import * as _$better_auth0 from "better-auth";
7
-
8
7
  //#region src/extensions.d.ts
9
8
  /**
10
9
  * Registers an {@link OAuthProviderExtension} with the OAuth Provider plugin
@@ -160,6 +159,14 @@ declare function createResourceServerChallenge(error: unknown, resource: string
160
159
  */
161
160
  declare function getOAuthProviderApi(ctx: GenericEndpointContext, opts: OAuthOptions<Scope[]>, grantType?: GrantType): OAuthProviderApi;
162
161
  //#endregion
162
+ //#region src/utils/index.d.ts
163
+ /**
164
+ * Verify the signature and expiration of an OAuth query issued by this provider.
165
+ * Use the provider's current auth secret on the server before displaying a
166
+ * consent page. A valid signature does not replace client or session checks.
167
+ */
168
+ declare function verifyOAuthQueryParams(oauth_query: string, secret: string): Promise<boolean>;
169
+ //#endregion
163
170
  //#region src/utils/client-assertion.d.ts
164
171
  /**
165
172
  * Enforces the assertion-hygiene claims every client-assertion authentication
@@ -193,4 +200,4 @@ declare function consumeClientAssertion(ctx: GenericEndpointContext, opts: OAuth
193
200
  expectedAudience: string;
194
201
  }): Promise<void>;
195
202
  //#endregion
196
- export { ActiveAccessTokenPayload, AuthMethod, AuthServerMetadata, AuthorizePrompt, BearerMethodsSupported, ClientDiscovery, ClientMetadataResourceFetch, ClientRegistrationRequest, Confirmation, DEFAULT_OAUTH_SCOPES, DEVICE_CODE_GRANT_TYPE, GrantType, InitialAccessTokenAuthorization, OAuthAuthenticatedClient, OAuthAuthorizationQuery, OAuthClaimExtensionInput, type OAuthClient, OAuthClientAdministrativeResponse, OAuthClientAuthenticationInput, OAuthClientAuthenticationRequest, OAuthClientAuthenticationResult, OAuthClientAuthenticationStrategy, type OAuthClientMetadata, OAuthClientRegistrationResponse, OAuthClientResource, OAuthConsent, type OAuthDeviceAuthorizationOptions, type OAuthEndpointErrorResult, type OAuthEndpointRedirectContext, type OAuthErrorCode, OAuthExtensionGrantHandler, OAuthExtensionGrantHandlerInput, type OAuthFieldErrorCode, type OAuthFieldErrorCodeMap, OAuthMetadataExtensionInput, OAuthOpaqueAccessToken, OAuthOptions, OAuthProviderApi, OAuthProviderExtension, type OAuthRedirectOnError, OAuthRefreshToken, OAuthResource, OAuthResourceInput, OAuthTokenIssueParams, OAuthTokenResponse, OAuthUserInfoExtensionInput, OIDCMetadata, Prompt, type ResourceServerMetadata, ResourceUriSchema, SchemaClient, Scope, StoreTokenType, StoredAuthorizationQuery, TokenEndpointAuthMethod, TokenType, VerificationValue, authServerMetadata, consumeClientAssertion, createResourceServerChallenge, extendOAuthProvider, getIssuer, getOAuthProviderApi, getOAuthProviderState, metadataResponse, oauthAuthorizationServerMetadata, oauthClientMetadataSchema, oauthDeviceAuthorization, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
203
+ export { ActiveAccessTokenPayload, AuthMethod, AuthServerMetadata, AuthorizePrompt, BearerMethodsSupported, ClientDiscovery, ClientMetadataResourceFetch, ClientRegistrationRequest, Confirmation, DEFAULT_OAUTH_SCOPES, DEVICE_CODE_GRANT_TYPE, GrantType, InitialAccessTokenAuthorization, OAuthAuthenticatedClient, OAuthAuthorizationQuery, OAuthClaimExtensionInput, type OAuthClient, OAuthClientAdministrativeResponse, OAuthClientAuthenticationInput, OAuthClientAuthenticationRequest, OAuthClientAuthenticationResult, OAuthClientAuthenticationStrategy, type OAuthClientMetadata, OAuthClientRegistrationResponse, OAuthClientResource, OAuthConsent, type OAuthDeviceAuthorizationOptions, type OAuthEndpointErrorResult, type OAuthEndpointRedirectContext, type OAuthErrorCode, OAuthExtensionGrantHandler, OAuthExtensionGrantHandlerInput, type OAuthFieldErrorCode, type OAuthFieldErrorCodeMap, OAuthMetadataExtensionInput, OAuthOpaqueAccessToken, OAuthOptions, OAuthProviderApi, OAuthProviderExtension, type OAuthRedirectOnError, OAuthRefreshToken, OAuthResource, OAuthResourceInput, OAuthTokenIssueParams, OAuthTokenResponse, OAuthUserInfoExtensionInput, OIDCMetadata, Prompt, type ResourceServerMetadata, ResourceUriSchema, SchemaClient, Scope, StoreTokenType, StoredAuthorizationQuery, TokenEndpointAuthMethod, TokenType, VerificationValue, authServerMetadata, consumeClientAssertion, createResourceServerChallenge, extendOAuthProvider, getIssuer, getOAuthProviderApi, getOAuthProviderState, metadataResponse, oauthAuthorizationServerMetadata, oauthClientMetadataSchema, oauthDeviceAuthorization, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata, verifyOAuthQueryParams };
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-CbYi2MJT.mjs";
2
- import { D as validateClientScopes, N as extendOAuthProvider, T as toResourceList, a as getClient, f as normalizeClientAuthenticationParameters, s as getOAuthProviderPlugin, w as toAudienceClaim } from "./utils-CoqjzHuU.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-CLuqtSXQ.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";
@@ -228,4 +228,4 @@ function oauthDeviceAuthorization(options = {}) {
228
228
  };
229
229
  }
230
230
  //#endregion
231
- export { DEFAULT_OAUTH_SCOPES, DEVICE_CODE_GRANT_TYPE, ResourceUriSchema, authServerMetadata, consumeClientAssertion, createResourceServerChallenge, extendOAuthProvider, getIssuer, getOAuthProviderApi, getOAuthProviderState, metadataResponse, oauthAuthorizationServerMetadata, oauthClientMetadataSchema, oauthDeviceAuthorization, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
231
+ export { DEFAULT_OAUTH_SCOPES, DEVICE_CODE_GRANT_TYPE, ResourceUriSchema, authServerMetadata, consumeClientAssertion, createResourceServerChallenge, extendOAuthProvider, getIssuer, getOAuthProviderApi, getOAuthProviderState, metadataResponse, oauthAuthorizationServerMetadata, oauthClientMetadataSchema, oauthDeviceAuthorization, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata, verifyOAuthQueryParams };
@@ -1,4 +1,4 @@
1
- import { H as Scope, V as SchemaClient, j as OAuthOptions, o as OAuthClient } from "./oauth-1Ud-hvZY.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-CLuqtSXQ.mjs";
1
+ import { a as registerClientMetadataDocument, c as isForbiddenCimdClientMetadataField, l as validatePublicClientJwks } from "./authorize-C95F-9E1.mjs";
2
2
  export { isForbiddenCimdClientMetadataField, registerClientMetadataDocument, validatePublicClientJwks };