@better-auth/core 1.7.0-beta.3 → 1.7.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/dist/api/index.d.mts +3 -3
  2. package/dist/context/global.mjs +1 -1
  3. package/dist/db/adapter/factory.mjs +62 -0
  4. package/dist/db/adapter/index.d.mts +35 -1
  5. package/dist/db/adapter/types.d.mts +1 -1
  6. package/dist/db/get-tables.mjs +3 -3
  7. package/dist/db/schema/account.d.mts +1 -1
  8. package/dist/db/schema/account.mjs +1 -1
  9. package/dist/db/type.d.mts +12 -0
  10. package/dist/env/env-impl.mjs +1 -1
  11. package/dist/error/codes.d.mts +6 -0
  12. package/dist/error/codes.mjs +6 -0
  13. package/dist/index.d.mts +2 -2
  14. package/dist/instrumentation/tracer.mjs +1 -1
  15. package/dist/oauth2/authorization-params.d.mts +12 -0
  16. package/dist/oauth2/authorization-params.mjs +12 -0
  17. package/dist/oauth2/basic-credentials.d.mts +30 -0
  18. package/dist/oauth2/basic-credentials.mjs +64 -0
  19. package/dist/oauth2/client-assertion.d.mts +38 -22
  20. package/dist/oauth2/client-assertion.mjs +63 -28
  21. package/dist/oauth2/client-credentials-token.d.mts +19 -40
  22. package/dist/oauth2/client-credentials-token.mjs +18 -29
  23. package/dist/oauth2/create-authorization-url.d.mts +13 -2
  24. package/dist/oauth2/create-authorization-url.mjs +28 -7
  25. package/dist/oauth2/index.d.mts +13 -8
  26. package/dist/oauth2/index.mjs +11 -7
  27. package/dist/oauth2/oauth-provider.d.mts +149 -11
  28. package/dist/oauth2/refresh-access-token.d.mts +20 -40
  29. package/dist/oauth2/refresh-access-token.mjs +20 -33
  30. package/dist/oauth2/scopes.d.mts +76 -0
  31. package/dist/oauth2/scopes.mjs +96 -0
  32. package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
  33. package/dist/oauth2/token-endpoint-auth.mjs +89 -0
  34. package/dist/oauth2/utils.d.mts +9 -1
  35. package/dist/oauth2/utils.mjs +14 -2
  36. package/dist/oauth2/validate-authorization-code.d.mts +17 -52
  37. package/dist/oauth2/validate-authorization-code.mjs +17 -30
  38. package/dist/oauth2/verify-id-token.d.mts +26 -0
  39. package/dist/oauth2/verify-id-token.mjs +62 -0
  40. package/dist/oauth2/verify.d.mts +14 -0
  41. package/dist/oauth2/verify.mjs +38 -12
  42. package/dist/social-providers/apple.d.mts +18 -20
  43. package/dist/social-providers/apple.mjs +15 -28
  44. package/dist/social-providers/atlassian.d.mts +8 -2
  45. package/dist/social-providers/atlassian.mjs +9 -6
  46. package/dist/social-providers/cognito.d.mts +29 -3
  47. package/dist/social-providers/cognito.mjs +30 -34
  48. package/dist/social-providers/discord.d.mts +8 -2
  49. package/dist/social-providers/discord.mjs +20 -6
  50. package/dist/social-providers/dropbox.d.mts +8 -2
  51. package/dist/social-providers/dropbox.mjs +10 -9
  52. package/dist/social-providers/facebook.d.mts +24 -3
  53. package/dist/social-providers/facebook.mjs +51 -24
  54. package/dist/social-providers/figma.d.mts +8 -2
  55. package/dist/social-providers/figma.mjs +8 -7
  56. package/dist/social-providers/github.d.mts +8 -2
  57. package/dist/social-providers/github.mjs +9 -8
  58. package/dist/social-providers/gitlab.d.mts +8 -2
  59. package/dist/social-providers/gitlab.mjs +8 -7
  60. package/dist/social-providers/google.d.mts +32 -4
  61. package/dist/social-providers/google.mjs +26 -29
  62. package/dist/social-providers/huggingface.d.mts +8 -2
  63. package/dist/social-providers/huggingface.mjs +11 -10
  64. package/dist/social-providers/index.d.mts +322 -75
  65. package/dist/social-providers/kakao.d.mts +8 -2
  66. package/dist/social-providers/kakao.mjs +11 -10
  67. package/dist/social-providers/kick.d.mts +8 -2
  68. package/dist/social-providers/kick.mjs +7 -6
  69. package/dist/social-providers/line.d.mts +11 -3
  70. package/dist/social-providers/line.mjs +14 -15
  71. package/dist/social-providers/linear.d.mts +8 -2
  72. package/dist/social-providers/linear.mjs +7 -6
  73. package/dist/social-providers/linkedin.d.mts +8 -2
  74. package/dist/social-providers/linkedin.mjs +12 -11
  75. package/dist/social-providers/microsoft-entra-id.d.mts +33 -7
  76. package/dist/social-providers/microsoft-entra-id.mjs +28 -38
  77. package/dist/social-providers/naver.d.mts +8 -2
  78. package/dist/social-providers/naver.mjs +7 -6
  79. package/dist/social-providers/notion.d.mts +8 -2
  80. package/dist/social-providers/notion.mjs +9 -6
  81. package/dist/social-providers/paybin.d.mts +8 -2
  82. package/dist/social-providers/paybin.mjs +12 -11
  83. package/dist/social-providers/paypal.d.mts +8 -3
  84. package/dist/social-providers/paypal.mjs +10 -14
  85. package/dist/social-providers/polar.d.mts +8 -2
  86. package/dist/social-providers/polar.mjs +11 -10
  87. package/dist/social-providers/railway.d.mts +8 -2
  88. package/dist/social-providers/railway.mjs +11 -10
  89. package/dist/social-providers/reddit.d.mts +8 -2
  90. package/dist/social-providers/reddit.mjs +11 -9
  91. package/dist/social-providers/roblox.d.mts +8 -2
  92. package/dist/social-providers/roblox.mjs +15 -5
  93. package/dist/social-providers/salesforce.d.mts +8 -2
  94. package/dist/social-providers/salesforce.mjs +11 -10
  95. package/dist/social-providers/slack.d.mts +8 -2
  96. package/dist/social-providers/slack.mjs +18 -15
  97. package/dist/social-providers/spotify.d.mts +8 -2
  98. package/dist/social-providers/spotify.mjs +7 -6
  99. package/dist/social-providers/tiktok.d.mts +8 -2
  100. package/dist/social-providers/tiktok.mjs +21 -5
  101. package/dist/social-providers/twitch.d.mts +8 -2
  102. package/dist/social-providers/twitch.mjs +7 -6
  103. package/dist/social-providers/twitter.d.mts +7 -2
  104. package/dist/social-providers/twitter.mjs +11 -10
  105. package/dist/social-providers/vercel.d.mts +8 -2
  106. package/dist/social-providers/vercel.mjs +7 -9
  107. package/dist/social-providers/vk.d.mts +8 -2
  108. package/dist/social-providers/vk.mjs +7 -6
  109. package/dist/social-providers/wechat.d.mts +8 -2
  110. package/dist/social-providers/wechat.mjs +16 -6
  111. package/dist/social-providers/zoom.d.mts +10 -3
  112. package/dist/social-providers/zoom.mjs +14 -15
  113. package/dist/types/context.d.mts +33 -11
  114. package/dist/types/index.d.mts +1 -1
  115. package/dist/types/init-options.d.mts +121 -6
  116. package/dist/utils/ip.d.mts +5 -4
  117. package/dist/utils/ip.mjs +3 -3
  118. package/dist/utils/redirect-uri.d.mts +20 -0
  119. package/dist/utils/redirect-uri.mjs +48 -0
  120. package/dist/utils/string.d.mts +5 -1
  121. package/dist/utils/string.mjs +20 -1
  122. package/dist/utils/url.d.mts +18 -1
  123. package/dist/utils/url.mjs +30 -1
  124. package/package.json +13 -12
  125. package/src/db/adapter/factory.ts +126 -0
  126. package/src/db/adapter/index.ts +32 -0
  127. package/src/db/adapter/types.ts +1 -0
  128. package/src/db/get-tables.ts +8 -3
  129. package/src/db/schema/account.ts +14 -2
  130. package/src/db/type.ts +12 -0
  131. package/src/env/env-impl.ts +1 -2
  132. package/src/error/codes.ts +6 -0
  133. package/src/oauth2/authorization-params.ts +28 -0
  134. package/src/oauth2/basic-credentials.ts +87 -0
  135. package/src/oauth2/client-assertion.ts +131 -58
  136. package/src/oauth2/client-credentials-token.ts +48 -72
  137. package/src/oauth2/create-authorization-url.ts +30 -8
  138. package/src/oauth2/index.ts +42 -10
  139. package/src/oauth2/oauth-provider.ts +161 -12
  140. package/src/oauth2/refresh-access-token.ts +52 -78
  141. package/src/oauth2/scopes.ts +118 -0
  142. package/src/oauth2/token-endpoint-auth.ts +221 -0
  143. package/src/oauth2/utils.ts +21 -5
  144. package/src/oauth2/validate-authorization-code.ts +55 -85
  145. package/src/oauth2/verify-id-token.ts +111 -0
  146. package/src/oauth2/verify.ts +82 -15
  147. package/src/social-providers/apple.ts +32 -45
  148. package/src/social-providers/atlassian.ts +20 -9
  149. package/src/social-providers/cognito.ts +51 -48
  150. package/src/social-providers/discord.ts +37 -22
  151. package/src/social-providers/dropbox.ts +20 -12
  152. package/src/social-providers/facebook.ts +108 -57
  153. package/src/social-providers/figma.ts +21 -10
  154. package/src/social-providers/github.ts +16 -10
  155. package/src/social-providers/gitlab.ts +16 -8
  156. package/src/social-providers/google.ts +67 -46
  157. package/src/social-providers/huggingface.ts +20 -9
  158. package/src/social-providers/kakao.ts +18 -9
  159. package/src/social-providers/kick.ts +20 -8
  160. package/src/social-providers/line.ts +39 -37
  161. package/src/social-providers/linear.ts +20 -7
  162. package/src/social-providers/linkedin.ts +16 -10
  163. package/src/social-providers/microsoft-entra-id.ts +66 -64
  164. package/src/social-providers/naver.ts +14 -7
  165. package/src/social-providers/notion.ts +20 -7
  166. package/src/social-providers/paybin.ts +16 -11
  167. package/src/social-providers/paypal.ts +12 -25
  168. package/src/social-providers/polar.ts +20 -9
  169. package/src/social-providers/railway.ts +20 -9
  170. package/src/social-providers/reddit.ts +22 -10
  171. package/src/social-providers/roblox.ts +31 -15
  172. package/src/social-providers/salesforce.ts +21 -10
  173. package/src/social-providers/slack.ts +31 -16
  174. package/src/social-providers/spotify.ts +20 -7
  175. package/src/social-providers/tiktok.ts +32 -13
  176. package/src/social-providers/twitch.ts +14 -9
  177. package/src/social-providers/twitter.ts +18 -8
  178. package/src/social-providers/vercel.ts +24 -11
  179. package/src/social-providers/vk.ts +20 -7
  180. package/src/social-providers/wechat.ts +28 -8
  181. package/src/social-providers/zoom.ts +28 -19
  182. package/src/types/context.ts +33 -12
  183. package/src/types/index.ts +7 -0
  184. package/src/types/init-options.ts +148 -5
  185. package/src/utils/ip.ts +12 -13
  186. package/src/utils/redirect-uri.ts +54 -0
  187. package/src/utils/string.ts +37 -0
  188. package/src/utils/url.ts +28 -0
