@better-auth/oauth-provider 1.5.0-beta.8 → 1.5.0

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.
@@ -114,6 +114,10 @@ declare const schema: {
114
114
  type: "string";
115
115
  required: false;
116
116
  };
117
+ requirePKCE: {
118
+ type: "boolean";
119
+ required: false;
120
+ };
117
121
  referenceId: {
118
122
  type: "string";
119
123
  required: false;
@@ -174,6 +178,10 @@ declare const schema: {
174
178
  type: "date";
175
179
  required: false;
176
180
  };
181
+ authTime: {
182
+ type: "date";
183
+ required: false;
184
+ };
177
185
  scopes: {
178
186
  type: "string[]";
179
187
  required: true;
@@ -694,12 +702,10 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
694
702
  * @returns Additional claims for userinfo request
695
703
  */
696
704
  customUserInfoClaims?: (info: {
697
- /** The user object */
698
- user: User & Record<string, unknown>;
705
+ /** The user object */user: User & Record<string, unknown>;
699
706
  /** The scopes from the access token used
700
707
  * in the /userinfo request (matches jwt.scopes) */
701
- scopes: Scopes;
702
- /** The access token payload used in the /userinfo request */
708
+ scopes: Scopes; /** The access token payload used in the /userinfo request */
703
709
  jwt: JWTPayload;
704
710
  }) => Awaitable<Record<string, any>>;
705
711
  /**
@@ -713,11 +719,8 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
713
719
  * @param info - context that may be useful when creating custom claims
714
720
  */
715
721
  customIdTokenClaims?: (info: {
716
- /** The user object if token is associated to a user. */
717
- user: User & Record<string, unknown>;
718
- /** Scopes granted for this token */
719
- scopes: Scopes;
720
- /** oAuthClient metadata */
722
+ /** The user object if token is associated to a user. */user: User & Record<string, unknown>; /** Scopes granted for this token */
723
+ scopes: Scopes; /** oAuthClient metadata */
721
724
  metadata?: Record<string, any>;
722
725
  }) => Awaitable<Record<string, any>>;
723
726
  /**
@@ -734,15 +737,10 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
734
737
  * @param info - context that may be useful when creating custom claims
735
738
  */
736
739
  customAccessTokenClaims?: (info: {
737
- /** The user object if token is associated to a user. Null if user doesn't exist. Undefined if user not applicable. */
738
- user?: (User & Record<string, unknown>) | null;
739
- /** reference of the consent/authorization */
740
- referenceId?: string;
741
- /** Scopes granted for this token */
742
- scopes: Scopes;
743
- /** The resource requesting. Provided by the token endpoint. */
744
- resource?: string;
745
- /** oAuthClient metadata */
740
+ /** The user object if token is associated to a user. Null if user doesn't exist. Undefined if user not applicable. */user?: (User & Record<string, unknown>) | null; /** reference of the consent/authorization */
741
+ referenceId?: string; /** Scopes granted for this token */
742
+ scopes: Scopes; /** The resource requesting. Provided by the token endpoint. */
743
+ resource?: string; /** oAuthClient metadata */
746
744
  metadata?: Record<string, any>;
747
745
  }) => Awaitable<Record<string, any>>;
748
746
  /**
@@ -869,6 +867,74 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
869
867
  * @default false
870
868
  */
871
869
  disableJwtPlugin?: boolean;
870
+ /**
871
+ * Rate limit configuration for OAuth endpoints.
872
+ *
873
+ * Each endpoint can be configured with a `window` (in seconds) and `max` requests.
874
+ * Set to `false` to disable rate limiting for a specific endpoint.
875
+ *
876
+ * @default
877
+ * ```ts
878
+ * {
879
+ * token: { window: 60, max: 20 },
880
+ * authorize: { window: 60, max: 30 },
881
+ * introspect: { window: 60, max: 100 },
882
+ * revoke: { window: 60, max: 30 },
883
+ * register: { window: 60, max: 5 },
884
+ * userinfo: { window: 60, max: 60 },
885
+ * }
886
+ * ```
887
+ */
888
+ rateLimit?: {
889
+ /**
890
+ * Rate limit for /oauth2/token endpoint
891
+ * @default { window: 60, max: 20 }
892
+ */
893
+ token?: {
894
+ window: number;
895
+ max: number;
896
+ } | false;
897
+ /**
898
+ * Rate limit for /oauth2/authorize endpoint
899
+ * @default { window: 60, max: 30 }
900
+ */
901
+ authorize?: {
902
+ window: number;
903
+ max: number;
904
+ } | false;
905
+ /**
906
+ * Rate limit for /oauth2/introspect endpoint
907
+ * @default { window: 60, max: 100 }
908
+ */
909
+ introspect?: {
910
+ window: number;
911
+ max: number;
912
+ } | false;
913
+ /**
914
+ * Rate limit for /oauth2/revoke endpoint
915
+ * @default { window: 60, max: 30 }
916
+ */
917
+ revoke?: {
918
+ window: number;
919
+ max: number;
920
+ } | false;
921
+ /**
922
+ * Rate limit for /oauth2/register endpoint
923
+ * @default { window: 60, max: 5 }
924
+ */
925
+ register?: {
926
+ window: number;
927
+ max: number;
928
+ } | false;
929
+ /**
930
+ * Rate limit for /oauth2/userinfo endpoint
931
+ * @default { window: 60, max: 60 }
932
+ */
933
+ userinfo?: {
934
+ window: number;
935
+ max: number;
936
+ } | false;
937
+ };
872
938
  }
873
939
  interface OAuthAuthorizationQuery {
874
940
  /**
@@ -991,6 +1057,7 @@ interface VerificationValue {
991
1057
  sessionId: string;
992
1058
  userId: string;
993
1059
  referenceId?: string;
1060
+ authTime?: number;
994
1061
  }
995
1062
  /**
996
1063
  * Client registered values as used within the plugin
@@ -1082,6 +1149,15 @@ interface SchemaClient<Scopes extends readonly Scope[] = InternallySupportedScop
1082
1149
  * - user-agent-based - A user-agent-based application (public client)
1083
1150
  */
1084
1151
  type?: "web" | "native" | "user-agent-based";
1152
+ /**
1153
+ * Whether this client requires PKCE for authorization code flow.
1154
+ *
1155
+ * @default true
1156
+ *
1157
+ * Note: PKCE is always required for public clients and when
1158
+ * requesting offline_access scope, regardless of this setting.
1159
+ */
1160
+ requirePKCE?: boolean;
1085
1161
  /** Used to indicate if consent screen can be skipped */
1086
1162
  skipConsent?: boolean;
1087
1163
  /** Used to enable client to logout via the `/oauth2/end-session` endpoint */
@@ -1155,6 +1231,11 @@ interface OAuthRefreshToken<Scopes extends readonly Scope[] = InternallySupporte
1155
1231
  * When token was revoked. If set, token is considered a replay attack.
1156
1232
  */
1157
1233
  revoked?: Date;
1234
+ /**
1235
+ * The time the user originally authenticated.
1236
+ * Persisted so refreshed ID tokens can include a correct `auth_time` claim.
1237
+ */
1238
+ authTime?: Date;
1158
1239
  /**
1159
1240
  * Scopes granted for this refresh token.
1160
1241
  *
@@ -1331,6 +1412,14 @@ interface AuthServerMetadata {
1331
1412
  * @default ["S256"]
1332
1413
  */
1333
1414
  code_challenge_methods_supported: "S256"[];
1415
+ /**
1416
+ * Boolean value specifying whether the authorization server provides
1417
+ * the iss parameter in the authorization response (RFC 9207)
1418
+ *
1419
+ * @see https://datatracker.ietf.org/doc/html/rfc9207
1420
+ * @default true
1421
+ */
1422
+ authorization_response_iss_parameter_supported?: boolean;
1334
1423
  }
1335
1424
  /**
1336
1425
  * Metadata returned by the openid-configuration endpoint:
@@ -1438,6 +1527,15 @@ interface OAuthClient {
1438
1527
  disabled?: boolean;
1439
1528
  skip_consent?: boolean;
1440
1529
  enable_end_session?: boolean;
1530
+ /**
1531
+ * Whether this client requires PKCE for authorization code flow.
1532
+ *
1533
+ * @default true
1534
+ *
1535
+ * Note: PKCE is always required for public clients and when
1536
+ * requesting offline_access scope, regardless of this setting.
1537
+ */
1538
+ require_pkce?: boolean;
1441
1539
  reference_id?: string;
1442
1540
  [key: string]: unknown;
1443
1541
  }
