@better-auth/core 1.7.0-beta.6 → 1.7.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/api/index.d.mts +3 -3
  2. package/dist/context/global.mjs +1 -1
  3. package/dist/db/get-tables.mjs +3 -3
  4. package/dist/db/schema/account.d.mts +1 -1
  5. package/dist/db/schema/account.mjs +1 -1
  6. package/dist/error/codes.d.mts +0 -5
  7. package/dist/error/codes.mjs +0 -5
  8. package/dist/instrumentation/tracer.mjs +1 -1
  9. package/dist/oauth2/create-authorization-url.d.mts +4 -5
  10. package/dist/oauth2/create-authorization-url.mjs +4 -5
  11. package/dist/oauth2/index.d.mts +3 -4
  12. package/dist/oauth2/index.mjs +2 -3
  13. package/dist/oauth2/oauth-provider.d.mts +44 -48
  14. package/dist/oauth2/refresh-access-token.mjs +17 -2
  15. package/dist/oauth2/utils.d.mts +6 -1
  16. package/dist/oauth2/utils.mjs +24 -2
  17. package/dist/oauth2/verify-id-token.d.mts +6 -5
  18. package/dist/oauth2/verify-id-token.mjs +2 -2
  19. package/dist/social-providers/apple.d.mts +3 -5
  20. package/dist/social-providers/apple.mjs +5 -5
  21. package/dist/social-providers/atlassian.d.mts +3 -5
  22. package/dist/social-providers/atlassian.mjs +4 -4
  23. package/dist/social-providers/cognito.d.mts +3 -5
  24. package/dist/social-providers/cognito.mjs +11 -18
  25. package/dist/social-providers/discord.d.mts +3 -5
  26. package/dist/social-providers/discord.mjs +6 -7
  27. package/dist/social-providers/dropbox.d.mts +3 -5
  28. package/dist/social-providers/dropbox.mjs +5 -5
  29. package/dist/social-providers/facebook.d.mts +3 -5
  30. package/dist/social-providers/facebook.mjs +5 -5
  31. package/dist/social-providers/figma.d.mts +3 -5
  32. package/dist/social-providers/figma.mjs +5 -5
  33. package/dist/social-providers/github.d.mts +3 -5
  34. package/dist/social-providers/github.mjs +4 -4
  35. package/dist/social-providers/gitlab.d.mts +3 -5
  36. package/dist/social-providers/gitlab.mjs +6 -6
  37. package/dist/social-providers/google.d.mts +10 -10
  38. package/dist/social-providers/google.mjs +12 -13
  39. package/dist/social-providers/huggingface.d.mts +3 -5
  40. package/dist/social-providers/huggingface.mjs +8 -8
  41. package/dist/social-providers/index.d.mts +105 -177
  42. package/dist/social-providers/kakao.d.mts +3 -5
  43. package/dist/social-providers/kakao.mjs +8 -8
  44. package/dist/social-providers/kick.d.mts +3 -5
  45. package/dist/social-providers/kick.mjs +4 -4
  46. package/dist/social-providers/line.d.mts +3 -5
  47. package/dist/social-providers/line.mjs +10 -10
  48. package/dist/social-providers/linear.d.mts +3 -5
  49. package/dist/social-providers/linear.mjs +4 -4
  50. package/dist/social-providers/linkedin.d.mts +3 -5
  51. package/dist/social-providers/linkedin.mjs +10 -10
  52. package/dist/social-providers/microsoft-entra-id.d.mts +3 -5
  53. package/dist/social-providers/microsoft-entra-id.mjs +10 -11
  54. package/dist/social-providers/naver.d.mts +3 -5
  55. package/dist/social-providers/naver.mjs +4 -4
  56. package/dist/social-providers/notion.d.mts +3 -5
  57. package/dist/social-providers/notion.mjs +4 -4
  58. package/dist/social-providers/paybin.d.mts +3 -5
  59. package/dist/social-providers/paybin.mjs +10 -10
  60. package/dist/social-providers/paypal.d.mts +3 -5
  61. package/dist/social-providers/paypal.mjs +2 -8
  62. package/dist/social-providers/polar.d.mts +3 -5
  63. package/dist/social-providers/polar.mjs +8 -8
  64. package/dist/social-providers/railway.d.mts +3 -5
  65. package/dist/social-providers/railway.mjs +9 -9
  66. package/dist/social-providers/reddit.d.mts +3 -5
  67. package/dist/social-providers/reddit.mjs +5 -5
  68. package/dist/social-providers/roblox.d.mts +3 -5
  69. package/dist/social-providers/roblox.mjs +5 -5
  70. package/dist/social-providers/salesforce.d.mts +3 -5
  71. package/dist/social-providers/salesforce.mjs +8 -8
  72. package/dist/social-providers/slack.d.mts +3 -5
  73. package/dist/social-providers/slack.mjs +9 -9
  74. package/dist/social-providers/spotify.d.mts +3 -5
  75. package/dist/social-providers/spotify.mjs +5 -5
  76. package/dist/social-providers/tiktok.d.mts +3 -5
  77. package/dist/social-providers/tiktok.mjs +5 -9
  78. package/dist/social-providers/twitch.d.mts +3 -5
  79. package/dist/social-providers/twitch.mjs +4 -4
  80. package/dist/social-providers/twitter.d.mts +3 -5
  81. package/dist/social-providers/twitter.mjs +9 -9
  82. package/dist/social-providers/vercel.d.mts +3 -5
  83. package/dist/social-providers/vercel.mjs +7 -4
  84. package/dist/social-providers/vk.d.mts +3 -5
  85. package/dist/social-providers/vk.mjs +5 -5
  86. package/dist/social-providers/wechat.d.mts +3 -5
  87. package/dist/social-providers/wechat.mjs +5 -9
  88. package/dist/social-providers/zoom.d.mts +3 -6
  89. package/dist/social-providers/zoom.mjs +9 -15
  90. package/dist/types/context.d.mts +6 -2
  91. package/dist/utils/host.d.mts +1 -1
  92. package/dist/utils/host.mjs +3 -0
  93. package/package.json +1 -1
  94. package/src/db/get-tables.ts +3 -8
  95. package/src/db/schema/account.ts +5 -14
  96. package/src/error/codes.ts +0 -5
  97. package/src/oauth2/create-authorization-url.ts +5 -1
  98. package/src/oauth2/index.ts +3 -12
  99. package/src/oauth2/oauth-provider.ts +46 -53
  100. package/src/oauth2/refresh-access-token.ts +30 -5
  101. package/src/oauth2/utils.ts +39 -1
  102. package/src/oauth2/verify-id-token.ts +9 -5
  103. package/src/social-providers/apple.ts +8 -13
  104. package/src/social-providers/atlassian.ts +8 -12
  105. package/src/social-providers/cognito.ts +11 -18
  106. package/src/social-providers/discord.ts +8 -19
  107. package/src/social-providers/dropbox.ts +7 -13
  108. package/src/social-providers/facebook.ts +9 -13
  109. package/src/social-providers/figma.ts +9 -13
  110. package/src/social-providers/github.ts +8 -12
  111. package/src/social-providers/gitlab.ts +8 -14
  112. package/src/social-providers/google.ts +23 -29
  113. package/src/social-providers/huggingface.ts +8 -12
  114. package/src/social-providers/kakao.ts +8 -16
  115. package/src/social-providers/kick.ts +7 -12
  116. package/src/social-providers/line.ts +10 -14
  117. package/src/social-providers/linear.ts +6 -12
  118. package/src/social-providers/linkedin.ts +10 -14
  119. package/src/social-providers/microsoft-entra-id.ts +8 -18
  120. package/src/social-providers/naver.ts +6 -12
  121. package/src/social-providers/notion.ts +6 -12
  122. package/src/social-providers/paybin.ts +11 -14
  123. package/src/social-providers/paypal.ts +8 -6
  124. package/src/social-providers/polar.ts +8 -12
  125. package/src/social-providers/railway.ts +9 -13
  126. package/src/social-providers/reddit.ts +7 -18
  127. package/src/social-providers/roblox.ts +7 -18
  128. package/src/social-providers/salesforce.ts +8 -12
  129. package/src/social-providers/slack.ts +9 -18
  130. package/src/social-providers/spotify.ts +7 -13
  131. package/src/social-providers/tiktok.ts +7 -13
  132. package/src/social-providers/twitch.ts +8 -12
  133. package/src/social-providers/twitter.ts +8 -17
  134. package/src/social-providers/vercel.ts +10 -16
  135. package/src/social-providers/vk.ts +7 -13
  136. package/src/social-providers/wechat.ts +8 -20
  137. package/src/social-providers/zoom.ts +6 -19
  138. package/src/types/context.ts +8 -2
  139. package/src/utils/host.ts +10 -1
  140. package/dist/oauth2/scopes.d.mts +0 -76
  141. package/dist/oauth2/scopes.mjs +0 -96
  142. package/src/oauth2/scopes.ts +0 -118