@@ -1,6 +1,11 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
- import { refreshAccessToken, validateAuthorizationCode } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
+ import {
4
+ RESERVED_AUTHORIZATION_PARAMS_SET,
5
+ refreshAccessToken,
6
+ resolveRequestedScopes,
7
+ validateAuthorizationCode,
8
+ } from "../oauth2";
4
9
 
5
10
  /**
6
11
  * [More info](https://developers.tiktok.com/doc/tiktok-api-v2-get-user-info/)
@@ -126,22 +131,36 @@ export interface TiktokOptions extends ProviderOptions {
126
131
  clientKey: string;
127
132
  }
128
133
 
134
+ const TIKTOK_DEFAULT_SCOPES = ["user.info.profile"];
135
+
129
136
  export const tiktok = (options: TiktokOptions) => {
130
137
  const tokenEndpoint = "https://open.tiktokapis.com/v2/oauth/token/";
131
138
  return {
132
139
  id: "tiktok",
133
140
  name: "TikTok",
134
- createAuthorizationURL({ state, scopes, redirectURI }) {
135
- const _scopes = options.disableDefaultScope ? [] : ["user.info.profile"];
136
- if (options.scope) _scopes.push(...options.scope);
137
- if (scopes) _scopes.push(...scopes);
138
- return new URL(
139
- `https://www.tiktok.com/v2/auth/authorize?scope=${_scopes.join(
140
- ",",
141
- )}&response_type=code&client_key=${options.clientKey}&redirect_uri=${encodeURIComponent(
142
- options.redirectURI || redirectURI,
143
- )}&state=${state}`,
141
+ callbackPath: "/callback/tiktok",
142
+ createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
143
+ const requestedScopes = resolveRequestedScopes(
144
+ options,
145
+ TIKTOK_DEFAULT_SCOPES,
146
+ scopes,
144
147
  );
148
+ // TikTok uses `client_key` instead of the standard `client_id`, so the
149
+ // shared createAuthorizationURL helper cannot be used directly.
150
+ const url = new URL("https://www.tiktok.com/v2/auth/authorize");
151
+ url.searchParams.set("scope", requestedScopes.join(","));
152
+ url.searchParams.set("response_type", "code");
153
+ url.searchParams.set("client_key", options.clientKey);
154
+ url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
155
+ url.searchParams.set("state", state);
156
+ if (additionalParams) {
157
+ for (const [key, value] of Object.entries(additionalParams)) {
158
+ if (RESERVED_AUTHORIZATION_PARAMS_SET.has(key)) continue;
159
+ if (key === "client_key") continue;
160
+ url.searchParams.set(key, value);
161
+ }
162
+ }
163
+ return { url, requestedScopes };
145
164
  },
146
165
 
147
166
  validateAuthorizationCode: async ({ code, redirectURI }) => {
@@ -207,5 +226,5 @@ export const tiktok = (options: TiktokOptions) => {
207
226
  };
208
227
  },
209
228
  options,
210
- } satisfies OAuthProvider<TiktokProfile, TiktokOptions>;
229
+ } satisfies UpstreamProvider<TiktokProfile, TiktokOptions>;
211
230
  };
@@ -1,9 +1,10 @@
1
1
  import { decodeJwt } from "jose";
2
2
  import { logger } from "../env";
3
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
4
4
  import {
5
5
  createAuthorizationURL,
6
6
  refreshAccessToken,
7
+ resolveRequestedScopes,
7
8
  validateAuthorizationCode,
8
9
  } from "../oauth2";
9
10
 
@@ -37,23 +38,26 @@ export interface TwitchOptions extends ProviderOptions<TwitchProfile> {
37
38
  clientId: string;
38
39
  claims?: string[] | undefined;
39
40
  }
41
+ const TWITCH_DEFAULT_SCOPES = ["user:read:email", "openid"];
42
+
40
43
  export const twitch = (options: TwitchOptions) => {
41
44
  const tokenEndpoint = "https://id.twitch.tv/oauth2/token";
42
45
  return {
43
46
  id: "twitch",
44
47
  name: "Twitch",
45
- createAuthorizationURL({ state, scopes, redirectURI }) {
46
- const _scopes = options.disableDefaultScope
47
- ? []
48
- : ["user:read:email", "openid"];
49
- if (options.scope) _scopes.push(...options.scope);
50
- if (scopes) _scopes.push(...scopes);
48
+ callbackPath: "/callback/twitch",
49
+ createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
50
+ const requestedScopes = resolveRequestedScopes(
51
+ options,
52
+ TWITCH_DEFAULT_SCOPES,
53
+ scopes,
54
+ );
51
55
  return createAuthorizationURL({
52
56
  id: "twitch",
53
57
  redirectURI,
54
58
  options,
55
59
  authorizationEndpoint: "https://id.twitch.tv/oauth2/authorize",
56
- scopes: _scopes,
60
+ scopes: requestedScopes,
57
61
  state,
58
62
  claims: options.claims || [
59
63
  "email",
@@ -61,6 +65,7 @@ export const twitch = (options: TwitchOptions) => {
61
65
  "preferred_username",
62
66
  "picture",
63
67
  ],
68
+ additionalParams,
64
69
  });
65
70
  },
66
71
  validateAuthorizationCode: async ({ code, redirectURI }) => {
@@ -108,5 +113,5 @@ export const twitch = (options: TwitchOptions) => {
108
113
  };
109
114
  },
110
115
  options,
111
- } satisfies OAuthProvider<TwitchProfile>;
116
+ } satisfies UpstreamProvider<TwitchProfile>;
112
117
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -103,25 +104,34 @@ export interface TwitterOption extends ProviderOptions<TwitterProfile> {
103
104
  clientId: string;
104
105
  }
105
106
 
107
+ const TWITTER_DEFAULT_SCOPES = [
108
+ "users.read",
109
+ "tweet.read",
110
+ "offline.access",
111
+ "users.email",
112
+ ];
113
+
106
114
  export const twitter = (options: TwitterOption) => {
107
115
  const tokenEndpoint = "https://api.x.com/2/oauth2/token";
108
116
  return {
109
117
  id: "twitter",
110
118
  name: "Twitter",
119
+ callbackPath: "/callback/twitter",
111
120
  createAuthorizationURL(data) {
112
- const _scopes = options.disableDefaultScope
113
- ? []
114
- : ["users.read", "tweet.read", "offline.access", "users.email"];
115
- if (options.scope) _scopes.push(...options.scope);
116
- if (data.scopes) _scopes.push(...data.scopes);
121
+ const requestedScopes = resolveRequestedScopes(
122
+ options,
123
+ TWITTER_DEFAULT_SCOPES,
124
+ data.scopes,
125
+ );
117
126
  return createAuthorizationURL({
118
127
  id: "twitter",
119
128
  options,
120
129
  authorizationEndpoint: "https://x.com/i/oauth2/authorize",
121
- scopes: _scopes,
130
+ scopes: requestedScopes,
122
131
  state: data.state,
123
132
  codeVerifier: data.codeVerifier,
124
133
  redirectURI: data.redirectURI,
134
+ additionalParams: data.additionalParams,
125
135
  });
126
136
  },
127
137
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
@@ -195,5 +205,5 @@ export const twitter = (options: TwitterOption) => {
195
205
  };
196
206
  },
197
207
  options,
198
- } satisfies OAuthProvider<TwitterProfile>;
208
+ } satisfies UpstreamProvider<TwitterProfile>;
199
209
  };
@@ -1,7 +1,11 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
2
  import { BetterAuthError } from "../error";
3
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
4
- import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
3
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
4
+ import {
5
+ createAuthorizationURL,
6
+ resolveRequestedScopes,
7
+ validateAuthorizationCode,
8
+ } from "../oauth2";
5
9
 
6
10
  export interface VercelProfile {
7
11
  sub: string;
@@ -16,30 +20,39 @@ export interface VercelOptions extends ProviderOptions<VercelProfile> {
16
20
  clientId: string;
17
21
  }
18
22
 
23
+ const VERCEL_DEFAULT_SCOPES: string[] = [];
24
+
19
25
  export const vercel = (options: VercelOptions) => {
20
26
  return {
21
27
  id: "vercel",
22
28
  name: "Vercel",
23
- createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
29
+ callbackPath: "/callback/vercel",
30
+ createAuthorizationURL({
31
+ state,
32
+ scopes,
33
+ codeVerifier,
34
+ redirectURI,
35
+ additionalParams,
36
+ }) {
24
37
  if (!codeVerifier) {
25
38
  throw new BetterAuthError("codeVerifier is required for Vercel");
26
39
  }
27
40
 
28
- let _scopes: string[] | undefined = undefined;
29
- if (options.scope !== undefined || scopes !== undefined) {
30
- _scopes = [];
31
- if (options.scope) _scopes.push(...options.scope);
32
- if (scopes) _scopes.push(...scopes);
33
- }
41
+ const requestedScopes = resolveRequestedScopes(
42
+ options,
43
+ VERCEL_DEFAULT_SCOPES,
44
+ scopes,
45
+ );
34
46
 
35
47
  return createAuthorizationURL({
36
48
  id: "vercel",
37
49
  options,
38
50
  authorizationEndpoint: "https://vercel.com/oauth/authorize",
39
- scopes: _scopes,
51
+ scopes: requestedScopes,
40
52
  state,
41
53
  codeVerifier,
42
54
  redirectURI,
55
+ additionalParams,
43
56
  });
44
57
  },
45
58
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
@@ -83,5 +96,5 @@ export const vercel = (options: VercelOptions) => {
83
96
  };
84
97
  },
85
98
  options,
86
- } satisfies OAuthProvider<VercelProfile>;
99
+ } satisfies UpstreamProvider<VercelProfile>;
87
100
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -25,25 +26,37 @@ export interface VkOption extends ProviderOptions {
25
26
  scheme?: ("light" | "dark") | undefined;
26
27
  }
27
28
 
29
+ const VK_DEFAULT_SCOPES = ["email", "phone"];
30
+
28
31
  export const vk = (options: VkOption) => {
29
32
  const tokenEndpoint = "https://id.vk.com/oauth2/auth";
30
33
  return {
31
34
  id: "vk",
32
35
  name: "VK",
33
- async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
34
- const _scopes = options.disableDefaultScope ? [] : ["email", "phone"];
35
- if (options.scope) _scopes.push(...options.scope);
36
- if (scopes) _scopes.push(...scopes);
36
+ callbackPath: "/callback/vk",
37
+ createAuthorizationURL({
38
+ state,
39
+ scopes,
40
+ codeVerifier,
41
+ redirectURI,
42
+ additionalParams,
43
+ }) {
44
+ const requestedScopes = resolveRequestedScopes(
45
+ options,
46
+ VK_DEFAULT_SCOPES,
47
+ scopes,
48
+ );
37
49
  const authorizationEndpoint = "https://id.vk.com/authorize";
38
50
 
39
51
  return createAuthorizationURL({
40
52
  id: "vk",
41
53
  options,
42
54
  authorizationEndpoint,
43
- scopes: _scopes,
55
+ scopes: requestedScopes,
44
56
  state,
45
57
  redirectURI,
46
58
  codeVerifier,
59
+ additionalParams,
47
60
  });
48
61
  },
49
62
  validateAuthorizationCode: async ({
@@ -121,5 +134,5 @@ export const vk = (options: VkOption) => {
121
134
  };
122
135
  },
123
136
  options,
124
- } satisfies OAuthProvider<VkProfile>;
137
+ } satisfies UpstreamProvider<VkProfile>;
125
138
  };
@@ -1,5 +1,13 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuth2Tokens, OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type {
3
+ OAuth2Tokens,
4
+ ProviderOptions,
5
+ UpstreamProvider,
6
+ } from "../oauth2";
7
+ import {
8
+ RESERVED_AUTHORIZATION_PARAMS_SET,
9
+ resolveRequestedScopes,
10
+ } from "../oauth2";
3
11
 
4
12
  /**
5
13
  * WeChat user profile information
@@ -54,27 +62,39 @@ export interface WeChatOptions extends ProviderOptions<WeChatProfile> {
54
62
  lang?: "cn" | "en";
55
63
  }
56
64
 
65
+ const WECHAT_DEFAULT_SCOPES = ["snsapi_login"];
66
+
57
67
  export const wechat = (options: WeChatOptions) => {
58
68
  return {
59
69
  id: "wechat",
60
70
  name: "WeChat",
61
- createAuthorizationURL({ state, scopes, redirectURI }) {
62
- const _scopes = options.disableDefaultScope ? [] : ["snsapi_login"];
63
- options.scope && _scopes.push(...options.scope);
64
- scopes && _scopes.push(...scopes);
71
+ callbackPath: "/callback/wechat",
72
+ createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
73
+ const requestedScopes = resolveRequestedScopes(
74
+ options,
75
+ WECHAT_DEFAULT_SCOPES,
76
+ scopes,
77
+ );
65
78
 
66
79
  // WeChat uses non-standard OAuth2 parameters (appid instead of client_id)
67
80
  // and requires a fragment (#wechat_redirect), so we construct the URL manually.
68
81
  const url = new URL("https://open.weixin.qq.com/connect/qrconnect");
69
- url.searchParams.set("scope", _scopes.join(","));
82
+ url.searchParams.set("scope", requestedScopes.join(","));
70
83
  url.searchParams.set("response_type", "code");
71
84
  url.searchParams.set("appid", options.clientId);
72
85
  url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
73
86
  url.searchParams.set("state", state);
74
87
  url.searchParams.set("lang", options.lang || "cn");
88
+ if (additionalParams) {
89
+ for (const [key, value] of Object.entries(additionalParams)) {
90
+ if (RESERVED_AUTHORIZATION_PARAMS_SET.has(key)) continue;
91
+ if (key === "appid") continue;
92
+ url.searchParams.set(key, value);
93
+ }
94
+ }
75
95
  url.hash = "wechat_redirect";
76
96
 
77
- return url;
97
+ return { url, requestedScopes };
78
98
  },
79
99
 
80
100
  // WeChat uses non-standard token exchange (appid/secret instead of
@@ -209,5 +229,5 @@ export const wechat = (options: WeChatOptions) => {
209
229
  };
210
230
  },
211
231
  options,
212
- } satisfies OAuthProvider<WeChatProfile, WeChatOptions>;
232
+ } satisfies UpstreamProvider<WeChatProfile, WeChatOptions>;
213
233
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
- generateCodeChallenge,
4
+ createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -143,6 +144,8 @@ export interface ZoomOptions extends ProviderOptions<ZoomProfile> {
143
144
  pkce?: boolean | undefined;
144
145
  }
145
146
 
147
+ const ZOOM_DEFAULT_SCOPES: string[] = [];
148
+
146
149
  export const zoom = (userOptions: ZoomOptions) => {
147
150
  const options = {
148
151
  pkce: true,
@@ -152,24 +155,30 @@ export const zoom = (userOptions: ZoomOptions) => {
152
155
  return {
153
156
  id: "zoom",
154
157
  name: "Zoom",
155
- createAuthorizationURL: async ({ state, redirectURI, codeVerifier }) => {
156
- const params = new URLSearchParams({
157
- response_type: "code",
158
- redirect_uri: options.redirectURI ? options.redirectURI : redirectURI,
159
- client_id: options.clientId,
158
+ callbackPath: "/callback/zoom",
159
+ createAuthorizationURL: ({
160
+ state,
161
+ scopes,
162
+ redirectURI,
163
+ codeVerifier,
164
+ additionalParams,
165
+ }) => {
166
+ const requestedScopes = resolveRequestedScopes(
167
+ options,
168
+ ZOOM_DEFAULT_SCOPES,
169
+ scopes,
170
+ );
171
+
172
+ return createAuthorizationURL({
173
+ id: "zoom",
174
+ options,
175
+ authorizationEndpoint: "https://zoom.us/oauth/authorize",
176
+ scopes: requestedScopes,
160
177
  state,
178
+ redirectURI,
179
+ codeVerifier: options.pkce ? codeVerifier : undefined,
180
+ additionalParams,
161
181
  });
162
-
163
- if (options.pkce) {
164
- const codeChallenge = await generateCodeChallenge(codeVerifier);
165
- params.set("code_challenge_method", "S256");
166
- params.set("code_challenge", codeChallenge);
167
- }
168
-
169
- const url = new URL("https://zoom.us/oauth/authorize");
170
- url.search = params.toString();
171
-
172
- return url;
173
182
  },
174
183
  validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
175
184
  return validateAuthorizationCode({
@@ -226,5 +235,5 @@ export const zoom = (userOptions: ZoomOptions) => {
226
235
  },
227
236
  };
228
237
  },
229
- } satisfies OAuthProvider<ZoomProfile>;
238
+ } satisfies UpstreamProvider<ZoomProfile>;
230
239
  };
@@ -10,12 +10,13 @@ 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 { OAuthProvider } from "../oauth2";
13
+ import type { UpstreamProvider } from "../oauth2";
14
14
  import type { BetterAuthCookie, BetterAuthCookies } from "./cookie";
15
15
  import type { Awaitable, LiteralString } from "./helper";
16
16
  import type {
17
17
  BetterAuthOptions,
18
18
  BetterAuthRateLimitOptions,
19
+ UserProvisioningSource,
19
20
  } from "./init-options";
20
21
  import type { BetterAuthPlugin } from "./plugin";
21
22
  import type { SecretConfig } from "./secret";
@@ -87,16 +88,15 @@ export type GenericEndpointContext<
87
88
  export interface InternalAdapter<
88
89
  _Options extends BetterAuthOptions = BetterAuthOptions,
89
90
  > {
90
- createOAuthUser(
91
- user: Omit<User, "id" | "createdAt" | "updatedAt">,
92
- account: Omit<Account, "userId" | "id" | "createdAt" | "updatedAt"> &
93
- Partial<Account>,
94
- ): Promise<{ user: User; account: Account }>;
95
-
96
91
  createUser<T extends Record<string, any>>(
97
92
  user: Omit<User, "id" | "createdAt" | "updatedAt" | "emailVerified"> &
98
93
  Partial<User> &
99
94
  Record<string, any>,
95
+ /**
96
+ * Provisioning source. The creation seam adds `action: "create-user"` and
97
+ * runs the `user.validateUserInfo` gate.
98
+ */
99
+ source: UserProvisioningSource,
100
100
  ): Promise<T & User>;