@@ -1479,4 +1577,5 @@ interface ResourceServerMetadata {
1479
1577
  dpop_bound_access_tokens_required?: boolean;
1480
1578
  }
1481
1579
  //#endregion
1482
- export { Awaitable as _, ResourceServerMetadata as a, OAuthConsent as c, OAuthRefreshToken as d, Prompt as f, VerificationValue as g, StoreTokenType as h, OIDCMetadata as i, OAuthOpaqueAccessToken as l, Scope as m, GrantType as n, AuthorizePrompt as o, SchemaClient as p, OAuthClient as r, OAuthAuthorizationQuery as s, AuthServerMetadata as t, OAuthOptions as u };
1580
+ export { Awaitable as _, ResourceServerMetadata as a, OAuthConsent as c, OAuthRefreshToken as d, Prompt as f, VerificationValue as g, StoreTokenType as h, OIDCMetadata as i, OAuthOpaqueAccessToken as l, Scope as m, GrantType as n, AuthorizePrompt as o, SchemaClient as p, OAuthClient as r, OAuthAuthorizationQuery as s, AuthServerMetadata as t, OAuthOptions as u };
1581
+ //# sourceMappingURL=oauth-7Jc-EFsq.d.mts.map
@@ -1,19 +1,22 @@
1
- import { c as OAuthConsent, i as OIDCMetadata, m as Scope, r as OAuthClient, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-BrFoF22H.mjs";
1
+ import { c as OAuthConsent, i as OIDCMetadata, m as Scope, r as OAuthClient, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-7Jc-EFsq.mjs";
2
2
  import * as better_call0 from "better-call";
3
3
  import "@better-auth/core/context";
4
4
  import * as z from "zod";
5
5
  import * as better_auth_plugins0 from "better-auth/plugins";
6
- import * as jose0 from "jose";
6
+ import * as jose from "jose";
7
7
  import * as better_auth0 from "better-auth";
8
8
 
9
9
  //#region src/oauth.d.ts
10
10
  declare module "@better-auth/core" {
11
- interface BetterAuthPluginRegistry<Auth, Context> {
11
+ interface BetterAuthPluginRegistry<AuthOptions, Options> {
12
12
  "oauth-provider": {
13
13
  creator: typeof oauthProvider;
14
14
  };
15
15
  }
16
16
  }
17
+ declare const getOAuthProviderState: () => Promise<{
18
+ query?: string;
19
+ } | null>;
17
20
  /**
18
21
  * oAuth 2.1 provider plugin for Better Auth.
19
22
  *
@@ -83,6 +86,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
83
86
  }>>;
84
87
  nonce: z.ZodOptional<z.ZodString>;
85
88
  prompt: z.ZodOptional<z.ZodEnum<{
89
+ none: "none";
86
90
  consent: "consent";
87
91
  login: "login";
88
92
  create: "create";
@@ -221,7 +225,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
221
225
  };
222
226
  }, {
223
227
  redirect: boolean;
224
- uri: string;
228
+ url: string;
225
229
  }>;
226
230
  oauth2Continue: better_call0.StrictEndpoint<"/oauth2/continue", {
227
231
  method: "POST";
@@ -281,7 +285,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
281
285
  };
282
286
  }, {
283
287
  redirect: boolean;
284
- uri: string;
288
+ url: string;
285
289
  }>;
286
290
  oauth2Token: better_call0.StrictEndpoint<"/oauth2/token", {
287
291
  method: "POST";
@@ -562,7 +566,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
562
566
  };
563
567
  };
564
568
  };
565
- }, jose0.JWTPayload>;
569
+ }, jose.JWTPayload>;
566
570
  oauth2Revoke: better_call0.StrictEndpoint<"/oauth2/revoke", {
567
571
  method: "POST";
568
572
  body: z.ZodObject<{
@@ -823,9 +827,9 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
823
827
  software_statement: z.ZodOptional<z.ZodString>;
824
828
  post_logout_redirect_uris: z.ZodOptional<z.ZodArray<z.ZodURL>>;
825
829
  token_endpoint_auth_method: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
830
+ none: "none";
826
831
  client_secret_basic: "client_secret_basic";
827
832
  client_secret_post: "client_secret_post";
828
- none: "none";
829
833
  }>>>;
830
834
  grant_types: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
831
835
  authorization_code: "authorization_code";
@@ -850,8 +854,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
850
854
  content: {
851
855
  "application/json": {
852
856
  schema: {
853
- /** @returns {OauthClient} */
854
- type: "object";
857
+ /** @returns {OauthClient} */type: "object";
855
858
  properties: {
856
859
  client_id: {
857
860
  type: string;
@@ -992,9 +995,9 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
992
995
  software_statement: z.ZodOptional<z.ZodString>;
993
996
  post_logout_redirect_uris: z.ZodOptional<z.ZodArray<z.ZodURL>>;
994
997
  token_endpoint_auth_method: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
998
+ none: "none";
995
999
  client_secret_basic: "client_secret_basic";
996
1000
  client_secret_post: "client_secret_post";
997
- none: "none";
998
1001
  }>>>;
999
1002
  grant_types: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
1000
1003
  authorization_code: "authorization_code";
@@ -1012,6 +1015,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1012
1015
  client_secret_expires_at: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
1013
1016
  skip_consent: z.ZodOptional<z.ZodBoolean>;
1014
1017
  enable_end_session: z.ZodOptional<z.ZodBoolean>;
1018
+ require_pkce: z.ZodOptional<z.ZodBoolean>;
1015
1019
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1016
1020
  }, z.core.$strip>;
1017
1021
  metadata: {
@@ -1131,6 +1135,11 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1131
1135
  type: string;
1132
1136
  description: string;
1133
1137
  };
1138
+ require_pkce: {
1139
+ type: string;
1140
+ description: string;
1141
+ default: boolean;
1142
+ };
1134
1143
  metadata: {
1135
1144
  type: string;
1136
1145
  additionalProperties: boolean;
@@ -1186,9 +1195,9 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1186
1195
  software_statement: z.ZodOptional<z.ZodString>;
1187
1196
  post_logout_redirect_uris: z.ZodOptional<z.ZodArray<z.ZodURL>>;
1188
1197
  token_endpoint_auth_method: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
1198
+ none: "none";
1189
1199
  client_secret_basic: "client_secret_basic";
1190
1200
  client_secret_post: "client_secret_post";
1191
- none: "none";
1192
1201
  }>>>;
1193
1202
  grant_types: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
1194
1203
  authorization_code: "authorization_code";
@@ -1854,6 +1863,10 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1854
1863
  type: "string";
1855
1864
  required: false;
1856
1865
  };
1866
+ requirePKCE: {
1867
+ type: "boolean";
1868
+ required: false;
1869
+ };
1857
1870
  referenceId: {
1858
1871
  type: "string";
1859
1872
  required: false;
@@ -1909,6 +1922,10 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
1909
1922
  type: "date";
1910
1923
  required: false;
1911
1924
  };
1925
+ authTime: {
1926
+ type: "date";
1927
+ required: false;
1928
+ };
1912
1929
  scopes: {
1913
1930
  type: "string[]";
1914
1931
  required: true;
@@ -2007,6 +2024,32 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
2007
2024
  };
2008
2025
  };
2009
2026
  };
