@better-auth/oauth-provider 1.7.0-beta.3 → 1.7.0-beta.5

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,6 +1,9 @@
1
- import { i as getClient } from "./utils-_Jr_enAe.mjs";
1
+ import { o as getClient } from "./utils-D2dLqo7f.mjs";
2
2
  import { APIError } from "better-call";
3
- import { ASSERTION_SIGNING_ALGORITHMS, CLIENT_ASSERTION_TYPE } from "@better-auth/core/oauth2";
3
+ import { CLIENT_ASSERTION_TYPE, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS } from "@better-auth/core/oauth2";
4
+ import { isPublicRoutableHost } from "@better-auth/core/utils/host";
5
+ import { base64Url } from "@better-auth/utils/base64";
6
+ import { createHash } from "@better-auth/utils/hash";
4
7
  import { createLocalJWKSet, decodeJwt, decodeProtectedHeader, jwtVerify } from "jose";
5
8
  //#region \0rolldown/runtime.js
6
9
  var __defProp = Object.defineProperty;
@@ -33,34 +36,22 @@ function setJwksCache(uri, jwks, fetchedAt) {
33
36
  if (oldest !== void 0) jwksCache.delete(oldest);
34
37
  }
35
38
  }
36
- const ALGORITHMS_LIST = [...ASSERTION_SIGNING_ALGORITHMS];
37
- const pendingAssertionIds = /* @__PURE__ */ new Set();
39
+ const ALGORITHMS_LIST = [...PRIVATE_KEY_JWT_SIGNING_ALGORITHMS];
38
40
  /**
39
- * Block SSRF: reject jwks_uri pointing at private/reserved IP ranges.
40
- * Only HTTPS with public hostnames is allowed.
41
+ * SSRF gate for user-supplied server-side fetch targets (`jwks_uri`,
42
+ * `backchannel_logout_uri`): returns true when the host is NOT publicly
43
+ * routable. That covers loopback, RFC 1918 private, link-local (including AWS
44
+ * IMDS `169.254.169.254`), shared-address-space (carrier-grade NAT),
45
+ * IPv4-mapped IPv6, 6to4/NAT64/Teredo tunnels, every other RFC 6890
46
+ * special-purpose range, and cloud-metadata FQDNs.
47
+ *
48
+ * Delegates to the audited single source of truth so this check cannot drift
49
+ * into the kind of encoding bypass that bespoke regexes invite. This is a
50
+ * syntactic check only: it does not resolve DNS, so a public name that
51
+ * resolves to a private address at fetch time is not caught here.
41
52
  */
42
- function isPrivateIpv4(hostname) {
43
- const parts = hostname.split(".");
44
- if (parts.length !== 4 || parts.some((p) => !/^\d{1,3}$/.test(p))) return false;
45
- const octets = parts.map(Number);
46
- const a = octets[0];
47
- const b = octets[1];
48
- return a === 10 || a === 0 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254 || a === 127;
49
- }
50
53
  function isPrivateHostname(hostname) {
51
- const lower = hostname.toLowerCase();
52
- const host = lower.startsWith("[") && lower.endsWith("]") ? lower.slice(1, -1) : lower;
53
- if (host === "localhost" || host === "::1") return true;
54
- if (isPrivateIpv4(host)) return true;
55
- if (host.includes(":")) {
56
- const v4MappedMatch = host.match(/^(?:0{0,4}:){0,4}:?(?:0{0,4}:)?ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
57
- if (v4MappedMatch && isPrivateIpv4(v4MappedMatch[1])) return true;
58
- const isLinkLocal = host.startsWith("fe8") || host.startsWith("fe9") || host.startsWith("fea") || host.startsWith("feb");
59
- const isUniqueLocal = host.startsWith("fc") || host.startsWith("fd");
60
- if (isLinkLocal || isUniqueLocal) return true;
61
- }
62
- if (host === "metadata.google.internal") return true;
63
- return false;
54
+ return !isPublicRoutableHost(hostname);
64
55
  }
65
56
  function validateJwksUri(ctx, jwksUri, clientIdUrlOrigin) {
66
57
  const parsed = new URL(jwksUri);
@@ -160,7 +151,7 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
160
151
  error: "invalid_client"
161
152
  });
162
153
  }