@@ -1,11 +1,7 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
2
  import { BetterAuthError } from "../error";
3
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
4
- import {
5
- createAuthorizationURL,
6
- resolveRequestedScopes,
7
- validateAuthorizationCode,
8
- } from "../oauth2";
3
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
4
+ import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
9
5
 
10
6
  export interface VercelProfile {
11
7
  sub: string;
@@ -20,13 +16,10 @@ export interface VercelOptions extends ProviderOptions<VercelProfile> {
20
16
  clientId: string;
21
17
  }
22
18
 
23
- const VERCEL_DEFAULT_SCOPES: string[] = [];
24
-
25
19
  export const vercel = (options: VercelOptions) => {
26
20
  return {
27
21
  id: "vercel",
28
22
  name: "Vercel",
29
- callbackPath: "/callback/vercel",
30
23
  createAuthorizationURL({
31
24
  state,
32
25
  scopes,
@@ -38,17 +31,18 @@ export const vercel = (options: VercelOptions) => {
38
31
  throw new BetterAuthError("codeVerifier is required for Vercel");
39
32
  }
40
33
 
41
- const requestedScopes = resolveRequestedScopes(
42
- options,
43
- VERCEL_DEFAULT_SCOPES,
44
- scopes,
45
- );
34
+ let _scopes: string[] | undefined = undefined;
35
+ if (options.scope !== undefined || scopes !== undefined) {
36
+ _scopes = [];
37
+ if (options.scope) _scopes.push(...options.scope);
38
+ if (scopes) _scopes.push(...scopes);
39
+ }
46
40
 
47
41
  return createAuthorizationURL({
48
42
  id: "vercel",
49
43
  options,
50
44
  authorizationEndpoint: "https://vercel.com/oauth/authorize",
51
- scopes: requestedScopes,
45
+ scopes: _scopes,
52
46
  state,
53
47
  codeVerifier,
54
48
  redirectURI,
@@ -96,5 +90,5 @@ export const vercel = (options: VercelOptions) => {
96
90
  };
97
91
  },
98
92
  options,
99
- } satisfies UpstreamProvider<VercelProfile>;
93
+ } satisfies OAuthProvider<VercelProfile>;
100
94
  };
@@ -1,9 +1,8 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
- resolveRequestedScopes,
7
6
  validateAuthorizationCode,
8
7
  } from "../oauth2";
9
8
 
@@ -26,33 +25,28 @@ export interface VkOption extends ProviderOptions {
26
25
  scheme?: ("light" | "dark") | undefined;
27
26
  }
28
27
 
29
- const VK_DEFAULT_SCOPES = ["email", "phone"];
30
-
31
28
  export const vk = (options: VkOption) => {
32
29
  const tokenEndpoint = "https://id.vk.com/oauth2/auth";
33
30
  return {
34
31
  id: "vk",
35
32
  name: "VK",
36
- callbackPath: "/callback/vk",
37
- createAuthorizationURL({
33
+ async createAuthorizationURL({
38
34
  state,
39
35
  scopes,
40
36
  codeVerifier,
41
37
  redirectURI,
42
38
  additionalParams,
43
39
  }) {
44
- const requestedScopes = resolveRequestedScopes(
45
- options,
46
- VK_DEFAULT_SCOPES,
47
- scopes,
48
- );
40
+ const _scopes = options.disableDefaultScope ? [] : ["email", "phone"];
41
+ if (options.scope) _scopes.push(...options.scope);
42
+ if (scopes) _scopes.push(...scopes);
49
43
  const authorizationEndpoint = "https://id.vk.com/authorize";
50
44
 
51
45
  return createAuthorizationURL({
52
46
  id: "vk",
53
47
  options,
54
48
  authorizationEndpoint,
55
- scopes: requestedScopes,
49
+ scopes: _scopes,
56
50
  state,
57
51
  redirectURI,
58
52
  codeVerifier,
@@ -134,5 +128,5 @@ export const vk = (options: VkOption) => {
134
128
  };
135
129
  },
136
130
  options,
137
- } satisfies UpstreamProvider<VkProfile>;
131
+ } satisfies OAuthProvider<VkProfile>;
138
132
  };
@@ -1,13 +1,6 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type {
3
- OAuth2Tokens,
4
- ProviderOptions,
5
- UpstreamProvider,
6
- } from "../oauth2";
7
- import {
8
- RESERVED_AUTHORIZATION_PARAMS_SET,
9
- resolveRequestedScopes,
10
- } from "../oauth2";
2
+ import type { OAuth2Tokens, OAuthProvider, ProviderOptions } from "../oauth2";
3
+ import { RESERVED_AUTHORIZATION_PARAMS_SET } from "../oauth2";
11
4
 
12
5
  /**
13
6
  * WeChat user profile information
@@ -62,24 +55,19 @@ export interface WeChatOptions extends ProviderOptions<WeChatProfile> {
62
55
  lang?: "cn" | "en";
63
56
  }
64
57
 
65
- const WECHAT_DEFAULT_SCOPES = ["snsapi_login"];
66
-
67
58
  export const wechat = (options: WeChatOptions) => {
68
59
  return {
69
60
  id: "wechat",
70
61
  name: "WeChat",
71
- callbackPath: "/callback/wechat",
72
62
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
73
- const requestedScopes = resolveRequestedScopes(
74
- options,
75
- WECHAT_DEFAULT_SCOPES,
76
- scopes,
77
- );
63
+ const _scopes = options.disableDefaultScope ? [] : ["snsapi_login"];
64
+ options.scope && _scopes.push(...options.scope);
65
+ scopes && _scopes.push(...scopes);
78
66
 
79
67
  // WeChat uses non-standard OAuth2 parameters (appid instead of client_id)
80
68
  // and requires a fragment (#wechat_redirect), so we construct the URL manually.
81
69
  const url = new URL("https://open.weixin.qq.com/connect/qrconnect");
82
- url.searchParams.set("scope", requestedScopes.join(","));
70
+ url.searchParams.set("scope", _scopes.join(","));
83
71
  url.searchParams.set("response_type", "code");
84
72
  url.searchParams.set("appid", options.clientId);
85
73
  url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
@@ -94,7 +82,7 @@ export const wechat = (options: WeChatOptions) => {
94
82
  }
95
83
  url.hash = "wechat_redirect";
96
84
 
97
- return { url, requestedScopes };
85
+ return url;
98
86
  },
99
87
 
100
88
  // WeChat uses non-standard token exchange (appid/secret instead of
@@ -236,5 +224,5 @@ export const wechat = (options: WeChatOptions) => {
236
224
  };
237
225
  },
238
226
  options,
239
- } satisfies UpstreamProvider<WeChatProfile, WeChatOptions>;
227
+ } satisfies OAuthProvider<WeChatProfile, WeChatOptions>;
240
228
  };
@@ -1,9 +1,8 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
- resolveRequestedScopes,
7
6
  validateAuthorizationCode,
8
7
  } from "../oauth2";
9
8
 
@@ -144,8 +143,6 @@ export interface ZoomOptions extends ProviderOptions<ZoomProfile> {
144
143
  pkce?: boolean | undefined;
145
144
  }
146
145
 
147
- const ZOOM_DEFAULT_SCOPES: string[] = [];
148
-
149
146
  export const zoom = (userOptions: ZoomOptions) => {
150
147
  const options = {
151
148
  pkce: true,
@@ -155,31 +152,21 @@ export const zoom = (userOptions: ZoomOptions) => {
155
152
  return {
156
153
  id: "zoom",
157
154
  name: "Zoom",
158
- callbackPath: "/callback/zoom",
159
- createAuthorizationURL: ({
155
+ createAuthorizationURL: async ({
160
156
  state,
161
- scopes,
162
157
  redirectURI,
163
158
  codeVerifier,
164
159
  additionalParams,
165
- }) => {
166
- const requestedScopes = resolveRequestedScopes(
167
- options,
168
- ZOOM_DEFAULT_SCOPES,
169
- scopes,
170
- );
171
-
172
- return createAuthorizationURL({
160
+ }) =>
161
+ createAuthorizationURL({
173
162
  id: "zoom",
174
163
  options,
175
164
  authorizationEndpoint: "https://zoom.us/oauth/authorize",
176
- scopes: requestedScopes,
177
165
  state,
178
166
  redirectURI,
179
167
  codeVerifier: options.pkce ? codeVerifier : undefined,
180
168
  additionalParams,
181
- });
182
- },
169
+ }),
183
170
  validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
184
171
  return validateAuthorizationCode({
185
172
  code,
@@ -235,5 +222,5 @@ export const zoom = (userOptions: ZoomOptions) => {
235
222
  },
236
223
  };
237
224
  },
238
- } satisfies UpstreamProvider<ZoomProfile>;
225
+ } satisfies OAuthProvider<ZoomProfile>;
239
226
  };
@@ -10,7 +10,7 @@ import type {
10
10
  } from "../db";
11
11
  import type { DBAdapter, Where } from "../db/adapter";
12
12
  import type { createLogger } from "../env";
13
- import type { UpstreamProvider } from "../oauth2";
13
+ import type { OAuthProvider } from "../oauth2";
14
14
  import type { BetterAuthCookie, BetterAuthCookies } from "./cookie";
15
15
  import type { Awaitable, LiteralString } from "./helper";
16
16
  import type {
@@ -88,6 +88,12 @@ export type GenericEndpointContext<
88
88
  export interface InternalAdapter<
89
89
  _Options extends BetterAuthOptions = BetterAuthOptions,
90
90
  > {
91
+ createOAuthUser(
92
+ user: Omit<User, "id" | "createdAt" | "updatedAt">,
93
+ account: Omit<Account, "userId" | "id" | "createdAt" | "updatedAt"> &
94
+ Partial<Account>,
95
+ ): Promise<{ user: User; account: Account }>;
96
+
91
97
  createUser<T extends Record<string, any>>(
92
98
  user: Omit<User, "id" | "createdAt" | "updatedAt" | "emailVerified"> &
93
99
  Partial<User> &
@@ -369,7 +375,7 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
369
375
  user: User & Record<string, any>;
370
376
  } | null,
371
377
  ) => void;
372
- socialProviders: UpstreamProvider[];
378
+ socialProviders: OAuthProvider[];
373
379
  authCookies: BetterAuthCookies;
374
380
  logger: ReturnType<typeof createLogger>;
375
381
  rateLimit: {
package/src/utils/host.ts CHANGED
@@ -49,7 +49,7 @@ export type HostKind =
49
49
  | "multicast"
50
50
  /** IPv4 limited broadcast `255.255.255.255`. */
51
51
  | "broadcast"
52
- /** Other RFC 6890 special-purpose ranges (0/8, 192.0.0/24, 240/4, 2001::/32, etc.). */
52
+ /** Other RFC 6890 special-purpose ranges (0.0.0.0/8, 192.0.0.0/24, 192.88.99.0/24, 240.0.0.0/4, 2001::/32, etc.). */
53
53
  | "reserved"
54
54
  /** Cloud metadata service FQDN (e.g. `metadata.google.internal`). */
55
55
  | "cloudMetadata"
@@ -183,6 +183,8 @@ function classifyIPv4(ip: string): HostKind {
183
183
  if (inIPv4Range(n, ipv4ToUint32("224.0.0.0"), 4)) return "multicast";
184
184
  if (inIPv4Range(n, ipv4ToUint32("0.0.0.0"), 8)) return "reserved";
185
185
  if (inIPv4Range(n, ipv4ToUint32("192.0.0.0"), 24)) return "reserved";
186
+ // 6to4 relay anycast (RFC 7526, deprecated), not globally reachable.
187
+ if (inIPv4Range(n, ipv4ToUint32("192.88.99.0"), 24)) return "reserved";
186
188
  if (inIPv4Range(n, ipv4ToUint32("240.0.0.0"), 4)) return "reserved";
187
189
 
188
190
  return "public";
@@ -231,6 +233,8 @@ function classifyIPv6(expanded: string): HostKind {
231
233
 
232
234
  if (firstByte === 0xff) return "multicast";
233
235
  if (firstByte === 0xfe && (secondByte & 0xc0) === 0x80) return "linkLocal";
236
+ // fec0::/10 — deprecated site-local (RFC 3879), not globally reachable.
237
+ if (firstByte === 0xfe && (secondByte & 0xc0) === 0xc0) return "reserved";
234
238
  if ((firstByte & 0xfe) === 0xfc) return "private";
235
239
 
236
240
  if (expanded.startsWith("2001:0db8:")) return "documentation";
@@ -270,6 +274,11 @@ function classifyIPv6(expanded: string): HostKind {
270
274
  // 5f00::/16 — SRv6 SIDs (RFC 9602), not globally reachable.
271
275
  if (expanded.startsWith("5f00:")) return "reserved";
272
276
 
277
+ // ::/96 — deprecated IPv4-compatible IPv6 (RFC 4291 §2.5.5.1). `::` and
278
+ // `::1` are matched above; the rest of the block embeds an IPv4 (e.g.
279
+ // `::127.0.0.1`) and is never a valid public target.
280
+ if (expanded.startsWith("0000:0000:0000:0000:0000:0000:")) return "reserved";
281
+
273
282
  return "public";
274
283
  }
275
284
 
@@ -1,76 +0,0 @@
1
- import { ProviderOptions } from "./oauth-provider.mjs";
2
-
3
- //#region src/oauth2/scopes.d.ts
4
- /**
5
- * Parse a provider's `scope` token-response field into a string array.
6
- *
7
- * RFC 6749 §3.3 defines `scope` as a space-delimited string, but providers
8
- * vary: some (e.g. Twitch) return an already-split array. Accept both, plus the
9
- * omitted/empty case, without ever calling `.split` on a non-string. Returns
10
- * `[]` when no scope is present.
11
- *
12
- * @see https://github.com/better-auth/better-auth/issues/9076
13
- */
14
- declare function parseScopeField(scope: unknown): string[];
15
- /**
16
- * Normalize a scope set into a single deduped, sorted array.
17
- *
18
- * Scope order is insignificant per RFC 6749 §3.3, so normalize for idempotent
19
- * writes and trivial comparisons: trim each token, drop empties, dedupe, and
20
- * sort ascending. Returns `[]` when the union is empty.
21
- *
22
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
23
- */
24
- declare function normalizeScopes(stored: string[] | null | undefined, incoming?: string[] | undefined): string[];
25
- /**
26
- * Union the stored granted-scope set with the scopes observed on an
27
- * authorization or token exchange.
28
- *
29
- * The provider's echoed `scope` is authoritative when present. RFC 6749 §3.3
30
- * and §5.1 say an omitted or empty echo means the grant equals what was
31
- * requested, so fall back to `requested` in that case. The result unions onto
32
- * the stored grant (never narrows on a normal write) and is normalized per
33
- * {@link normalizeScopes}.
34
- *
35
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
36
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
37
- */
38
- declare function unionGrantedScopes(stored: string[] | null | undefined, echoed: string[] | undefined, requested: string[] | undefined): string[];
39
- /**
40
- * Coerce a stored granted-scope value into a usable array.
41
- *
42
- * `account.grantedScopes` is nullable (legacy rows and non-OAuth accounts read
43
- * as unset), and on dialects that store the array as a JSON string a malformed
44
- * operator backfill could deserialize to a non-array. Both collapse to `[]`
45
- * here so every reader works against a real `string[]` without re-deriving the
46
- * guard.
47
- */
48
- declare function readGrantedScopes(stored: string[] | null | undefined): string[];
49
- /**
50
- * Test whether a normalized granted-scope set contains a specific scope.
51
- *
52
- * Matching is exact and case-sensitive per RFC 6749 §3.3. The argument is the
53
- * normalized `account.grantedScopes` array; a raw provider `scope` string must
54
- * be run through {@link parseScopeField} first.
55
- *
56
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
57
- */
58
- declare function includesGrantedScope(granted: string[] | null | undefined, scope: string): boolean;
59
- /**
60
- * Compose the effective scope set to encode in a single authorization URL.
61
- *
62
- * Precedence: the provider's built-in defaults (unless `disableDefaultScope`),
63
- * then the integrator's configured `options.scope`, then the per-request
64
- * `scopes`. The result is the value persisted into OAuth state as the RFC 6749
65
- * §5.1 fallback, so it is preserved verbatim (not normalized) to match what is
66
- * sent to the provider.
67
- *
68
- * `defaultScopes` is a parameter rather than a provider-contract field so the
69
- * runtime-synthesized generic OAuth provider, which has no static default set,
70
- * can pass its configured scopes here.
71
- *
72
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
73
- */
74
- declare function resolveRequestedScopes(options: Pick<ProviderOptions, "scope" | "disableDefaultScope"> | undefined, defaultScopes: string[], perRequestScopes: string[] | undefined): string[];
75
- //#endregion
76
- export { includesGrantedScope, normalizeScopes, parseScopeField, readGrantedScopes, resolveRequestedScopes, unionGrantedScopes };
@@ -1,96 +0,0 @@
1
- //#region src/oauth2/scopes.ts
2
- /**
3
- * Parse a provider's `scope` token-response field into a string array.
4
- *
5
- * RFC 6749 §3.3 defines `scope` as a space-delimited string, but providers
6
- * vary: some (e.g. Twitch) return an already-split array. Accept both, plus the
7
- * omitted/empty case, without ever calling `.split` on a non-string. Returns
8
- * `[]` when no scope is present.
9
- *
10
- * @see https://github.com/better-auth/better-auth/issues/9076
11
- */
12
- function parseScopeField(scope) {
13
- if (Array.isArray(scope)) return scope.filter((s) => typeof s === "string" && s !== "");
14
- if (typeof scope === "string") return scope.split(" ").filter(Boolean);
15
- return [];
16
- }
17
- /**
18
- * Normalize a scope set into a single deduped, sorted array.
19
- *
20
- * Scope order is insignificant per RFC 6749 §3.3, so normalize for idempotent
21
- * writes and trivial comparisons: trim each token, drop empties, dedupe, and
22
- * sort ascending. Returns `[]` when the union is empty.
23
- *
24
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
25
- */
26
- function normalizeScopes(stored, incoming) {
27
- const normalized = /* @__PURE__ */ new Set();
28
- for (const scope of [...stored ?? [], ...incoming ?? []]) {
29
- const trimmed = scope.trim();
30
- if (trimmed) normalized.add(trimmed);
31
- }
32
- return [...normalized].sort();
33
- }
34
- /**
35
- * Union the stored granted-scope set with the scopes observed on an
36
- * authorization or token exchange.
37
- *
38
- * The provider's echoed `scope` is authoritative when present. RFC 6749 §3.3
39
- * and §5.1 say an omitted or empty echo means the grant equals what was
40
- * requested, so fall back to `requested` in that case. The result unions onto
41
- * the stored grant (never narrows on a normal write) and is normalized per
42
- * {@link normalizeScopes}.
43
- *
44
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
45
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
46
- */
47
- function unionGrantedScopes(stored, echoed, requested) {
48
- return normalizeScopes(stored, echoed?.length ? echoed : requested);
49
- }
50
- /**
51
- * Coerce a stored granted-scope value into a usable array.
52
- *
53
- * `account.grantedScopes` is nullable (legacy rows and non-OAuth accounts read
54
- * as unset), and on dialects that store the array as a JSON string a malformed
55
- * operator backfill could deserialize to a non-array. Both collapse to `[]`
56
- * here so every reader works against a real `string[]` without re-deriving the
57
- * guard.
58
- */
59
- function readGrantedScopes(stored) {
60
- return Array.isArray(stored) ? stored : [];
61
- }
62
- /**
63
- * Test whether a normalized granted-scope set contains a specific scope.
64
- *
65
- * Matching is exact and case-sensitive per RFC 6749 §3.3. The argument is the
66
- * normalized `account.grantedScopes` array; a raw provider `scope` string must
67
- * be run through {@link parseScopeField} first.
68
- *
69
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
70
- */
71
- function includesGrantedScope(granted, scope) {
72
- return granted?.includes(scope) ?? false;
73
- }
74
- /**
75
- * Compose the effective scope set to encode in a single authorization URL.
76
- *
77
- * Precedence: the provider's built-in defaults (unless `disableDefaultScope`),
78
- * then the integrator's configured `options.scope`, then the per-request
79
- * `scopes`. The result is the value persisted into OAuth state as the RFC 6749
80
- * §5.1 fallback, so it is preserved verbatim (not normalized) to match what is
81
- * sent to the provider.
82
- *
83
- * `defaultScopes` is a parameter rather than a provider-contract field so the
84
- * runtime-synthesized generic OAuth provider, which has no static default set,
85
- * can pass its configured scopes here.
86
- *
87
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
88
- */
89
- function resolveRequestedScopes(options, defaultScopes, perRequestScopes) {
90
- const scopes = options?.disableDefaultScope ? [] : [...defaultScopes];
91
- if (options?.scope) scopes.push(...options.scope);
92
- if (perRequestScopes) scopes.push(...perRequestScopes);
93
- return scopes;
94
- }
95
- //#endregion
96
- export { includesGrantedScope, normalizeScopes, parseScopeField, readGrantedScopes, resolveRequestedScopes, unionGrantedScopes };
@@ -1,118 +0,0 @@
1
- import type { ProviderOptions } from "./oauth-provider";
2
-
3
- /**
4
- * Parse a provider's `scope` token-response field into a string array.
5
- *
6
- * RFC 6749 §3.3 defines `scope` as a space-delimited string, but providers
7
- * vary: some (e.g. Twitch) return an already-split array. Accept both, plus the
8
- * omitted/empty case, without ever calling `.split` on a non-string. Returns
9
- * `[]` when no scope is present.
10
- *
11
- * @see https://github.com/better-auth/better-auth/issues/9076
12
- */
13
- export function parseScopeField(scope: unknown): string[] {
14
- if (Array.isArray(scope))
15
- return scope.filter((s): s is string => typeof s === "string" && s !== "");
16
- if (typeof scope === "string") return scope.split(" ").filter(Boolean);
17
- return [];
18
- }
19
-
20
- /**
21
- * Normalize a scope set into a single deduped, sorted array.
22
- *
23
- * Scope order is insignificant per RFC 6749 §3.3, so normalize for idempotent
24
- * writes and trivial comparisons: trim each token, drop empties, dedupe, and
25
- * sort ascending. Returns `[]` when the union is empty.
26
- *
27
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
28
- */
29
- export function normalizeScopes(
30
- stored: string[] | null | undefined,
31
- incoming?: string[] | undefined,
32
- ): string[] {
33
- const normalized = new Set<string>();
34
- for (const scope of [...(stored ?? []), ...(incoming ?? [])]) {
35
- const trimmed = scope.trim();
36
- if (trimmed) normalized.add(trimmed);
37
- }
38
- return [...normalized].sort();
39
- }
40
-
41
- /**
42
- * Union the stored granted-scope set with the scopes observed on an
43
- * authorization or token exchange.
44
- *
45
- * The provider's echoed `scope` is authoritative when present. RFC 6749 §3.3
46
- * and §5.1 say an omitted or empty echo means the grant equals what was
47
- * requested, so fall back to `requested` in that case. The result unions onto
48
- * the stored grant (never narrows on a normal write) and is normalized per
49
- * {@link normalizeScopes}.
50
- *
51
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
52
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
53
- */
54
- export function unionGrantedScopes(
55
- stored: string[] | null | undefined,
56
- echoed: string[] | undefined,
57
- requested: string[] | undefined,
58
- ): string[] {
59
- const granted = echoed?.length ? echoed : requested;
60
- return normalizeScopes(stored, granted);
61
- }
62
-
63
- /**
64
- * Coerce a stored granted-scope value into a usable array.
65
- *
66
- * `account.grantedScopes` is nullable (legacy rows and non-OAuth accounts read
67
- * as unset), and on dialects that store the array as a JSON string a malformed
68
- * operator backfill could deserialize to a non-array. Both collapse to `[]`
69
- * here so every reader works against a real `string[]` without re-deriving the
70
- * guard.
71
- */
72
- export function readGrantedScopes(
73
- stored: string[] | null | undefined,
74
- ): string[] {
75
- return Array.isArray(stored) ? stored : [];
76
- }
77
-
78
- /**
79
- * Test whether a normalized granted-scope set contains a specific scope.
80
- *
81
- * Matching is exact and case-sensitive per RFC 6749 §3.3. The argument is the
82
- * normalized `account.grantedScopes` array; a raw provider `scope` string must
83
- * be run through {@link parseScopeField} first.
84
- *
85
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
86
- */
87
- export function includesGrantedScope(
88
- granted: string[] | null | undefined,
89
- scope: string,
90
- ): boolean {
91
- return granted?.includes(scope) ?? false;
92
- }
93
-
94
- /**
95
- * Compose the effective scope set to encode in a single authorization URL.
96
- *
97
- * Precedence: the provider's built-in defaults (unless `disableDefaultScope`),
98
- * then the integrator's configured `options.scope`, then the per-request
99
- * `scopes`. The result is the value persisted into OAuth state as the RFC 6749
100
- * §5.1 fallback, so it is preserved verbatim (not normalized) to match what is
101
- * sent to the provider.
102
- *
103
- * `defaultScopes` is a parameter rather than a provider-contract field so the
104
- * runtime-synthesized generic OAuth provider, which has no static default set,
105
- * can pass its configured scopes here.
106
- *
107
- * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
108
- */
109
- export function resolveRequestedScopes(
110
- options: Pick<ProviderOptions, "scope" | "disableDefaultScope"> | undefined,
111
- defaultScopes: string[],
112
- perRequestScopes: string[] | undefined,
113
- ): string[] {
114
- const scopes = options?.disableDefaultScope ? [] : [...defaultScopes];
115
- if (options?.scope) scopes.push(...options.scope);
116
- if (perRequestScopes) scopes.push(...perRequestScopes);
117
- return scopes;
118
- }