@better-auth/core 1.7.0-rc.1 → 1.7.0-rc.3

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.
Files changed (213) hide show
  1. package/dist/api/index.d.mts +28 -28
  2. package/dist/async_hooks/index.d.mts +0 -1
  3. package/dist/async_hooks/pure.index.d.mts +0 -1
  4. package/dist/context/endpoint-context.d.mts +1 -1
  5. package/dist/context/global.d.mts +0 -1
  6. package/dist/context/global.mjs +1 -1
  7. package/dist/context/request-state.d.mts +0 -1
  8. package/dist/context/request-state.mjs +8 -5
  9. package/dist/context/transaction.d.mts +8 -3
  10. package/dist/context/transaction.mjs +27 -10
  11. package/dist/db/adapter/factory.d.mts +3 -5
  12. package/dist/db/adapter/factory.mjs +7 -5
  13. package/dist/db/adapter/get-default-field-name.d.mts +2 -9
  14. package/dist/db/adapter/get-default-model-name.d.mts +1 -5
  15. package/dist/db/adapter/get-default-model-name.mjs +6 -5
  16. package/dist/db/adapter/get-field-attributes.d.mts +4 -11
  17. package/dist/db/adapter/get-field-name.d.mts +2 -9
  18. package/dist/db/adapter/get-id-field.d.mts +4 -12
  19. package/dist/db/adapter/get-model-name.d.mts +1 -5
  20. package/dist/db/adapter/index.d.mts +7 -32
  21. package/dist/db/adapter/types.d.mts +5 -17
  22. package/dist/db/adapter/utils.d.mts +0 -1
  23. package/dist/db/database-index.d.mts +44 -0
  24. package/dist/db/database-index.mjs +168 -0
  25. package/dist/db/get-tables.d.mts +7 -1
  26. package/dist/db/get-tables.mjs +47 -6
  27. package/dist/db/index.d.mts +3 -3
  28. package/dist/db/index.mjs +2 -2
  29. package/dist/db/internal.d.mts +3 -0
  30. package/dist/db/internal.mjs +3 -0
  31. package/dist/db/plugin.d.mts +4 -3
  32. package/dist/db/schema/account.d.mts +16 -3
  33. package/dist/db/schema/account.mjs +35 -2
  34. package/dist/db/schema/rate-limit.d.mts +1 -1
  35. package/dist/db/schema/rate-limit.mjs +9 -0
  36. package/dist/db/schema/session.d.mts +1 -1
  37. package/dist/db/schema/shared.d.mts +0 -1
  38. package/dist/db/schema/user.d.mts +1 -1
  39. package/dist/db/schema/verification.d.mts +1 -1
  40. package/dist/db/type.d.mts +17 -9
  41. package/dist/env/logger.d.mts +25 -25
  42. package/dist/error/codes.d.mts +0 -1
  43. package/dist/error/index.d.mts +0 -1
  44. package/dist/index.d.mts +1 -1
  45. package/dist/instrumentation/attributes.d.mts +0 -1
  46. package/dist/instrumentation/pure.index.d.mts +0 -1
  47. package/dist/instrumentation/tracer.mjs +1 -1
  48. package/dist/oauth2/authorization-params.d.mts +0 -1
  49. package/dist/oauth2/client-assertion.d.mts +3 -13
  50. package/dist/oauth2/client-credentials-token.d.mts +3 -17
  51. package/dist/oauth2/client-credentials-token.mjs +1 -4
  52. package/dist/oauth2/create-authorization-url.d.mts +3 -21
  53. package/dist/oauth2/dpop.d.mts +2 -23
  54. package/dist/oauth2/dpop.mjs +1 -1
  55. package/dist/oauth2/index.d.mts +3 -3
  56. package/dist/oauth2/index.mjs +2 -2
  57. package/dist/oauth2/oauth-provider.d.mts +94 -36
  58. package/dist/oauth2/refresh-access-token.d.mts +3 -19
  59. package/dist/oauth2/refresh-access-token.mjs +3 -9
  60. package/dist/oauth2/reject-redirects.mjs +1 -1
  61. package/dist/oauth2/token-endpoint-auth.d.mts +0 -1
  62. package/dist/oauth2/utils.d.mts +0 -1
  63. package/dist/oauth2/utils.mjs +2 -3
  64. package/dist/oauth2/validate-authorization-code.d.mts +5 -29
  65. package/dist/oauth2/verify-id-token.d.mts +3 -2
  66. package/dist/oauth2/verify-id-token.mjs +3 -3
  67. package/dist/oauth2/verify.d.mts +18 -9
  68. package/dist/oauth2/verify.mjs +68 -5
  69. package/dist/social-providers/apple.d.mts +9 -24
  70. package/dist/social-providers/apple.mjs +2 -1
  71. package/dist/social-providers/atlassian.d.mts +18 -19
  72. package/dist/social-providers/atlassian.mjs +1 -1
  73. package/dist/social-providers/cognito.d.mts +33 -22
  74. package/dist/social-providers/cognito.mjs +2 -2
  75. package/dist/social-providers/discord.d.mts +7 -20
  76. package/dist/social-providers/discord.mjs +1 -1
  77. package/dist/social-providers/dropbox.d.mts +7 -22
  78. package/dist/social-providers/dropbox.mjs +1 -1
  79. package/dist/social-providers/facebook.d.mts +19 -27
  80. package/dist/social-providers/facebook.mjs +6 -17
  81. package/dist/social-providers/figma.d.mts +7 -22
  82. package/dist/social-providers/figma.mjs +1 -1
  83. package/dist/social-providers/github.d.mts +7 -23
  84. package/dist/social-providers/github.mjs +1 -1
  85. package/dist/social-providers/gitlab.d.mts +6 -43
  86. package/dist/social-providers/gitlab.mjs +1 -1
  87. package/dist/social-providers/google.d.mts +10 -32
  88. package/dist/social-providers/google.mjs +2 -1
  89. package/dist/social-providers/huggingface.d.mts +7 -22
  90. package/dist/social-providers/huggingface.mjs +1 -1
  91. package/dist/social-providers/index.d.mts +229 -787
  92. package/dist/social-providers/kakao.d.mts +8 -33
  93. package/dist/social-providers/kakao.mjs +1 -1
  94. package/dist/social-providers/kick.d.mts +7 -22
  95. package/dist/social-providers/kick.mjs +1 -1
  96. package/dist/social-providers/line.d.mts +8 -44
  97. package/dist/social-providers/line.mjs +4 -6
  98. package/dist/social-providers/linear.d.mts +7 -21
  99. package/dist/social-providers/linear.mjs +1 -1
  100. package/dist/social-providers/linkedin.d.mts +7 -21
  101. package/dist/social-providers/linkedin.mjs +1 -1
  102. package/dist/social-providers/microsoft-entra-id.d.mts +8 -18
  103. package/dist/social-providers/microsoft-entra-id.mjs +24 -4
  104. package/dist/social-providers/naver.d.mts +27 -42
  105. package/dist/social-providers/naver.mjs +1 -1
  106. package/dist/social-providers/notion.d.mts +7 -21
  107. package/dist/social-providers/notion.mjs +1 -1
  108. package/dist/social-providers/paybin.d.mts +8 -23
  109. package/dist/social-providers/paybin.mjs +2 -1
  110. package/dist/social-providers/paypal.d.mts +8 -33
  111. package/dist/social-providers/paypal.mjs +1 -1
  112. package/dist/social-providers/polar.d.mts +7 -22
  113. package/dist/social-providers/polar.mjs +1 -1
  114. package/dist/social-providers/railway.d.mts +7 -22
  115. package/dist/social-providers/railway.mjs +1 -1
  116. package/dist/social-providers/reddit.d.mts +7 -20
  117. package/dist/social-providers/reddit.mjs +1 -1
  118. package/dist/social-providers/roblox.d.mts +7 -20
  119. package/dist/social-providers/roblox.mjs +1 -1
  120. package/dist/social-providers/salesforce.d.mts +7 -22
  121. package/dist/social-providers/salesforce.mjs +1 -1
  122. package/dist/social-providers/slack.d.mts +7 -20
  123. package/dist/social-providers/slack.mjs +1 -1
  124. package/dist/social-providers/spotify.d.mts +7 -22
  125. package/dist/social-providers/spotify.mjs +1 -1
  126. package/dist/social-providers/tiktok.d.mts +10 -22
  127. package/dist/social-providers/tiktok.mjs +1 -1
  128. package/dist/social-providers/twitch.d.mts +7 -20
  129. package/dist/social-providers/twitch.mjs +1 -1
  130. package/dist/social-providers/twitter.d.mts +29 -26
  131. package/dist/social-providers/twitter.mjs +1 -1
  132. package/dist/social-providers/vercel.d.mts +7 -22
  133. package/dist/social-providers/vercel.mjs +1 -1
  134. package/dist/social-providers/vk.d.mts +8 -24
  135. package/dist/social-providers/vk.mjs +5 -6
  136. package/dist/social-providers/wechat.d.mts +7 -19
  137. package/dist/social-providers/wechat.mjs +1 -1
  138. package/dist/social-providers/zoom.d.mts +9 -25
  139. package/dist/social-providers/zoom.mjs +1 -1
  140. package/dist/types/context.d.mts +16 -9
  141. package/dist/types/cookie.d.mts +0 -1
  142. package/dist/types/helper.d.mts +1 -1
  143. package/dist/types/index.d.mts +1 -1
  144. package/dist/types/init-options.d.mts +37 -26
  145. package/dist/types/plugin-client.d.mts +1 -3
  146. package/dist/types/plugin.d.mts +2 -2
  147. package/dist/utils/async.d.mts +0 -1
  148. package/dist/utils/db.d.mts +1 -0
  149. package/dist/utils/deprecate.d.mts +0 -1
  150. package/dist/utils/error-codes.d.mts +2 -2
  151. package/dist/utils/host.d.mts +27 -1
  152. package/dist/utils/host.mjs +1 -1
  153. package/dist/utils/ip.d.mts +1 -0
  154. package/dist/utils/ip.mjs +1 -1
  155. package/dist/utils/is-api-error.d.mts +0 -1
  156. package/dist/utils/redirect-uri.d.mts +6 -2
  157. package/dist/utils/redirect-uri.mjs +11 -1
  158. package/package.json +10 -2
  159. package/src/context/request-state.ts +26 -6
  160. package/src/context/transaction.ts +37 -18
  161. package/src/db/adapter/factory.ts +34 -28
  162. package/src/db/adapter/get-default-model-name.ts +23 -14
  163. package/src/db/database-index.ts +351 -0
  164. package/src/db/get-tables.ts +89 -9
  165. package/src/db/index.ts +4 -0
  166. package/src/db/internal.ts +12 -0
  167. package/src/db/plugin.ts +3 -1
  168. package/src/db/schema/account.ts +27 -1
  169. package/src/db/schema/user.ts +1 -1
  170. package/src/db/type.ts +12 -0
  171. package/src/error/codes.ts +1 -0
  172. package/src/oauth2/index.ts +5 -0
  173. package/src/oauth2/oauth-provider.ts +105 -36
  174. package/src/oauth2/verify-id-token.ts +4 -2
  175. package/src/oauth2/verify.ts +121 -12
  176. package/src/social-providers/apple.ts +2 -1
  177. package/src/social-providers/atlassian.ts +1 -1
  178. package/src/social-providers/cognito.ts +2 -2
  179. package/src/social-providers/discord.ts +1 -1
  180. package/src/social-providers/dropbox.ts +1 -1
  181. package/src/social-providers/facebook.ts +21 -28
  182. package/src/social-providers/figma.ts +1 -1
  183. package/src/social-providers/github.ts +1 -1
  184. package/src/social-providers/gitlab.ts +1 -1
  185. package/src/social-providers/google.ts +2 -1
  186. package/src/social-providers/huggingface.ts +1 -1
  187. package/src/social-providers/kakao.ts +1 -1
  188. package/src/social-providers/kick.ts +1 -1
  189. package/src/social-providers/line.ts +7 -12
  190. package/src/social-providers/linear.ts +1 -1
  191. package/src/social-providers/linkedin.ts +1 -1
  192. package/src/social-providers/microsoft-entra-id.ts +36 -27
  193. package/src/social-providers/naver.ts +1 -1
  194. package/src/social-providers/notion.ts +1 -1
  195. package/src/social-providers/paybin.ts +2 -1
  196. package/src/social-providers/paypal.ts +1 -1
  197. package/src/social-providers/polar.ts +1 -1
  198. package/src/social-providers/railway.ts +1 -1
  199. package/src/social-providers/reddit.ts +1 -1
  200. package/src/social-providers/roblox.ts +1 -1
  201. package/src/social-providers/salesforce.ts +1 -1
  202. package/src/social-providers/slack.ts +1 -1
  203. package/src/social-providers/spotify.ts +1 -1
  204. package/src/social-providers/tiktok.ts +2 -2
  205. package/src/social-providers/twitch.ts +1 -1
  206. package/src/social-providers/twitter.ts +1 -1
  207. package/src/social-providers/vercel.ts +1 -1
  208. package/src/social-providers/vk.ts +2 -2
  209. package/src/social-providers/wechat.ts +1 -1
  210. package/src/social-providers/zoom.ts +1 -1
  211. package/src/types/context.ts +21 -14
  212. package/src/types/init-options.ts +15 -14
  213. package/src/utils/redirect-uri.ts +20 -0