2027
+ rateLimit: ({
2028
+ pathMatcher: (path: string) => path is "/oauth2/token";
2029
+ window: number;
2030
+ max: number;
2031
+ } | {
2032
+ pathMatcher: (path: string) => path is "/oauth2/authorize";
2033
+ window: number;
2034
+ max: number;
2035
+ } | {
2036
+ pathMatcher: (path: string) => path is "/oauth2/introspect";
2037
+ window: number;
2038
+ max: number;
2039
+ } | {
2040
+ pathMatcher: (path: string) => path is "/oauth2/revoke";
2041
+ window: number;
2042
+ max: number;
2043
+ } | {
2044
+ pathMatcher: (path: string) => path is "/oauth2/register";
2045
+ window: number;
2046
+ max: number;
2047
+ } | {
2048
+ pathMatcher: (path: string) => path is "/oauth2/userinfo";
2049
+ window: number;
2050
+ max: number;
2051
+ })[];
2010
2052
  };
2011
2053
  //#endregion
2012
- export { oauthProvider as t };
2054
+ export { oauthProvider as n, getOAuthProviderState as t };
2055
+ //# sourceMappingURL=oauth-C_QoLKZA.d.mts.map
@@ -1,9 +1,10 @@
1
+ import { isAPIError } from "better-auth/api";
1
2
  import { verifyAccessToken } from "better-auth/oauth2";