163
- if (!header.alg || !ASSERTION_SIGNING_ALGORITHMS.includes(header.alg)) throw new APIError("BAD_REQUEST", {
154
+ if (!header.alg || !ALGORITHMS_LIST.includes(header.alg)) throw new APIError("BAD_REQUEST", {
164
155
  error_description: `unsupported assertion signing algorithm: ${header.alg}`,
165
156
  error: "invalid_client"
166
157
  });
@@ -244,33 +235,33 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
244
235
  error_description: "client assertion must include jti claim",
245
236
  error: "invalid_client"
246
237
  });
247
- const jtiIdentifier = `private_key_jwt:${clientId}:${payload.jti}`;
248
- if (pendingAssertionIds.has(jtiIdentifier)) throw new APIError("BAD_REQUEST", {
249
- error_description: "client assertion jti has already been used",
250
- error: "invalid_client"
251
- });
252
- pendingAssertionIds.add(jtiIdentifier);
238
+ const jtiDigest = await createHash("SHA-256").digest(new TextEncoder().encode(`private_key_jwt:${clientId}:${payload.jti}`));
239
+ const jtiId = base64Url.encode(new Uint8Array(jtiDigest).slice(0, 24), { padding: false });
253
240
  try {
254
- if (await ctx.context.internalAdapter.findVerificationValue(jtiIdentifier)) throw new APIError("BAD_REQUEST", {
241
+ await ctx.context.adapter.create({
242
+ model: "oauthClientAssertion",
243
+ data: {
244
+ id: jtiId,
245
+ expiresAt: /* @__PURE__ */ new Date(payload.exp * 1e3)
246
+ },
247
+ forceAllowId: true
248
+ });
249
+ } catch (createErr) {
250
+ let alreadyUsed = false;
251
+ try {
252
+ alreadyUsed = Boolean(await ctx.context.adapter.findOne({
253
+ model: "oauthClientAssertion",
254
+ where: [{
255
+ field: "id",
256
+ value: jtiId
257
+ }]
258
+ }));
259
+ } catch {}
260
+ if (alreadyUsed) throw new APIError("BAD_REQUEST", {
255
261
  error_description: "client assertion jti has already been used",
256
262
  error: "invalid_client"
257
263
  });
258
- const jtiExpiry = /* @__PURE__ */ new Date(payload.exp * 1e3);
259
- try {
260
- await ctx.context.internalAdapter.createVerificationValue({
261
- identifier: jtiIdentifier,
262
- value: clientId,
263
- expiresAt: jtiExpiry
264
- });
265
- } catch (createErr) {
266
- if (await ctx.context.internalAdapter.findVerificationValue(jtiIdentifier)) throw new APIError("BAD_REQUEST", {
267
- error_description: "client assertion jti has already been used",
268
- error: "invalid_client"
269
- });
270
- throw createErr;
271
- }
272
- } finally {
273
- pendingAssertionIds.delete(jtiIdentifier);
264
+ throw createErr;
274
265
  }
275
266
  return {
276
267
  clientId,
@@ -1,9 +1,16 @@
1
- import { s as ResourceServerMetadata } from "./oauth-Ds-ejTJY.mjs";
1
+ import { s as ResourceServerMetadata } from "./oauth-BXrYl5x6.mjs";
2
2
  import { JWTPayload, JWTVerifyOptions } from "jose";
3
- import { Auth } from "better-auth/types";
3
+ import { BetterAuthOptions } from "better-auth/types";
4
4
 
5
5
  //#region src/client-resource.d.ts
6
- declare const oauthProviderResourceClient: <T extends Auth | undefined>(auth?: T) => {
6
+ type ResourceClientAuth = {
7
+ options: {
8
+ baseURL?: BetterAuthOptions["baseURL"];
9
+ basePath?: BetterAuthOptions["basePath"];
10
+ };
11
+ $context: Promise<unknown>;
12
+ };
13
+ declare const oauthProviderResourceClient: <T extends ResourceClientAuth | undefined = undefined>(auth?: T) => {
7
14
  id: "oauth-provider-resource-client";
8
15
  version: string;
9
16
  getActions(): {
@@ -42,8 +49,22 @@ interface VerifyAccessTokenRemote {
42
49
  * is also still active.
43
50
  */
44
51
  force?: boolean;
52
+ /**
53
+ * Accept introspection responses that omit the `aud` claim even when a
54
+ * required `audience` is configured in `verifyOptions`.
55
+ *
56
+ * By default verification fails closed: if you configure an `audience` and
57
+ * the introspection response has no `aud` (or a mismatching one), the token
58
+ * is rejected. Some authorization servers legitimately omit `aud` from
59
+ * introspection responses (it is OPTIONAL per RFC 7662 §2.2); only enable
60
+ * this if you trust the issuer to bind the token to this resource through
61
+ * another mechanism, as it skips the audience check in that case.
62
+ *
63
+ * @default false
64
+ */
65
+ allowMissingAudience?: boolean;
45
66
  }
46
- type VerifyAccessTokenOutput<T> = T extends Auth ? (token: string | undefined, opts?: VerifyAccessTokenAuthOpts) => Promise<JWTPayload> : (token: string | undefined, opts: VerifyAccessTokenNoAuthOpts) => Promise<JWTPayload>;
67
+ type VerifyAccessTokenOutput<T> = T extends undefined ? (token: string | undefined, opts: VerifyAccessTokenNoAuthOpts) => Promise<JWTPayload> : (token: string | undefined, opts?: VerifyAccessTokenAuthOpts) => Promise<JWTPayload>;
47
68
  type VerifyAccessTokenAuthOpts = {
48
69
  verifyOptions?: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience">>;
49
70
  scopes?: string[];
@@ -64,12 +85,12 @@ type VerifyAccessTokenNoAuthOpts = {
64
85
  remoteVerify: VerifyAccessTokenRemote; /** Maps non-url (ie urn, client) resources to resource_metadata */
65
86
  resourceMetadataMappings?: Record<string, string>;
66
87
  };
67
- type ProtectedResourceMetadataOutput<T> = T extends Auth ? (overrides?: Partial<ResourceServerMetadata>, opts?: {
88
+ type ProtectedResourceMetadataOutput<T> = T extends undefined ? (overrides: ResourceServerMetadata, opts?: {
68
89
  silenceWarnings?: {
69
90
  oidcScopes?: boolean;
70
91
  };
71
92
  externalScopes?: string[];
72
- }) => Promise<ResourceServerMetadata> : (overrides: ResourceServerMetadata, opts?: {
93
+ }) => Promise<ResourceServerMetadata> : (overrides?: Partial<ResourceServerMetadata>, opts?: {
73
94
  silenceWarnings?: {
74
95
  oidcScopes?: boolean;
75
96
  };
@@ -1,6 +1,6 @@
1
1
  import { t as handleMcpErrors } from "./mcp-CYnz-MXn.mjs";
2
- import { a as getJwtPlugin, o as getOAuthProviderPlugin } from "./utils-_Jr_enAe.mjs";
3
- import { t as PACKAGE_VERSION } from "./version-CG1YnCiF.mjs";
2
+ import { c as getOAuthProviderPlugin, s as getJwtPlugin } from "./utils-D2dLqo7f.mjs";
3
+ import { t as PACKAGE_VERSION } from "./version-B1ZiRmxj.mjs";
4
4
  import { verifyAccessToken } from "better-auth/oauth2";
5
5
  import { APIError } from "better-call";
6
6
  import { logger } from "@better-auth/core/env";
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as oauthProvider } from "./oauth-BxP4Iupj.mjs";
1
+ import { n as oauthProvider } from "./oauth-DU6NeviY.mjs";
2
2
  import * as _better_fetch_fetch0 from "@better-fetch/fetch";
3
3
 
4
4
  //#region src/client.d.ts
package/dist/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-CG1YnCiF.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-B1ZiRmxj.mjs";
2
2
  import { safeJSONParse } from "@better-auth/core/utils/json";
3
3
  //#region src/client.ts
4
4
  function parseSignedQuery(search) {
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as SchemaClient, a as OAuthClient, b as VerificationValue, c as TokenEndpointAuthMethod, d as OAuthAuthorizationQuery, f as OAuthConsent, g as Prompt, h as OAuthRefreshToken, i as GrantType, l as AuthorizePrompt, m as OAuthOptions, n as AuthServerMetadata, o as OIDCMetadata, p as OAuthOpaqueAccessToken, r as BearerMethodsSupported, s as ResourceServerMetadata, t as AuthMethod, u as ClientDiscovery, v as Scope, x as Awaitable, y as StoreTokenType } from "./oauth-Ds-ejTJY.mjs";
2
- import { a as OAuthErrorCode, c as OAuthRedirectOnError, i as OAuthEndpointRedirectContext, n as oauthProvider, o as OAuthFieldErrorCode, r as OAuthEndpointErrorResult, s as OAuthFieldErrorCodeMap, t as getOAuthProviderState } from "./oauth-BxP4Iupj.mjs";
1
+ import { _ as SchemaClient, a as OAuthClient, b as VerificationValue, c as TokenEndpointAuthMethod, d as OAuthAuthorizationQuery, f as OAuthConsent, g as Prompt, h as OAuthRefreshToken, i as GrantType, l as AuthorizePrompt, m as OAuthOptions, n as AuthServerMetadata, o as OIDCMetadata, p as OAuthOpaqueAccessToken, r as BearerMethodsSupported, s as ResourceServerMetadata, t as AuthMethod, u as ClientDiscovery, v as Scope, x as Awaitable, y as StoreTokenType } from "./oauth-BXrYl5x6.mjs";
2
+ import { a as OAuthErrorCode, c as OAuthRedirectOnError, i as OAuthEndpointRedirectContext, n as oauthProvider, o as OAuthFieldErrorCode, r as OAuthEndpointErrorResult, s as OAuthFieldErrorCodeMap, t as getOAuthProviderState } from "./oauth-DU6NeviY.mjs";
3
3
  import { verifyAccessToken } from "better-auth/oauth2";
4
4
  import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
5
5
  import { JWTPayload } from "jose";
@@ -22,6 +22,7 @@ verifyOptions: Parameters<typeof verifyAccessToken>[1], handler: (req: Request,
22
22
  //#region src/metadata.d.ts
23
23
  declare function authServerMetadata(ctx: GenericEndpointContext, opts?: JwtOptions, overrides?: {
24
24
  scopes_supported?: AuthServerMetadata["scopes_supported"];
25
+ dynamic_client_registration_supported?: boolean;
25
26
  public_client_supported?: boolean;
26
27
  grant_types_supported?: GrantType[];
27
28
  jwt_disabled?: boolean;
@@ -39,26 +40,28 @@ declare function oidcServerMetadata(ctx: GenericEndpointContext, opts: OAuthOpti
39
40
  issuer: string;
40
41
  authorization_endpoint: string;
41
42
  token_endpoint: string;
42
- registration_endpoint: string;
43
+ registration_endpoint?: string | undefined;
43
44
  scopes_supported?: string[] | undefined;
44
45
  response_types_supported: "code"[];
45
46
  response_modes_supported: "query"[];
46
47
  grant_types_supported: GrantType[];
47
48
  token_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[] | undefined;
48
- token_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
49
+ token_endpoint_auth_signing_alg_values_supported?: better_auth0.PrivateKeyJwtSigningAlgorithm[] | undefined;
49
50
  service_documentation?: string | undefined;
50
51
  ui_locales_supported?: string[] | undefined;
51
52
  op_policy_uri?: string | undefined;
52
53
  op_tos_uri?: string | undefined;
53
54
  revocation_endpoint?: string | undefined;
54
55
  revocation_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
55
- revocation_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
56
+ revocation_endpoint_auth_signing_alg_values_supported?: better_auth0.PrivateKeyJwtSigningAlgorithm[] | undefined;
56
57
  introspection_endpoint?: string | undefined;
57
58
  introspection_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
58
- introspection_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
59
+ introspection_endpoint_auth_signing_alg_values_supported?: better_auth0.PrivateKeyJwtSigningAlgorithm[] | undefined;
59
60
  code_challenge_methods_supported: "S256"[];
60
61
  authorization_response_iss_parameter_supported?: boolean | undefined;
61
62
  client_id_metadata_document_supported?: boolean | undefined;
63
+ backchannel_logout_supported?: boolean | undefined;
64
+ backchannel_logout_session_supported?: boolean | undefined;
62
65
  id_token_signing_alg_values_supported: JWSAlgorithms[] | ["HS256"];
63
66
  };
64
67
  /**