@@ -16,8 +16,7 @@ function parseScopeField(scope) {
16
16
  }
17
17
  function getOAuth2Tokens(data) {
18
18
  const getDate = (seconds) => {
19
- const now = /* @__PURE__ */ new Date();
20
- return new Date(now.getTime() + seconds * 1e3);
19
+ return new Date((/* @__PURE__ */ new Date()).getTime() + seconds * 1e3);
21
20
  };
22
21
  return {
23
22
  tokenType: data.token_type,
@@ -48,7 +47,7 @@ function applyDefaultAccessTokenExpiry(tokens, accessTokenExpiresIn) {
48
47
  function mergeScopes(stored, incoming) {
49
48
  const existing = stored ? stored.split(",").map((scope) => scope.trim()).filter(Boolean) : [];
50
49
  const next = (incoming ?? []).map((scope) => scope.trim()).filter(Boolean);
51
- return [...new Set([...existing, ...next])].join(",");
50
+ return [.../* @__PURE__ */ new Set([...existing, ...next])].join(",");
52
51
  }
53
52
  /**
54
53
  * Return the provider's primary Client ID: the single string, or the entry at
@@ -1,8 +1,8 @@
1
1
  import { AwaitableFunction } from "../types/helper.mjs";
2
+ import "../types/index.mjs";
2
3
  import { OAuth2Tokens, ProviderOptions } from "./oauth-provider.mjs";
3
4
  import { TokenEndpointAuth, TokenEndpointSecretAuthentication } from "./token-endpoint-auth.mjs";
4
- import * as jose from "jose";
5
-
5
+ import "./index.mjs";
6
6
  //#region src/oauth2/validate-authorization-code.d.ts
7
7
  interface AuthorizationCodeRequestInput {
8
8
  code: string;
@@ -20,38 +20,14 @@ interface AuthorizationCodeRequestInput {
20
20
  interface ValidateAuthorizationCodeInput extends AuthorizationCodeRequestInput {
21
21
  tokenEndpoint: string;
22
22
  }
23
- declare function authorizationCodeRequest({
24
- code,
25
- codeVerifier,
26
- redirectURI,
27
- options,
28
- authentication,
29
- tokenEndpointAuth,
30
- tokenEndpoint,
31
- deviceId,
32
- headers,
33
- additionalParams,
34
- resource
35
- }: AuthorizationCodeRequestInput): Promise<{
23
+ declare function authorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, tokenEndpointAuth, tokenEndpoint, deviceId, headers, additionalParams, resource }: AuthorizationCodeRequestInput): Promise<{
36
24
  body: URLSearchParams;
37
25
  headers: Record<string, string>;
38
26
  }>;
39
- declare function validateAuthorizationCode({
40
- code,
41
- codeVerifier,
42
- redirectURI,
43
- options,
44
- tokenEndpoint,
45
- authentication,
46
- tokenEndpointAuth,
47
- deviceId,
48
- headers,
49
- additionalParams,
50
- resource
51
- }: ValidateAuthorizationCodeInput): Promise<OAuth2Tokens>;
27
+ declare function validateAuthorizationCode({ code, codeVerifier, redirectURI, options, tokenEndpoint, authentication, tokenEndpointAuth, deviceId, headers, additionalParams, resource }: ValidateAuthorizationCodeInput): Promise<OAuth2Tokens>;
52
28
  declare function validateToken(token: string, jwksEndpoint: string, options?: {
53
29
  audience?: string | string[];
54
30
  issuer?: string | string[];
55
- }): Promise<jose.JWTVerifyResult<jose.JWTPayload> & jose.ResolvedKey>;
31
+ }): Promise<import("jose").JWTVerifyResult<import("jose").JWTPayload> & import("jose").ResolvedKey>;
56
32
  //#endregion
57
33
  export { authorizationCodeRequest, validateAuthorizationCode, validateToken };
@@ -1,5 +1,6 @@
1
+ import { GenericEndpointContext } from "../types/context.mjs";
2
+ import "../types/index.mjs";
1
3
  import { OAuthProvider } from "./oauth-provider.mjs";
2
-
3
4
  //#region src/oauth2/verify-id-token.d.ts
4
5
  type ProviderWithIdTokenConfig = Pick<OAuthProvider, "idToken" | "options">;
5
6
  /**
@@ -22,6 +23,6 @@ declare function supportsIdTokenSignIn(provider: ProviderWithIdTokenConfig): boo
22
23
  *
23
24
  * @returns `true` only when the token is authentic for the provider.
24
25
  */
25
- declare function verifyProviderIdToken(provider: ProviderWithIdTokenConfig, token: string, nonce?: string): Promise<boolean>;
26
+ declare function verifyProviderIdToken(provider: ProviderWithIdTokenConfig, token: string, nonce?: string, ctx?: GenericEndpointContext): Promise<boolean>;
26
27
  //#endregion
27
28
  export { supportsIdTokenSignIn, verifyProviderIdToken };
@@ -35,14 +35,14 @@ function supportsIdTokenSignIn(provider) {
35
35
  *
36
36
  * @returns `true` only when the token is authentic for the provider.
37
37
  */
38
- async function verifyProviderIdToken(provider, token, nonce) {
38
+ async function verifyProviderIdToken(provider, token, nonce, ctx) {
39
39
  const options = provider.options ?? {};
40
40
  if (options.disableIdTokenSignIn) return false;
41
41
  try {
42
- if (options.verifyIdToken) return await options.verifyIdToken(token, nonce);
42
+ if (options.verifyIdToken) return await options.verifyIdToken(token, nonce, ctx);
43
43
  const config = provider.idToken;
44
44
  if (!config) return false;
45
- if ("verify" in config) return await config.verify(token, nonce);
45
+ if ("verify" in config) return await config.verify(token, nonce, ctx);
46
46
  if (token.split(".").length !== 3) return config.allowOpaqueToken === true;
47
47
  const { alg } = decodeProtectedHeader(token);
48
48
  const { payload } = await jwtVerify(token, config.jwks, {
@@ -1,9 +1,10 @@
1
1
  import { DpopReplayStore } from "./dpop.mjs";
2
+ import { APIError } from "better-call";
2
3
  import { JSONWebKeySet, JWTPayload, JWTVerifyOptions } from "jose";
3
-
4
4
  //#region src/oauth2/verify.d.ts
5
5
  type JwksFetchOptions = {
6
- /** Jwks url or promise of a Jwks */jwksFetch: string | (() => Promise<JSONWebKeySet | undefined>);
6
+ /** Jwks url or promise of a Jwks */
7
+ jwksFetch: string | (() => Promise<JSONWebKeySet | undefined>);
7
8
  /**
8
9
  * Stable object to cache the result of a function `jwksFetch` under,
9
10
  * with the same TTL and kid-miss refetch rules as string sources.
@@ -11,9 +12,6 @@ type JwksFetchOptions = {
11
12
  */
12
13
  jwksCacheKey?: object;
13
14
  };
14
- /**
15
- * @internal
16
- */
17
15
  interface VerifyAccessTokenRemote {
18
16
  /** Full url of the introspect endpoint. Should end with `/oauth2/introspect` */
19
17
  introspectUrl: string;
@@ -45,8 +43,13 @@ interface VerifyAccessTokenRemote {
45
43
  interface VerifyAccessTokenOptions {
46
44
  /** Verify options */
47
45
  verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
48
- /** Scopes to additionally verify. Token must include all but not exact. */
49
- scopes?: string[];
46
+ /** Scopes the token must satisfy. */
47
+ requiredScopes?: readonly string[];
48
+ /**
49
+ * Determines whether a required scope is satisfied by the granted scope
50
+ * set. Defaults to exact set membership.
51
+ */
52
+ isScopeSatisfied?: (requiredScope: string, grantedScopes: ReadonlySet<string>) => boolean;
50
53
  /** Required to verify access token locally */
51
54
  jwksUrl?: string;
52
55
  /** If provided, can verify a token remotely */
@@ -87,9 +90,15 @@ declare function requestToResourceInput(request: Request): ResourceRequestInput;
87
90
  * Can also be configured for remote verification.
88
91
  */
89
92
  declare function verifyJwsAccessToken(token: string, opts: JwksFetchOptions & {
90
- /** Verify options */verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
93
+ /** Verify options */
94
+ verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
91
95
  }): Promise<JWTPayload>;
92
96
  declare function getJwks(token: string, opts: JwksFetchOptions): Promise<JSONWebKeySet>;
97
+ declare function createInsufficientScopeError(requiredScopes: readonly string[], description?: string): APIError;
98
+ /**
99
+ * Returns whether an error is a typed RFC 6750 insufficient-scope failure.
100
+ */
101
+ declare function isInsufficientScopeError(error: unknown): error is APIError;
93
102
  /**
94
103
  * Performs local verification of a bearer access token for your API.
95
104
  *
@@ -112,4 +121,4 @@ declare function verifyBearerToken(token: string, opts: VerifyAccessTokenOptions
112
121
  */
113
122
  declare function verifyAccessTokenRequest(request: ResourceRequestInput, opts: VerifyAccessTokenRequestOptions): Promise<JWTPayload>;
114
123
  //#endregion
115
- export { ResourceRequestInput, VerifyAccessTokenOptions, VerifyAccessTokenRequestOptions, getJwks, requestToResourceInput, verifyAccessTokenRequest, verifyBearerToken, verifyJwsAccessToken };
124
+ export { ResourceRequestInput, VerifyAccessTokenOptions, VerifyAccessTokenRemote, VerifyAccessTokenRequestOptions, createInsufficientScopeError, getJwks, isInsufficientScopeError, requestToResourceInput, verifyAccessTokenRequest, verifyBearerToken, verifyJwsAccessToken };
@@ -4,7 +4,7 @@ import { createInMemoryDpopReplayStore, enforceDpopBinding, getDpopJktFromPayloa
4
4
  import { APIError } from "better-call";
5
5
  import { UnsecuredJWT, createLocalJWKSet, decodeProtectedHeader, errors, jwtVerify } from "jose";
6
6
  //#region src/oauth2/verify.ts
7
- const joseInfrastructureErrorCodes = new Set([
7
+ const joseInfrastructureErrorCodes = /* @__PURE__ */ new Set([
8
8
  errors.JWKSTimeout.code,
9
9
  errors.JWKSInvalid.code,
10
10
  errors.JWKSMultipleMatchingKeys.code
@@ -207,12 +207,73 @@ async function verifyAccessTokenPayload(token, opts) {
207
207
  }
208
208
  }
209
209
  if (!payload) throw new APIError("UNAUTHORIZED", { message: `no token payload` });
210
- if (opts.scopes) {
211
- const validScopes = new Set(payload.scope?.split(" "));
212
- for (const sc of opts.scopes) if (!validScopes.has(sc)) throw new APIError("FORBIDDEN", { message: `invalid scope ${sc}` });
210
+ const grantedScopes = parseGrantedScopes(payload.scope);
211
+ if (opts.requiredScopes) {
212
+ const isScopeSatisfied = opts.isScopeSatisfied ?? ((requiredScope, scopes) => scopes.has(requiredScope));
213
+ const missingScopes = opts.requiredScopes.filter((scope) => !isScopeSatisfied(scope, grantedScopes));
214
+ if (missingScopes.length > 0) throw createInsufficientScopeError(missingScopes);
213
215
  }
214
216
  return payload;
215
217
  }
218
+ /**
219
+ * Build the RFC 6750 §3.1 insufficient-scope failure: the access token is valid
220
+ * but lacks scopes the operation needs.
221
+ *
222
+ * Resource-server challenge builders turn this into a `403` carrying a
223
+ * `WWW-Authenticate: Bearer error="insufficient_scope"` challenge that names
224
+ * `scopes`, so the client knows what to request when it re-authorizes. Throw it
225
+ * from a route handler to challenge for scopes only that operation needs; a
226
+ * plain `FORBIDDEN` stays a plain `403`, since a permission denial the client
227
+ * cannot fix by re-authorizing must not send the user through consent again.
228
+ *
229
+ * @param requiredScopes - Every scope the operation requires but the token lacks.
230
+ * @param description - RFC 6750 `error_description` text. It must use the
231
+ * printable ASCII character set allowed by the specification.
232
+ */
233
+ const OAUTH_SCOPE_TOKEN_PATTERN = /^[\x21\x23-\x5b\x5d-\x7e]+$/;
234
+ const OAUTH_ERROR_DESCRIPTION_PATTERN = /^[\x20-\x21\x23-\x5b\x5d-\x7e]+$/;
235
+ const insufficientScopeErrors = /* @__PURE__ */ new WeakSet();
236
+ function isOAuthScopeToken(value) {
237
+ return OAUTH_SCOPE_TOKEN_PATTERN.test(value);
238
+ }
239
+ function validateScopeTokens(scopes, label) {
240
+ for (const scope of scopes) if (!isOAuthScopeToken(scope)) throw new TypeError(`invalid ${label}: ${JSON.stringify(scope)}`);
241
+ }
242
+ function validateRequiredScopes(opts) {
243
+ if (opts.requiredScopes) validateScopeTokens(opts.requiredScopes, "required scope");
244
+ }
245
+ function parseGrantedScopes(scope) {
246
+ if (scope === void 0) return /* @__PURE__ */ new Set();
247
+ if (typeof scope !== "string" || scope.length === 0 || scope.split(" ").some((token) => !isOAuthScopeToken(token))) throw new APIError("UNAUTHORIZED", {
248
+ message: "access token scope claim is invalid",
249
+ error: "invalid_token",
250
+ error_description: "access token scope claim is invalid"
251
+ });
252
+ return new Set(scope.split(" "));
253
+ }
254
+ function createInsufficientScopeError(requiredScopes, description = `access token is missing required scope: ${requiredScopes.join(" ")}`) {
255
+ if (requiredScopes.length === 0) throw new TypeError("requiredScopes must contain at least one scope");
256
+ validateScopeTokens(requiredScopes, "required scope");
257
+ if (typeof description !== "string" || !OAUTH_ERROR_DESCRIPTION_PATTERN.test(description)) throw new TypeError("invalid error_description");
258
+ const error = new APIError("FORBIDDEN", {
259
+ message: description,
260
+ error: "insufficient_scope",
261
+ error_description: description,
262
+ scope: [...new Set(requiredScopes)].join(" ")
263
+ });
264
+ insufficientScopeErrors.add(error);
265
+ return error;
266
+ }
267
+ /**
268
+ * Returns whether an error is a typed RFC 6750 insufficient-scope failure.
269
+ */
270
+ function isInsufficientScopeError(error) {
271
+ if (!(error instanceof APIError) || !insufficientScopeErrors.has(error) || error.status !== "FORBIDDEN") return false;
272
+ const body = error.body;
273
+ if (body?.error !== "insufficient_scope" || typeof body.scope !== "string") return false;
274
+ const scopes = body.scope.split(" ");
275
+ return scopes.length > 0 && scopes.every(isOAuthScopeToken);
276
+ }
216
277
  function throwDpopUnauthorized(message, error) {
217
278
  throw new APIError("UNAUTHORIZED", error ? {
218
279
  message,
@@ -231,6 +292,7 @@ function throwDpopUnauthorized(message, error) {
231
292
  * alone.
232
293
  */
233
294
  async function verifyBearerToken(token, opts) {
295
+ validateRequiredScopes(opts);
234
296
  const payload = await verifyAccessTokenPayload(token, opts);
235
297
  if (getDpopJktFromPayload(payload)) throwDpopUnauthorized("DPoP-bound access token requires verifyAccessTokenRequest", "invalid_token");
236
298
  return payload;
@@ -245,6 +307,7 @@ async function verifyBearerToken(token, opts) {
245
307
  * scheme, method, URL, DPoP proof, `ath`, and `cnf.jkt` binding.
246
308
  */
247
309
  async function verifyAccessTokenRequest(request, opts) {
310
+ validateRequiredScopes(opts);
248
311
  const authorization = parseAccessTokenAuthorization(request.authorizationHeader);
249
312
  if (!authorization?.token) throwDpopUnauthorized("missing authorization header");
250
313
  if (authorization.scheme === "Unknown") throwDpopUnauthorized("authorization scheme must be Bearer or DPoP", "invalid_token");
@@ -267,4 +330,4 @@ async function verifyAccessTokenRequest(request, opts) {
267
330
  return payload;
268
331
  }
269
332
  //#endregion
270
- export { getJwks, requestToResourceInput, verifyAccessTokenRequest, verifyBearerToken, verifyJwsAccessToken };
333
+ export { createInsufficientScopeError, getJwks, isInsufficientScopeError, jwksCache, requestToResourceInput, verifyAccessTokenRequest, verifyBearerToken, verifyJwsAccessToken };
@@ -1,6 +1,5 @@
1
- import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
2
- import * as jose from "jose";
3
-
1
+ import { OAuth2Tokens, OAuth2UserInfo, OAuthAccountKeyContext, ProviderOptions } from "../oauth2/oauth-provider.mjs";
2
+ import "../oauth2/index.mjs";
4
3
  //#region src/social-providers/apple.d.ts
5
4
  interface AppleProfile {
6
5
  /**
@@ -69,12 +68,9 @@ interface AppleOptions extends ProviderOptions<AppleProfile> {
69
68
  declare const apple: (options: AppleOptions) => {
70
69
  id: "apple";
71
70
  name: string;
72
- createAuthorizationURL({
73
- state,
74
- scopes,
75
- redirectURI,
76
- additionalParams
77
- }: {
71
+ accountSubject: ({ profile }: OAuthAccountKeyContext<AppleProfile>) => string;
72
+ accountIssuer: string;
73
+ createAuthorizationURL({ state, scopes, redirectURI, additionalParams }: {
78
74
  state: string;
79
75
  codeVerifier: string;
80
76
  scopes?: string[] | undefined;
@@ -84,18 +80,14 @@ declare const apple: (options: AppleOptions) => {
84
80
  idTokenNonce?: string | undefined;
85
81
  additionalParams?: Record<string, string> | undefined;
86
82
  }): Promise<URL>;
87
- validateAuthorizationCode: ({
88
- code,
89
- codeVerifier,
90
- redirectURI
91
- }: {
83
+ validateAuthorizationCode: ({ code, codeVerifier, redirectURI }: {
92
84
  code: string;
93
85
  redirectURI: string;
94
86
  codeVerifier?: string | undefined;
95
87
  deviceId?: string | undefined;
96
88
  }) => Promise<OAuth2Tokens>;
97
89
  idToken: {
98
- jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
90
+ jwks: (header: import("jose").JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
99
91
  issuer: string;
100
92
  audience: string | string[];
101
93
  maxTokenAge: string;
@@ -112,15 +104,8 @@ declare const apple: (options: AppleOptions) => {
112
104
  email?: string;
113
105
  } | undefined;
114
106
  }): Promise<{
115
- user: {
116
- id: string;
117
- name?: string;
118
- email?: string | null;
119
- image?: string;
120
- emailVerified: boolean;
121
- [key: string]: any;
122
- };
123
- data: any;
107
+ user: OAuth2UserInfo & Record<string, unknown>;
108
+ data: AppleProfile;
124
109
  } | null>;
125
110
  options: AppleOptions;
126
111
  };
@@ -12,6 +12,8 @@ const apple = (options) => {
12
12
  return {
13
13
  id: "apple",
14
14
  name: "Apple",
15
+ accountSubject: ({ profile }) => profile.sub,
16
+ accountIssuer: "https://appleid.apple.com",
15
17
  async createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
16
18
  if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
17
19
  logger.error("Client ID and client secret are required for Apple. Make sure to provide them in the options.");
@@ -71,7 +73,6 @@ const apple = (options) => {
71
73
  const userMap = await options.mapProfileToUser?.(enrichedProfile);
72
74
  return {
73
75
  user: {
74
- id: profile.sub,
75
76
  name: enrichedProfile.name,
76
77
  emailVerified,
77
78
  email: profile.email,
@@ -1,4 +1,5 @@
1
- import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
1
+ import { OAuth2Tokens, OAuth2UserInfo, OAuthAccountKeyContext, ProviderOptions } from "../oauth2/oauth-provider.mjs";
2
+ import "../oauth2/index.mjs";
2
3
  //#region src/social-providers/atlassian.d.ts
3
4
  interface AtlassianProfile {
4
5
  account_type?: string | undefined;
@@ -21,13 +22,8 @@ interface AtlassianOptions extends ProviderOptions<AtlassianProfile> {
21
22
  declare const atlassian: (options: AtlassianOptions) => {
22
23
  id: "atlassian";
23
24
  name: string;
24
- createAuthorizationURL({
25
- state,
26
- scopes,
27
- codeVerifier,
28
- redirectURI,
29
- additionalParams
30
- }: {
25
+ accountSubject: ({ profile }: OAuthAccountKeyContext<AtlassianProfile>) => string;
26
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }: {
31
27
  state: string;
32
28
  codeVerifier: string;
33
29
  scopes?: string[] | undefined;
@@ -37,11 +33,7 @@ declare const atlassian: (options: AtlassianOptions) => {
37
33
  idTokenNonce?: string | undefined;
38
34
  additionalParams?: Record<string, string> | undefined;
39
35
  }): Promise<URL>;
40
- validateAuthorizationCode: ({
41
- code,
42
- codeVerifier,
43
- redirectURI
44
- }: {
36
+ validateAuthorizationCode: ({ code, codeVerifier, redirectURI }: {
45
37
  code: string;
46
38
  redirectURI: string;
47
39
  codeVerifier?: string | undefined;
@@ -58,15 +50,22 @@ declare const atlassian: (options: AtlassianOptions) => {
58
50
  email?: string;
59
51
  } | undefined;
60
52
  }): Promise<{
53
+ user: OAuth2UserInfo & Record<string, unknown>;
54
+ data: AtlassianProfile;
55
+ } | {
61
56
  user: {
62
- id: string;
63
- name?: string;
64
- email?: string | null;
65
- image?: string;
57
+ id?: never;
58
+ name: string;
59
+ email: string | null;
60
+ image: string;
66
61
  emailVerified: boolean;
67
- [key: string]: any;
68
62
  };
69
- data: any;
63
+ data: {
64
+ account_id: string;
65
+ name: string;
66
+ email?: string | undefined;
67
+ picture?: string | undefined;
68
+ };
70
69
  } | null>;
71
70
  options: AtlassianOptions;
72
71
  };
@@ -10,6 +10,7 @@ const atlassian = (options) => {
10
10
  return {
11
11
  id: "atlassian",
12
12
  name: "Atlassian",
13
+ accountSubject: ({ profile }) => profile.account_id,
13
14
  async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
14
15
  if (!options.clientId || !options.clientSecret) {
15
16
  logger.error("Client Id and Secret are required for Atlassian");
@@ -62,7 +63,6 @@ const atlassian = (options) => {
62
63
  const userMap = await options.mapProfileToUser?.(profile);
63
64
  return {
64
65
  user: {
65
- id: profile.account_id,
66
66
  name: profile.name,
67
67
  email: profile.email,
68
68
  image: profile.picture,
@@ -1,6 +1,5 @@
1
- import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
2
- import * as jose from "jose";
3
-
1
+ import { OAuth2Tokens, OAuth2UserInfo, OAuthAccountKeyContext, ProviderOptions } from "../oauth2/oauth-provider.mjs";
2
+ import "../oauth2/index.mjs";
4
3
  //#region src/social-providers/cognito.d.ts
5
4
  interface CognitoProfile {
6
5
  sub: string;
@@ -49,13 +48,9 @@ interface CognitoOptions extends ProviderOptions<CognitoProfile> {
49
48
  declare const cognito: (options: CognitoOptions) => {
50
49
  id: "cognito";
51
50
  name: string;
52
- createAuthorizationURL({
53
- state,
54
- scopes,
55
- codeVerifier,
56
- redirectURI,
57
- additionalParams
58
- }: {
51
+ accountSubject: ({ profile }: OAuthAccountKeyContext<CognitoProfile>) => string;
52
+ accountIssuer: string;
53
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }: {
59
54
  state: string;
60
55
  codeVerifier: string;
61
56
  scopes?: string[] | undefined;
@@ -65,11 +60,7 @@ declare const cognito: (options: CognitoOptions) => {
65
60
  idTokenNonce?: string | undefined;
66
61
  additionalParams?: Record<string, string> | undefined;
67
62
  }): Promise<URL>;
68
- validateAuthorizationCode: ({
69
- code,
70
- codeVerifier,
71
- redirectURI
72
- }: {
63
+ validateAuthorizationCode: ({ code, codeVerifier, redirectURI }: {
73
64
  code: string;
74
65
  redirectURI: string;
75
66
  codeVerifier?: string | undefined;
@@ -77,7 +68,7 @@ declare const cognito: (options: CognitoOptions) => {
77
68
  }) => Promise<OAuth2Tokens>;
78
69
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
79
70
  idToken: {
80
- jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
71
+ jwks: (header: import("jose").JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
81
72
  issuer: string;
82
73
  audience: string | string[];
83
74
  maxTokenAge: string;
@@ -92,15 +83,35 @@ declare const cognito: (options: CognitoOptions) => {
92
83
  email?: string;
93
84
  } | undefined;
94
85
  }): Promise<{
86
+ user: OAuth2UserInfo & Record<string, unknown>;
87
+ data: CognitoProfile;
88
+ } | {
95
89
  user: {
96
- id: string;
97
- name?: string;
98
- email?: string | null;
99
- image?: string;
90
+ id?: never;
91
+ name: string;
92
+ email: string | null;
93
+ image: string;
100
94
  emailVerified: boolean;
101
- [key: string]: any;
102
95
  };
103
- data: any;
96
+ data: {
97
+ name: string;
98
+ sub: string;
99
+ email: string;
100
+ email_verified: boolean;
101
+ given_name?: string | undefined;
102
+ family_name?: string | undefined;
103
+ picture?: string | undefined;
104
+ username?: string | undefined;
105
+ locale?: string | undefined;
106
+ phone_number?: string | undefined;
107
+ phone_number_verified?: boolean | undefined;
108
+ aud: string | (string & string[]);
109
+ iss: string;
110
+ exp: number;
111
+ iat: number;
112
+ jti?: string;
113
+ nbf?: number;
114
+ };
104
115
  } | null>;
105
116
  options: CognitoOptions;
106
117
  };
@@ -19,6 +19,8 @@ const cognito = (options) => {
19
19
  return {
20
20
  id: "cognito",
21
21
  name: "Cognito",
22
+ accountSubject: ({ profile }) => profile.sub,
23
+ accountIssuer: `https://cognito-idp.${options.region}.amazonaws.com/${options.userPoolId}`,
22
24
  async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
23
25
  if (!getPrimaryClientId(options.clientId)) {
24
26
  logger.error("ClientId is required for Amazon Cognito. Make sure to provide them in the options.");
@@ -98,7 +100,6 @@ const cognito = (options) => {
98
100
  const userMap = await options.mapProfileToUser?.(enrichedProfile);
99
101
  return {
100
102
  user: {
101
- id: profile.sub,
102
103
  name: enrichedProfile.name,
103
104
  email: profile.email,
104
105
  image: profile.picture,
@@ -116,7 +117,6 @@ const cognito = (options) => {
116
117
  const userMap = await options.mapProfileToUser?.(userInfo);
117
118
  return {
118
119
  user: {
119
- id: userInfo.sub,
120
120
  name: userInfo.name || userInfo.given_name || userInfo.username || "",
121
121
  email: userInfo.email,
122
122
  image: userInfo.picture,
@@ -1,4 +1,5 @@
1
- import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
1
+ import { OAuth2Tokens, OAuth2UserInfo, OAuthAccountKeyContext, ProviderOptions } from "../oauth2/oauth-provider.mjs";
2
+ import "../oauth2/index.mjs";
2
3
  //#region src/social-providers/discord.d.ts
3
4
  interface DiscordProfile extends Record<string, any> {
4
5
  /** the user's id (i.e. the numerical snowflake) */
@@ -77,12 +78,8 @@ interface DiscordOptions extends ProviderOptions<DiscordProfile> {
77
78
  declare const discord: (options: DiscordOptions) => {
78
79
  id: "discord";
79
80
  name: string;
80
- createAuthorizationURL({
81
- state,
82
- scopes,
83
- redirectURI,
84
- additionalParams
85
- }: {
81
+ accountSubject: ({ profile }: OAuthAccountKeyContext<DiscordProfile>) => string;
82
+ createAuthorizationURL({ state, scopes, redirectURI, additionalParams }: {
86
83
  state: string;
87
84
  codeVerifier: string;
88
85
  scopes?: string[] | undefined;
@@ -92,10 +89,7 @@ declare const discord: (options: DiscordOptions) => {
92
89
  idTokenNonce?: string | undefined;
93
90
  additionalParams?: Record<string, string> | undefined;
94
91
  }): Promise<URL>;
95
- validateAuthorizationCode: ({
96
- code,
97
- redirectURI
98
- }: {
92
+ validateAuthorizationCode: ({ code, redirectURI }: {
99
93
  code: string;
100
94
  redirectURI: string;
101
95
  codeVerifier?: string | undefined;
@@ -112,15 +106,8 @@ declare const discord: (options: DiscordOptions) => {
112
106
  email?: string;
113
107
  } | undefined;
114
108
  }): Promise<{
115
- user: {
116
- id: string;
117
- name?: string;
118
- email?: string | null;
119
- image?: string;
120
- emailVerified: boolean;
121
- [key: string]: any;
122
- };
123
- data: any;
109
+ user: OAuth2UserInfo & Record<string, unknown>;
110
+ data: DiscordProfile;
124
111
  } | null>;
125
112
  options: DiscordOptions;
126
113
  };
@@ -8,6 +8,7 @@ const discord = (options) => {
8
8
  return {
9
9
  id: "discord",
10
10
  name: "Discord",
11
+ accountSubject: ({ profile }) => profile.id,
11
12
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
12
13
  const _scopes = options.disableDefaultScope ? [] : ["identify", "email"];
13
14
  if (scopes) _scopes.push(...scopes);
@@ -58,7 +59,6 @@ const discord = (options) => {
58
59
  const userMap = await options.mapProfileToUser?.(profile);
59
60
  return {
60
61
  user: {
61
- id: profile.id,
62
62
  name: profile.global_name || profile.username || "",
63
63
  email: profile.email,
64
64
  emailVerified: profile.verified,