101
101
 
102
102
  createAccount<T extends Record<string, any>>(
@@ -158,7 +158,15 @@ export interface InternalAdapter<
158
158
  */
159
159
  deleteAccount(id: string): Promise<void>;
160
160
 
161
- deleteSessions(userIdOrSessionTokens: string | string[]): Promise<void>;
161
+ /**
162
+ * Delete every session belonging to a user.
163
+ */
164
+ deleteUserSessions(userId: string): Promise<void>;
165
+
166
+ /**
167
+ * Delete sessions by their session tokens.
168
+ */
169
+ deleteSessions(sessionTokens: string[]): Promise<void>;
162
170
 
163
171
  findOAuthUser(
164
172
  email: string,
@@ -196,8 +204,6 @@ export interface InternalAdapter<
196
204
 
197
205
  findAccounts(userId: string): Promise<Account[]>;
198
206
 
199
- findAccount(accountId: string): Promise<Account | null>;
200
-
201
207
  findAccountByProviderId(
202
208
  accountId: string,
203
209
  providerId: string,
@@ -216,6 +222,21 @@ export interface InternalAdapter<
216
222
 
217
223
  deleteVerificationByIdentifier(identifier: string): Promise<void>;
218
224
 
225
+ /**
226
+ * Atomically consume a single-use verification row by `identifier` and
227
+ * return it. Only the first concurrent caller receives the latest row;
228
+ * subsequent callers receive `null`. Consuming one row invalidates the
229
+ * whole identifier so stale rows cannot be replayed. Rows past their
230
+ * `expiresAt` are treated as already invalid: the row is deleted but
231
+ * `null` is returned, so callers do not need to gate on `expiresAt`
232
+ * themselves. Callers MUST gate any state change (issue session, mint
233
+ * token, change password) on a non-null result.
234
+ *
235
+ * Replaces the racy `findVerificationValue` + `deleteVerificationByIdentifier`
236
+ * pair at single-use credential consumption sites.
237
+ */
238
+ consumeVerificationValue(identifier: string): Promise<Verification | null>;
239
+
219
240
  updateVerificationByIdentifier(
220
241
  identifier: string,
221
242
  data: Partial<Verification>,
@@ -306,7 +327,7 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
306
327
  * - "cookie": Store state in an encrypted cookie (stateless)
307
328
  * - "database": Store state in the database
308
329
  *
309
- * @default "cookie"
330
+ * @default "database" when `database` or `secondaryStorage` is configured, "cookie" otherwise
310
331
  */
311
332
  storeStateStrategy: "database" | "cookie";
312
333
  };
@@ -330,7 +351,7 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
330
351
  user: User & Record<string, any>;
331
352
  } | null,
332
353
  ) => void;
333
- socialProviders: OAuthProvider[];
354
+ socialProviders: UpstreamProvider[];
334
355
  authCookies: BetterAuthCookies;
335
356
  logger: ReturnType<typeof createLogger>;
336
357
  rateLimit: {
@@ -24,6 +24,13 @@ export type {
24
24
  DynamicBaseURLConfig,
25
25
  GenerateIdFn,
26
26
  StoreIdentifierOption,
27
+ UserProvisioningSource,
28
+ ValidateUserInfoAction,
29
+ ValidateUserInfoMethod,
30
+ ValidateUserInfoOAuthInfo,
31
+ ValidateUserInfoResult,
32
+ ValidateUserInfoSource,
33
+ ValidateUserInfoSSOInfo,
27
34
  } from "./init-options";
28
35
  export type {
29
36
  BetterAuthPlugin,