2
- import { APIError } from "better-call";
3
+ import { APIError as APIError$1 } from "better-call";
4
+ import { constantTimeEqual, symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
3
5
  import { BetterAuthError } from "@better-auth/core/error";
4
6
  import { base64, base64Url } from "@better-auth/utils/base64";
5
7
  import { createHash } from "@better-auth/utils/hash";
6
- import { constantTimeEqual, symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
7
8
 
8
9
  //#region src/mcp.ts
9
10
  /**
@@ -17,13 +18,13 @@ const mcpHandler = (verifyOptions, handler, opts) => {
17
18
  const authorization = req.headers?.get("authorization") ?? void 0;
18
19
  const accessToken = authorization?.startsWith("Bearer ") ? authorization.replace("Bearer ", "") : authorization;
19
20
  try {
20
- if (!accessToken?.length) throw new APIError("UNAUTHORIZED", { message: "missing authorization header" });
21
+ if (!accessToken?.length) throw new APIError$1("UNAUTHORIZED", { message: "missing authorization header" });
21
22
  return handler(req, await verifyAccessToken(accessToken, verifyOptions));
22
23
  } catch (error) {
23
24
  try {
24
25
  handleMcpErrors(error, verifyOptions.verifyOptions.audience, opts);
25
26
  } catch (err) {
26
- if (err instanceof APIError) return new Response(err.message, {
27
+ if (err instanceof APIError$1) return new Response(err.message, {
27
28
  ...err,
28
29
  status: err.statusCode
29
30
  });
@@ -39,7 +40,7 @@ const mcpHandler = (verifyOptions, handler, opts) => {
39
40
  * @internal
40
41
  */
41
42
  function handleMcpErrors(error, resource, opts) {
42
- if (error instanceof APIError && error.status === "UNAUTHORIZED") {
43
+ if (isAPIError(error) && error.status === "UNAUTHORIZED") {
43
44
  const wwwAuthenticateValue = (Array.isArray(resource) ? resource : [resource]).map((v) => {
44
45
  let audiencePath;
45
46
  if (URL.canParse?.(v)) {
@@ -48,11 +49,11 @@ function handleMcpErrors(error, resource, opts) {
48
49
  return `Bearer resource_metadata="${url.origin}/.well-known/oauth-protected-resource${audiencePath}"`;
49
50
  } else {
50
51
  const resourceMetadata = opts?.resourceMetadataMappings?.[v];
51
- if (!resourceMetadata) throw new APIError("INTERNAL_SERVER_ERROR", { message: `missing resource_metadata mapping for ${v}` });
52
+ if (!resourceMetadata) throw new APIError$1("INTERNAL_SERVER_ERROR", { message: `missing resource_metadata mapping for ${v}` });
52
53
  return `Bearer resource_metadata=${resourceMetadata}`;
53
54
  }
54
55
  }).join(", ");
55
- throw new APIError("UNAUTHORIZED", { message: error.message }, { "WWW-Authenticate": wwwAuthenticateValue });
56
+ throw new APIError$1("UNAUTHORIZED", { message: error.message }, { "WWW-Authenticate": wwwAuthenticateValue });
56
57
  } else if (error instanceof Error) throw error;
57
58
  else throw new Error(error);
58
59
  }
@@ -124,7 +125,7 @@ const defaultHasher = async (value) => {
124
125
  */
125
126
  async function decryptStoredClientSecret(ctx, storageMethod, storedClientSecret) {
126
127
  if (storageMethod === "encrypted") return await symmetricDecrypt({
127
- key: ctx.context.secret,
128
+ key: ctx.context.secretConfig,
128
129
  data: storedClientSecret
129
130
  });
130
131
  else if (typeof storageMethod === "object" && "decrypt" in storageMethod) return await storageMethod.decrypt(storedClientSecret);
@@ -138,7 +139,7 @@ async function decryptStoredClientSecret(ctx, storageMethod, storedClientSecret)
138
139
  async function verifyStoredClientSecret(ctx, opts, storedClientSecret, clientSecret) {
139
140
  const storageMethod = opts.storeClientSecret ?? (opts.disableJwtPlugin ? "encrypted" : "hashed");
140
141
  if (clientSecret && opts.prefix?.clientSecret) if (clientSecret.startsWith(opts.prefix?.clientSecret)) clientSecret = clientSecret.replace(opts.prefix.clientSecret, "");
141
- else throw new APIError("UNAUTHORIZED", {
142
+ else throw new APIError$1("UNAUTHORIZED", {
142
143
  error_description: "invalid client_secret",
143
144
  error: "invalid_client"
144
145
  });
@@ -150,7 +151,13 @@ async function verifyStoredClientSecret(ctx, opts, storedClientSecret, clientSec
150
151
  const hashedClientSecret = clientSecret ? await storageMethod.hash(clientSecret) : void 0;
151
152
  return !!hashedClientSecret && constantTimeEqual(hashedClientSecret, storedClientSecret);
152
153
  }
153
- else if (storageMethod === "encrypted" || typeof storageMethod === "object" && "decrypt" in storageMethod) {
154
+ else if (storageMethod === "encrypted") try {
155
+ const decryptedClientSecret = await decryptStoredClientSecret(ctx, storageMethod, storedClientSecret);
156
+ return !!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret);
157
+ } catch {
158
+ return false;
159
+ }
160
+ else if (typeof storageMethod === "object" && "decrypt" in storageMethod) {
154
161
  const decryptedClientSecret = await decryptStoredClientSecret(ctx, storageMethod, storedClientSecret);
155
162
  return !!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret);
156
163
  }
@@ -164,7 +171,7 @@ async function verifyStoredClientSecret(ctx, opts, storedClientSecret, clientSec
164
171
  async function storeClientSecret(ctx, opts, clientSecret) {
165
172
  const storageMethod = opts.storeClientSecret ?? (opts.disableJwtPlugin ? "encrypted" : "hashed");
166
173
  if (storageMethod === "encrypted") return await symmetricEncrypt({
167
- key: ctx.context.secret,
174
+ key: ctx.context.secretConfig,
168
175
  data: clientSecret
169
176
  });
170
177
  else if (storageMethod === "hashed") return await defaultHasher(clientSecret);
@@ -203,12 +210,12 @@ function basicToClientCredentials(authorization) {
203
210
  if (authorization.startsWith("Basic ")) {
204
211
  const encoded = authorization.replace("Basic ", "");
205
212
  const decoded = new TextDecoder().decode(base64.decode(encoded));
206
- if (!decoded.includes(":")) throw new APIError("BAD_REQUEST", {
213
+ if (!decoded.includes(":")) throw new APIError$1("BAD_REQUEST", {
207
214
  error_description: "invalid authorization header format",
208
215
  error: "invalid_client"
209
216
  });
210
217
  const [id, secret] = decoded.split(":", 2);
211
- if (!id || !secret) throw new APIError("BAD_REQUEST", {
218
+ if (!id || !secret) throw new APIError$1("BAD_REQUEST", {
212
219
  error_description: "invalid authorization header format",
213
220
  error: "invalid_client"
214
221
  });
@@ -226,29 +233,29 @@ function basicToClientCredentials(authorization) {
226
233
  */
227
234
  async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes) {
228
235
  const client = await getClient(ctx, options, clientId);
229
- if (!client) throw new APIError("BAD_REQUEST", {
236
+ if (!client) throw new APIError$1("BAD_REQUEST", {
230
237
  error_description: "missing client",
231
238
  error: "invalid_client"
232
239
  });
233
- if (client.disabled) throw new APIError("BAD_REQUEST", {
240
+ if (client.disabled) throw new APIError$1("BAD_REQUEST", {
234
241
  error_description: "client is disabled",
235
242
  error: "invalid_client"
236
243
  });
237
- if (!client.public && !clientSecret) throw new APIError("BAD_REQUEST", {
244
+ if (!client.public && !clientSecret) throw new APIError$1("BAD_REQUEST", {
238
245
  error_description: "client secret must be provided",
239
246
  error: "invalid_client"
240
247
  });
241
- if (clientSecret && !client.clientSecret) throw new APIError("BAD_REQUEST", {
248
+ if (clientSecret && !client.clientSecret) throw new APIError$1("BAD_REQUEST", {
242
249
  error_description: "public client, client secret should not be received",
243
250
  error: "invalid_client"
244
251
  });
245
- if (clientSecret && !await verifyStoredClientSecret(ctx, options, client.clientSecret, clientSecret)) throw new APIError("UNAUTHORIZED", {
252
+ if (clientSecret && !await verifyStoredClientSecret(ctx, options, client.clientSecret, clientSecret)) throw new APIError$1("UNAUTHORIZED", {
246
253
  error_description: "invalid client_secret",
247
254
  error: "invalid_client"
248
255
  });
249
256
  if (scopes && client.scopes) {
250
257
  const validScopes = new Set(client.scopes);
251
- for (const sc of scopes) if (!validScopes.has(sc)) throw new APIError("BAD_REQUEST", {
258
+ for (const sc of scopes) if (!validScopes.has(sc)) throw new APIError$1("BAD_REQUEST", {
252
259
  error_description: `client does not allow scope ${sc}`,
253
260
  error: "invalid_scope"
254
261
  });
@@ -291,6 +298,33 @@ function deleteFromPrompt(query, prompt) {
291
298
  }
292
299
  return Object.fromEntries(query);
293
300
  }
301
+ var PKCERequirementErrors = /* @__PURE__ */ function(PKCERequirementErrors) {
302
+ PKCERequirementErrors["PUBLIC_CLIENT"] = "pkce is required for public clients";
303
+ PKCERequirementErrors["OFFLINE_ACCESS_SCOPE"] = "pkce is required when requesting offline_access scope";
304
+ PKCERequirementErrors["CLIENT_REQUIRE_PKCE"] = "pkce is required for this client";
305
+ return PKCERequirementErrors;
306
+ }(PKCERequirementErrors || {});
307
+ /**
308
+ * Determines if PKCE is required for a given client and scope.
309
+ *
310
+ * PKCE is always required for:
311
+ * 1. Public clients (cannot securely store client_secret)
312
+ * 2. Requests with offline_access scope (refresh token security)
313
+ *
314
+ * For confidential clients without offline_access:
315
+ * - Uses client.requirePKCE if set (defaults to true)
316
+ *
317
+ * Returns false if PKCE is not required, or the reason it is required.
318
+ *
319
+ * @internal
320
+ */
321
+ function isPKCERequired(client, requestedScopes) {
322
+ if (client.tokenEndpointAuthMethod === "none" || client.type === "native" || client.type === "user-agent-based" || client.public === true) return PKCERequirementErrors.PUBLIC_CLIENT;
323
+ if (requestedScopes?.includes("offline_access")) return PKCERequirementErrors.OFFLINE_ACCESS_SCOPE;
324
+ if (client.requirePKCE ?? true) return PKCERequirementErrors.CLIENT_REQUIRE_PKCE;
325
+ return false;
326
+ }
294
327
 
295
328
  //#endregion
296
- export { getJwtPlugin as a, parseClientMetadata as c, storeToken as d, validateClientCredentials as f, getClient as i, parsePrompt as l, mcpHandler as m, decryptStoredClientSecret as n, getOAuthProviderPlugin as o, handleMcpErrors as p, deleteFromPrompt as r, getStoredToken as s, basicToClientCredentials as t, storeClientSecret as u };
329
+ export { getJwtPlugin as a, isPKCERequired as c, storeClientSecret as d, storeToken as f, mcpHandler as h, getClient as i, parseClientMetadata as l, handleMcpErrors as m, decryptStoredClientSecret as n, getOAuthProviderPlugin as o, validateClientCredentials as p, deleteFromPrompt as r, getStoredToken as s, basicToClientCredentials as t, parsePrompt as u };
330
+ //# sourceMappingURL=utils-D6kv_BUA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-D6kv_BUA.mjs","names":["APIError","APIError"],"sources":["../src/mcp.ts","../src/utils/index.ts"],"sourcesContent":["import { isAPIError } from \"better-auth/api\";\nimport { verifyAccessToken } from \"better-auth/oauth2\";\nimport { APIError } from \"better-call\";\nimport type { JWTPayload } from \"jose\";\nimport type { Awaitable } from \"./types/helpers\";\n\n/**\n * A request middleware handler that checks and responds with\n * a WWW-Authenticate header for unauthenticated responses.\n *\n * @external\n */\nexport const mcpHandler = (\n\t/** Resource is the same url as the audience */\n\tverifyOptions: Parameters<typeof verifyAccessToken>[1],\n\thandler: (req: Request, jwt: JWTPayload) => Awaitable<Response>,\n\topts?: {\n\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\tresourceMetadataMappings: Record<string, string>;\n\t},\n) => {\n\treturn async (req: Request) => {\n\t\tconst authorization = req.headers?.get(\"authorization\") ?? undefined;\n\t\tconst accessToken = authorization?.startsWith(\"Bearer \")\n\t\t\t? authorization.replace(\"Bearer \", \"\")\n\t\t\t: authorization;\n\t\ttry {\n\t\t\tif (!accessToken?.length) {\n\t\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\t\tmessage: \"missing authorization header\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst token = await verifyAccessToken(accessToken, verifyOptions);\n\t\t\treturn handler(req, token);\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\thandleMcpErrors(error, verifyOptions.verifyOptions.audience, opts);\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof APIError) {\n\t\t\t\t\treturn new Response(err.message, {\n\t\t\t\t\t\t...err,\n\t\t\t\t\t\tstatus: err.statusCode,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthrow new Error(String(err));\n\t\t\t}\n\t\t\tthrow new Error(String(error));\n\t\t}\n\t};\n};\n\n/**\n * The following handles all MCP errors and API errors\n *\n * @internal\n */\nexport function handleMcpErrors(\n\terror: unknown,\n\tresource: string | string[],\n\topts?: {\n\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\tresourceMetadataMappings?: Record<string, string>;\n\t},\n) {\n\tif (isAPIError(error) && error.status === \"UNAUTHORIZED\") {\n\t\tconst _resources = Array.isArray(resource) ? resource : [resource];\n\t\tconst wwwAuthenticateValue = _resources\n\t\t\t.map((v) => {\n\t\t\t\tlet audiencePath: string;\n\t\t\t\tif (URL.canParse?.(v)) {\n\t\t\t\t\tconst url = new URL(v);\n\t\t\t\t\taudiencePath = url.pathname.endsWith(\"/\")\n\t\t\t\t\t\t? url.pathname.slice(0, -1)\n\t\t\t\t\t\t: url.pathname;\n\t\t\t\t\treturn `Bearer resource_metadata=\"${url.origin}/.well-known/oauth-protected-resource${\n\t\t\t\t\t\taudiencePath\n\t\t\t\t\t}\"`;\n\t\t\t\t} else {\n\t\t\t\t\tconst resourceMetadata = opts?.resourceMetadataMappings?.[v];\n\t\t\t\t\tif (!resourceMetadata) {\n\t\t\t\t\t\tthrow new APIError(\"INTERNAL_SERVER_ERROR\", {\n\t\t\t\t\t\t\tmessage: `missing resource_metadata mapping for ${v}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn `Bearer resource_metadata=${resourceMetadata}`;\n\t\t\t\t}\n\t\t\t})\n\t\t\t.join(\", \");\n\t\tthrow new APIError(\n\t\t\t\"UNAUTHORIZED\",\n\t\t\t{\n\t\t\t\tmessage: error.message,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"WWW-Authenticate\": wwwAuthenticateValue,\n\t\t\t},\n\t\t);\n\t} else if (error instanceof Error) {\n\t\tthrow error;\n\t} else {\n\t\tthrow new Error(error as unknown as string);\n\t}\n}\n","import type { AuthContext, GenericEndpointContext } from \"@better-auth/core\";\nimport { BetterAuthError } from \"@better-auth/core/error\";\nimport { base64, base64Url } from \"@better-auth/utils/base64\";\nimport { createHash } from \"@better-auth/utils/hash\";\nimport {\n\tconstantTimeEqual,\n\tsymmetricDecrypt,\n\tsymmetricEncrypt,\n} from \"better-auth/crypto\";\nimport type { jwt } from \"better-auth/plugins\";\nimport { APIError } from \"better-call\";\nimport type { oauthProvider } from \"../oauth\";\nimport type {\n\tOAuthOptions,\n\tPrompt,\n\tSchemaClient,\n\tScope,\n\tStoreTokenType,\n} from \"../types\";\n\nclass TTLCache<K, V extends { expiresAt?: Date }> {\n\tprivate cache = new Map<K, V>();\n\tconstructor() {}\n\n\tset(key: K, value: V) {\n\t\tthis.cache.set(key, value);\n\t}\n\n\tget(key: K): V | undefined {\n\t\tconst entry = this.cache.get(key);\n\t\tif (!entry) return undefined;\n\t\tif (entry.expiresAt && entry.expiresAt < new Date()) {\n\t\t\tthis.cache.delete(key);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn entry;\n\t}\n}\n\n/**\n * Gets the oAuth Provider Plugin\n * @internal\n */\nexport const getOAuthProviderPlugin = (ctx: AuthContext) => {\n\treturn ctx.getPlugin(\"oauth-provider\") satisfies ReturnType<\n\t\ttypeof oauthProvider\n\t> | null;\n};\n\n/**\n * Gets the JWT Plugin\n * @internal\n */\nexport const getJwtPlugin = (ctx: AuthContext) => {\n\tconst plugin = ctx.getPlugin(\"jwt\") satisfies ReturnType<typeof jwt> | null;\n\tif (!plugin) {\n\t\tthrow new BetterAuthError(\"jwt_config\", \"jwt plugin not found\");\n\t}\n\treturn plugin;\n};\n\nconst cachedTrustedClients = new TTLCache<string, SchemaClient<Scope[]>>();\n\n/**\n * Get a client by ID, checking trusted clients first, then database\n */\nexport async function getClient(\n\tctx: GenericEndpointContext,\n\toptions: OAuthOptions<Scope[]>,\n\tclientId: string,\n) {\n\tconst trustedClient = cachedTrustedClients.get(clientId);\n\tif (trustedClient) {\n\t\treturn Object.assign({}, trustedClient);\n\t}\n\n\tconst dbClient = await ctx.context.adapter.findOne<SchemaClient<Scope[]>>({\n\t\tmodel: options.schema?.oauthClient?.modelName ?? \"oauthClient\",\n\t\twhere: [{ field: \"clientId\", value: clientId }],\n\t});\n\n\tif (dbClient && options.cachedTrustedClients?.has(clientId)) {\n\t\tcachedTrustedClients.set(clientId, Object.assign({}, dbClient));\n\t}\n\n\treturn dbClient;\n}\n\n/**\n * Default client secret hasher using SHA-256\n *\n * @internal\n */\nconst defaultHasher = async (value: string) => {\n\tconst hash = await createHash(\"SHA-256\").digest(\n\t\tnew TextEncoder().encode(value),\n\t);\n\tconst hashed = base64Url.encode(new Uint8Array(hash), {\n\t\tpadding: false,\n\t});\n\treturn hashed;\n};\n\n/**\n * Decrypts a storedClientSecret for signing\n *\n * @internal\n */\nexport async function decryptStoredClientSecret(\n\tctx: GenericEndpointContext,\n\tstorageMethod: OAuthOptions<Scope[]>[\"storeClientSecret\"],\n\tstoredClientSecret: string,\n) {\n\tif (storageMethod === \"encrypted\") {\n\t\treturn await symmetricDecrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: storedClientSecret,\n\t\t});\n\t} else if (typeof storageMethod === \"object\" && \"decrypt\" in storageMethod) {\n\t\treturn await storageMethod.decrypt(storedClientSecret);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`Unsupported decryption storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Verify stored client secret against provided client secret\n *\n * @internal\n */\nasync function verifyStoredClientSecret(\n\tctx: GenericEndpointContext,\n\topts: OAuthOptions<Scope[]>,\n\tstoredClientSecret: string,\n\tclientSecret?: string,\n): Promise<boolean> {\n\tconst storageMethod =\n\t\topts.storeClientSecret ?? (opts.disableJwtPlugin ? \"encrypted\" : \"hashed\");\n\n\tif (clientSecret && opts.prefix?.clientSecret) {\n\t\tif (clientSecret.startsWith(opts.prefix?.clientSecret)) {\n\t\t\tclientSecret = clientSecret.replace(opts.prefix.clientSecret, \"\");\n\t\t} else {\n\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\terror_description: \"invalid client_secret\",\n\t\t\t\terror: \"invalid_client\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (storageMethod === \"hashed\") {\n\t\tconst hashedClientSecret = clientSecret\n\t\t\t? await defaultHasher(clientSecret)\n\t\t\t: undefined;\n\t\treturn (\n\t\t\t!!hashedClientSecret &&\n\t\t\tconstantTimeEqual(hashedClientSecret, storedClientSecret)\n\t\t);\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\tif (storageMethod.verify) {\n\t\t\treturn (\n\t\t\t\t!!clientSecret &&\n\t\t\t\t(await storageMethod.verify(clientSecret, storedClientSecret))\n\t\t\t);\n\t\t} else {\n\t\t\tconst hashedClientSecret = clientSecret\n\t\t\t\t? await storageMethod.hash(clientSecret)\n\t\t\t\t: undefined;\n\t\t\treturn (\n\t\t\t\t!!hashedClientSecret &&\n\t\t\t\tconstantTimeEqual(hashedClientSecret, storedClientSecret)\n\t\t\t);\n\t\t}\n\t} else if (storageMethod === \"encrypted\") {\n\t\ttry {\n\t\t\tconst decryptedClientSecret = await decryptStoredClientSecret(\n\t\t\t\tctx,\n\t\t\t\tstorageMethod,\n\t\t\t\tstoredClientSecret,\n\t\t\t);\n\t\t\treturn (\n\t\t\t\t!!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret)\n\t\t\t);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t} else if (typeof storageMethod === \"object\" && \"decrypt\" in storageMethod) {\n\t\tconst decryptedClientSecret = await decryptStoredClientSecret(\n\t\t\tctx,\n\t\t\tstorageMethod,\n\t\t\tstoredClientSecret,\n\t\t);\n\t\treturn (\n\t\t\t!!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret)\n\t\t);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`Unsupported verify storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Store client secret according to the configured storage method\n *\n * @internal\n */\nexport async function storeClientSecret(\n\tctx: GenericEndpointContext,\n\topts: OAuthOptions<Scope[]>,\n\tclientSecret: string,\n) {\n\tconst storageMethod =\n\t\topts.storeClientSecret ?? (opts.disableJwtPlugin ? \"encrypted\" : \"hashed\");\n\n\tif (storageMethod === \"encrypted\") {\n\t\treturn await symmetricEncrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: clientSecret,\n\t\t});\n\t} else if (storageMethod === \"hashed\") {\n\t\treturn await defaultHasher(clientSecret);\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\treturn await storageMethod.hash(clientSecret);\n\t} else if (typeof storageMethod === \"object\" && \"encrypt\" in storageMethod) {\n\t\treturn await storageMethod.encrypt(clientSecret);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`Unsupported storeClientSecret type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Stores a token value (ie opaque tokens, refresh tokens, transaction tokens, verification codes)\n * on the database in a secure hashed format.\n *\n * @internal\n */\nexport async function storeToken(\n\tstorageMethod: OAuthOptions<Scope[]>[\"storeTokens\"] = \"hashed\",\n\ttoken: string,\n\ttype: StoreTokenType,\n) {\n\tif (storageMethod === \"hashed\") {\n\t\treturn await defaultHasher(token);\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\treturn await storageMethod.hash(token, type);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`storeToken: unsupported storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Gets a hashed token value to find on the database.\n *\n * @internal\n */\nexport async function getStoredToken(\n\tstorageMethod: OAuthOptions<Scope[]>[\"storeTokens\"] = \"hashed\",\n\ttoken: string,\n\ttype: StoreTokenType,\n) {\n\tif (storageMethod === \"hashed\") {\n\t\tconst hashedToken = await defaultHasher(token);\n\t\treturn hashedToken;\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\tconst hashedToken = await storageMethod.hash(token, type);\n\t\treturn hashedToken;\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`getStoredToken: unsupported storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Converts a BASIC authorization header\n * into its client_id and client_secret representation\n *\n * @internal\n */\nexport function basicToClientCredentials(authorization: string) {\n\tif (authorization.startsWith(\"Basic \")) {\n\t\tconst encoded = authorization.replace(\"Basic \", \"\");\n\t\tconst decoded = new TextDecoder().decode(base64.decode(encoded));\n\t\tif (!decoded.includes(\":\")) {\n\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\terror_description: \"invalid authorization header format\",\n\t\t\t\terror: \"invalid_client\",\n\t\t\t});\n\t\t}\n\t\tconst [id, secret] = decoded.split(\":\", 2);\n\t\tif (!id || !secret) {\n\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\terror_description: \"invalid authorization header format\",\n\t\t\t\terror: \"invalid_client\",\n\t\t\t});\n\t\t}\n\t\treturn {\n\t\t\tclient_id: id,\n\t\t\tclient_secret: secret,\n\t\t};\n\t}\n}\n\n/**\n * Validates client credentials failing on mismatches\n * and incorrectly provided information\n *\n * @internal\n */\nexport async function validateClientCredentials(\n\tctx: GenericEndpointContext,\n\toptions: OAuthOptions<Scope[]>,\n\tclientId: string,\n\tclientSecret?: string, // optional because required if client is confidential or this value is defined\n\tscopes?: string[], // checks requested scopes against allowed scopes\n) {\n\tconst client = await getClient(ctx, options, clientId);\n\tif (!client) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"missing client\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\tif (client.disabled) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"client is disabled\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// Require secret for confidential clients\n\tif (!client.public && !clientSecret) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"client secret must be provided\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// Secret should not be received\n\tif (clientSecret && !client.clientSecret) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"public client, client secret should not be received\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// Compare Secrets when secret is provided\n\tif (\n\t\tclientSecret &&\n\t\t!(await verifyStoredClientSecret(\n\t\t\tctx,\n\t\t\toptions,\n\t\t\tclient.clientSecret!,\n\t\t\tclientSecret,\n\t\t))\n\t) {\n\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\terror_description: \"invalid client_secret\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// If scopes set, check against client allowed scopes\n\tif (scopes && client.scopes) {\n\t\tconst validScopes = new Set(client.scopes);\n\t\tfor (const sc of scopes) {\n\t\t\tif (!validScopes.has(sc)) {\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\terror_description: `client does not allow scope ${sc}`,\n\t\t\t\t\terror: \"invalid_scope\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn client;\n}\n\n/**\n * Parse client metadata that may be stored as JSON string or already parsed object.\n * Handles database adapters that auto-parse JSON columns.\n *\n * @internal\n */\nexport function parseClientMetadata(\n\tmetadata: string | object | undefined,\n): object | undefined {\n\tif (!metadata) return undefined;\n\treturn typeof metadata === \"string\" ? JSON.parse(metadata) : metadata;\n}\n\n/**\n * Parse space-separated prompt string into a set of prompts\n *\n * @param prompt\n */\nexport function parsePrompt(prompt: string) {\n\tconst prompts = prompt.split(\" \").map((p) => p.trim());\n\tconst set = new Set<Prompt>();\n\tfor (const p of prompts) {\n\t\tif (\n\t\t\tp === \"login\" ||\n\t\t\tp === \"consent\" ||\n\t\t\tp === \"create\" ||\n\t\t\tp === \"select_account\" ||\n\t\t\tp === \"none\"\n\t\t) {\n\t\t\tset.add(p);\n\t\t}\n\t}\n\treturn new Set(set);\n}\n\n/**\n * Deletes a prompt value\n *\n * @param ctx\n * @param prompt - the prompt value to delete\n */\nexport function deleteFromPrompt(query: URLSearchParams, prompt: Prompt) {\n\tconst prompts = query.get(\"prompt\")?.split(\" \");\n\tconst foundPrompt = prompts?.findIndex((v) => v === prompt) ?? -1;\n\tif (foundPrompt >= 0) {\n\t\tprompts?.splice(foundPrompt, 1);\n\t\tprompts?.length\n\t\t\t? query.set(\"prompt\", prompts.join(\" \"))\n\t\t\t: query.delete(\"prompt\");\n\t}\n\treturn Object.fromEntries(query);\n}\n\nenum PKCERequirementErrors {\n\tPUBLIC_CLIENT = \"pkce is required for public clients\",\n\tOFFLINE_ACCESS_SCOPE = \"pkce is required when requesting offline_access scope\",\n\tCLIENT_REQUIRE_PKCE = \"pkce is required for this client\",\n}\n/**\n * Determines if PKCE is required for a given client and scope.\n *\n * PKCE is always required for:\n * 1. Public clients (cannot securely store client_secret)\n * 2. Requests with offline_access scope (refresh token security)\n *\n * For confidential clients without offline_access:\n * - Uses client.requirePKCE if set (defaults to true)\n *\n * Returns false if PKCE is not required, or the reason it is required.\n *\n * @internal\n */\nexport function isPKCERequired(\n\tclient: SchemaClient<Scope[]>,\n\trequestedScopes?: string[],\n): false | PKCERequirementErrors {\n\t// Determine if client is public\n\tconst isPublicClient =\n\t\tclient.tokenEndpointAuthMethod === \"none\" ||\n\t\tclient.type === \"native\" ||\n\t\tclient.type === \"user-agent-based\" ||\n\t\tclient.public === true;\n\n\t// PKCE always required for public clients\n\tif (isPublicClient) {\n\t\treturn PKCERequirementErrors.PUBLIC_CLIENT;\n\t}\n\n\t// PKCE always required for offline_access scope (refresh tokens)\n\tif (requestedScopes?.includes(\"offline_access\")) {\n\t\treturn PKCERequirementErrors.OFFLINE_ACCESS_SCOPE;\n\t}\n\n\tif (client.requirePKCE ?? true) {\n\t\treturn PKCERequirementErrors.CLIENT_REQUIRE_PKCE;\n\t}\n\n\treturn false;\n}\n"],"mappings":"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"}