@better-auth/core 1.7.0-beta.0 → 1.7.0-beta.10

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 (218) hide show
  1. package/dist/api/index.d.mts +44 -1
  2. package/dist/api/index.mjs +69 -4
  3. package/dist/context/global.mjs +1 -1
  4. package/dist/context/transaction.d.mts +7 -4
  5. package/dist/context/transaction.mjs +6 -3
  6. package/dist/db/adapter/factory.mjs +91 -3
  7. package/dist/db/adapter/get-id-field.mjs +1 -1
  8. package/dist/db/adapter/index.d.mts +87 -3
  9. package/dist/db/adapter/types.d.mts +1 -1
  10. package/dist/db/get-tables.mjs +2 -1
  11. package/dist/db/type.d.mts +17 -0
  12. package/dist/env/env-impl.mjs +1 -1
  13. package/dist/error/codes.d.mts +1 -0
  14. package/dist/error/codes.mjs +1 -0
  15. package/dist/error/index.d.mts +7 -0
  16. package/dist/index.d.mts +2 -2
  17. package/dist/instrumentation/api.mjs +12 -0
  18. package/dist/instrumentation/noop.mjs +42 -0
  19. package/dist/instrumentation/pure.index.d.mts +7 -0
  20. package/dist/instrumentation/pure.index.mjs +7 -0
  21. package/dist/instrumentation/tracer.mjs +6 -3
  22. package/dist/oauth2/authorization-params.d.mts +12 -0
  23. package/dist/oauth2/authorization-params.mjs +12 -0
  24. package/dist/oauth2/basic-credentials.d.mts +30 -0
  25. package/dist/oauth2/basic-credentials.mjs +64 -0
  26. package/dist/oauth2/client-assertion.d.mts +38 -22
  27. package/dist/oauth2/client-assertion.mjs +63 -28
  28. package/dist/oauth2/client-credentials-token.d.mts +19 -40
  29. package/dist/oauth2/client-credentials-token.mjs +20 -31
  30. package/dist/oauth2/create-authorization-url.d.mts +11 -1
  31. package/dist/oauth2/create-authorization-url.mjs +27 -7
  32. package/dist/oauth2/dpop.d.mts +142 -0
  33. package/dist/oauth2/dpop.mjs +246 -0
  34. package/dist/oauth2/index.d.mts +14 -9
  35. package/dist/oauth2/index.mjs +12 -8
  36. package/dist/oauth2/oauth-provider.d.mts +150 -10
  37. package/dist/oauth2/refresh-access-token.d.mts +20 -40
  38. package/dist/oauth2/refresh-access-token.mjs +38 -36
  39. package/dist/oauth2/reject-redirects.mjs +65 -0
  40. package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
  41. package/dist/oauth2/token-endpoint-auth.mjs +89 -0
  42. package/dist/oauth2/utils.d.mts +23 -1
  43. package/dist/oauth2/utils.mjs +48 -2
  44. package/dist/oauth2/validate-authorization-code.d.mts +17 -52
  45. package/dist/oauth2/validate-authorization-code.mjs +28 -34
  46. package/dist/oauth2/verify-id-token.d.mts +27 -0
  47. package/dist/oauth2/verify-id-token.mjs +62 -0
  48. package/dist/oauth2/verify.d.mts +88 -15
  49. package/dist/oauth2/verify.mjs +203 -25
  50. package/dist/social-providers/apple.d.mts +16 -4
  51. package/dist/social-providers/apple.mjs +17 -24
  52. package/dist/social-providers/atlassian.d.mts +5 -1
  53. package/dist/social-providers/atlassian.mjs +6 -3
  54. package/dist/social-providers/cognito.d.mts +27 -3
  55. package/dist/social-providers/cognito.mjs +15 -25
  56. package/dist/social-providers/discord.d.mts +7 -3
  57. package/dist/social-providers/discord.mjs +16 -3
  58. package/dist/social-providers/dropbox.d.mts +5 -1
  59. package/dist/social-providers/dropbox.mjs +5 -4
  60. package/dist/social-providers/facebook.d.mts +24 -5
  61. package/dist/social-providers/facebook.mjs +54 -20
  62. package/dist/social-providers/figma.d.mts +5 -1
  63. package/dist/social-providers/figma.mjs +4 -3
  64. package/dist/social-providers/github.d.mts +6 -2
  65. package/dist/social-providers/github.mjs +5 -4
  66. package/dist/social-providers/gitlab.d.mts +5 -1
  67. package/dist/social-providers/gitlab.mjs +3 -2
  68. package/dist/social-providers/google.d.mts +53 -5
  69. package/dist/social-providers/google.mjs +69 -25
  70. package/dist/social-providers/huggingface.d.mts +5 -1
  71. package/dist/social-providers/huggingface.mjs +3 -2
  72. package/dist/social-providers/index.d.mts +221 -45
  73. package/dist/social-providers/index.mjs +2 -2
  74. package/dist/social-providers/kakao.d.mts +5 -1
  75. package/dist/social-providers/kakao.mjs +3 -2
  76. package/dist/social-providers/kick.d.mts +5 -1
  77. package/dist/social-providers/kick.mjs +3 -2
  78. package/dist/social-providers/line.d.mts +8 -2
  79. package/dist/social-providers/line.mjs +5 -6
  80. package/dist/social-providers/linear.d.mts +5 -1
  81. package/dist/social-providers/linear.mjs +3 -2
  82. package/dist/social-providers/linkedin.d.mts +7 -3
  83. package/dist/social-providers/linkedin.mjs +4 -3
  84. package/dist/social-providers/microsoft-entra-id.d.mts +41 -7
  85. package/dist/social-providers/microsoft-entra-id.mjs +37 -28
  86. package/dist/social-providers/naver.d.mts +5 -1
  87. package/dist/social-providers/naver.mjs +3 -2
  88. package/dist/social-providers/notion.d.mts +5 -1
  89. package/dist/social-providers/notion.mjs +5 -2
  90. package/dist/social-providers/paybin.d.mts +5 -1
  91. package/dist/social-providers/paybin.mjs +4 -3
  92. package/dist/social-providers/paypal.d.mts +6 -2
  93. package/dist/social-providers/paypal.mjs +19 -14
  94. package/dist/social-providers/polar.d.mts +5 -1
  95. package/dist/social-providers/polar.mjs +3 -2
  96. package/dist/social-providers/railway.d.mts +5 -1
  97. package/dist/social-providers/railway.mjs +3 -2
  98. package/dist/social-providers/reddit.d.mts +5 -1
  99. package/dist/social-providers/reddit.mjs +7 -5
  100. package/dist/social-providers/roblox.d.mts +6 -2
  101. package/dist/social-providers/roblox.mjs +12 -2
  102. package/dist/social-providers/salesforce.d.mts +5 -1
  103. package/dist/social-providers/salesforce.mjs +4 -3
  104. package/dist/social-providers/slack.d.mts +6 -2
  105. package/dist/social-providers/slack.mjs +11 -8
  106. package/dist/social-providers/spotify.d.mts +5 -1
  107. package/dist/social-providers/spotify.mjs +3 -2
  108. package/dist/social-providers/tiktok.d.mts +5 -1
  109. package/dist/social-providers/tiktok.mjs +14 -2
  110. package/dist/social-providers/twitch.d.mts +5 -1
  111. package/dist/social-providers/twitch.mjs +3 -2
  112. package/dist/social-providers/twitter.d.mts +4 -1
  113. package/dist/social-providers/twitter.mjs +2 -1
  114. package/dist/social-providers/vercel.d.mts +5 -1
  115. package/dist/social-providers/vercel.mjs +3 -2
  116. package/dist/social-providers/vk.d.mts +5 -1
  117. package/dist/social-providers/vk.mjs +3 -2
  118. package/dist/social-providers/wechat.d.mts +5 -1
  119. package/dist/social-providers/wechat.mjs +8 -2
  120. package/dist/social-providers/zoom.d.mts +5 -1
  121. package/dist/social-providers/zoom.mjs +10 -17
  122. package/dist/types/context.d.mts +55 -6
  123. package/dist/types/index.d.mts +1 -1
  124. package/dist/types/init-options.d.mts +182 -12
  125. package/dist/types/plugin-client.d.mts +12 -2
  126. package/dist/utils/async.d.mts +22 -0
  127. package/dist/utils/async.mjs +32 -0
  128. package/dist/utils/host.d.mts +147 -0
  129. package/dist/utils/host.mjs +298 -0
  130. package/dist/utils/ip.d.mts +28 -5
  131. package/dist/utils/ip.mjs +118 -4
  132. package/dist/utils/is-api-error.d.mts +6 -0
  133. package/dist/utils/is-api-error.mjs +8 -0
  134. package/dist/utils/redirect-uri.d.mts +20 -0
  135. package/dist/utils/redirect-uri.mjs +48 -0
  136. package/dist/utils/string.d.mts +5 -1
  137. package/dist/utils/string.mjs +20 -1
  138. package/dist/utils/url.d.mts +18 -1
  139. package/dist/utils/url.mjs +34 -4
  140. package/package.json +26 -16
  141. package/src/api/index.ts +121 -5
  142. package/src/context/transaction.ts +45 -12
  143. package/src/db/adapter/factory.ts +193 -7
  144. package/src/db/adapter/get-id-field.ts +2 -2
  145. package/src/db/adapter/index.ts +85 -2
  146. package/src/db/adapter/types.ts +2 -0
  147. package/src/db/get-tables.ts +9 -1
  148. package/src/db/schema/account.ts +4 -1
  149. package/src/db/schema/user.ts +3 -0
  150. package/src/db/type.ts +17 -0
  151. package/src/env/env-impl.ts +1 -2
  152. package/src/error/codes.ts +1 -0
  153. package/src/error/index.ts +9 -0
  154. package/src/instrumentation/api.ts +17 -0
  155. package/src/instrumentation/noop.ts +74 -0
  156. package/src/instrumentation/pure.index.ts +31 -0
  157. package/src/instrumentation/tracer.ts +8 -3
  158. package/src/oauth2/authorization-params.ts +28 -0
  159. package/src/oauth2/basic-credentials.ts +87 -0
  160. package/src/oauth2/client-assertion.ts +131 -58
  161. package/src/oauth2/client-credentials-token.ts +50 -74
  162. package/src/oauth2/create-authorization-url.ts +33 -7
  163. package/src/oauth2/dpop.ts +568 -0
  164. package/src/oauth2/index.ts +81 -11
  165. package/src/oauth2/oauth-provider.ts +159 -11
  166. package/src/oauth2/refresh-access-token.ts +82 -83
  167. package/src/oauth2/reject-redirects.ts +75 -0
  168. package/src/oauth2/token-endpoint-auth.ts +221 -0
  169. package/src/oauth2/utils.ts +72 -5
  170. package/src/oauth2/validate-authorization-code.ts +69 -89
  171. package/src/oauth2/verify-id-token.ts +115 -0
  172. package/src/oauth2/verify.ts +409 -78
  173. package/src/social-providers/apple.ts +30 -40
  174. package/src/social-providers/atlassian.ts +8 -1
  175. package/src/social-providers/cognito.ts +36 -39
  176. package/src/social-providers/discord.ts +22 -18
  177. package/src/social-providers/dropbox.ts +7 -5
  178. package/src/social-providers/facebook.ts +108 -52
  179. package/src/social-providers/figma.ts +8 -1
  180. package/src/social-providers/github.ts +5 -3
  181. package/src/social-providers/gitlab.ts +2 -0
  182. package/src/social-providers/google.ts +141 -39
  183. package/src/social-providers/huggingface.ts +8 -1
  184. package/src/social-providers/kakao.ts +2 -1
  185. package/src/social-providers/kick.ts +8 -1
  186. package/src/social-providers/line.ts +25 -27
  187. package/src/social-providers/linear.ts +8 -1
  188. package/src/social-providers/linkedin.ts +5 -3
  189. package/src/social-providers/microsoft-entra-id.ts +100 -55
  190. package/src/social-providers/naver.ts +2 -1
  191. package/src/social-providers/notion.ts +8 -1
  192. package/src/social-providers/paybin.ts +2 -0
  193. package/src/social-providers/paypal.ts +28 -17
  194. package/src/social-providers/polar.ts +8 -1
  195. package/src/social-providers/railway.ts +8 -1
  196. package/src/social-providers/reddit.ts +9 -4
  197. package/src/social-providers/roblox.ts +16 -11
  198. package/src/social-providers/salesforce.ts +8 -1
  199. package/src/social-providers/slack.ts +15 -9
  200. package/src/social-providers/spotify.ts +8 -1
  201. package/src/social-providers/tiktok.ts +22 -9
  202. package/src/social-providers/twitch.ts +2 -1
  203. package/src/social-providers/twitter.ts +1 -0
  204. package/src/social-providers/vercel.ts +8 -1
  205. package/src/social-providers/vk.ts +8 -1
  206. package/src/social-providers/wechat.ts +17 -2
  207. package/src/social-providers/zoom.ts +15 -19
  208. package/src/types/context.ts +57 -5
  209. package/src/types/index.ts +7 -0
  210. package/src/types/init-options.ts +204 -14
  211. package/src/types/plugin-client.ts +16 -2
  212. package/src/utils/async.ts +53 -0
  213. package/src/utils/host.ts +425 -0
  214. package/src/utils/ip.ts +197 -13
  215. package/src/utils/is-api-error.ts +10 -0
  216. package/src/utils/redirect-uri.ts +54 -0
  217. package/src/utils/string.ts +37 -0
  218. package/src/utils/url.ts +38 -4
@@ -24,7 +24,8 @@ declare const vk: (options: VkOption) => {
24
24
  state,
25
25
  scopes,
26
26
  codeVerifier,
27
- redirectURI
27
+ redirectURI,
28
+ additionalParams
28
29
  }: {
29
30
  state: string;
30
31
  codeVerifier: string;
@@ -32,6 +33,8 @@ declare const vk: (options: VkOption) => {
32
33
  redirectURI: string;
33
34
  display?: string | undefined;
34
35
  loginHint?: string | undefined;
36
+ idTokenNonce?: string | undefined;
37
+ additionalParams?: Record<string, string> | undefined;
35
38
  }): Promise<URL>;
36
39
  validateAuthorizationCode: ({
37
40
  code,
@@ -46,6 +49,7 @@ declare const vk: (options: VkOption) => {
46
49
  }) => Promise<OAuth2Tokens>;
47
50
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
48
51
  getUserInfo(data: OAuth2Tokens & {
52
+ expectedIdTokenNonce?: string | undefined;
49
53
  user?: {
50
54
  name?: {
51
55
  firstName?: string;
@@ -8,7 +8,7 @@ const vk = (options) => {
8
8
  return {
9
9
  id: "vk",
10
10
  name: "VK",
11
- async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
11
+ async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
12
12
  const _scopes = options.disableDefaultScope ? [] : ["email", "phone"];
13
13
  if (options.scope) _scopes.push(...options.scope);
14
14
  if (scopes) _scopes.push(...scopes);
@@ -19,7 +19,8 @@ const vk = (options) => {
19
19
  scopes: _scopes,
20
20
  state,
21
21
  redirectURI,
22
- codeVerifier
22
+ codeVerifier,
23
+ additionalParams
23
24
  });
24
25
  },
25
26
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI, deviceId }) => {
@@ -56,7 +56,8 @@ declare const wechat: (options: WeChatOptions) => {
56
56
  createAuthorizationURL({
57
57
  state,
58
58
  scopes,
59
- redirectURI
59
+ redirectURI,
60
+ additionalParams
60
61
  }: {
61
62
  state: string;
62
63
  codeVerifier: string;
@@ -64,6 +65,8 @@ declare const wechat: (options: WeChatOptions) => {
64
65
  redirectURI: string;
65
66
  display?: string | undefined;
66
67
  loginHint?: string | undefined;
68
+ idTokenNonce?: string | undefined;
69
+ additionalParams?: Record<string, string> | undefined;
67
70
  }): URL;
68
71
  validateAuthorizationCode: ({
69
72
  code
@@ -89,6 +92,7 @@ declare const wechat: (options: WeChatOptions) => {
89
92
  scopes: string[];
90
93
  }>);
91
94
  getUserInfo(token: OAuth2Tokens & {
95
+ expectedIdTokenNonce?: string | undefined;
92
96
  user?: {
93
97
  name?: {
94
98
  firstName?: string;
@@ -1,10 +1,11 @@
1
+ import { RESERVED_AUTHORIZATION_PARAMS_SET } from "../oauth2/create-authorization-url.mjs";
1
2
  import { betterFetch } from "@better-fetch/fetch";
2
3
  //#region src/social-providers/wechat.ts
3
4
  const wechat = (options) => {
4
5
  return {
5
6
  id: "wechat",
6
7
  name: "WeChat",
7
- createAuthorizationURL({ state, scopes, redirectURI }) {
8
+ createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
8
9
  const _scopes = options.disableDefaultScope ? [] : ["snsapi_login"];
9
10
  options.scope && _scopes.push(...options.scope);
10
11
  scopes && _scopes.push(...scopes);
@@ -15,6 +16,11 @@ const wechat = (options) => {
15
16
  url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
16
17
  url.searchParams.set("state", state);
17
18
  url.searchParams.set("lang", options.lang || "cn");
19
+ if (additionalParams) for (const [key, value] of Object.entries(additionalParams)) {
20
+ if (RESERVED_AUTHORIZATION_PARAMS_SET.has(key)) continue;
21
+ if (key === "appid") continue;
22
+ url.searchParams.set(key, value);
23
+ }
18
24
  url.hash = "wechat_redirect";
19
25
  return url;
20
26
  },
@@ -66,7 +72,7 @@ const wechat = (options) => {
66
72
  user: {
67
73
  id: profile.unionid || profile.openid || openid,
68
74
  name: profile.nickname,
69
- email: profile.email || null,
75
+ email: profile.email || `${profile.unionid || profile.openid || openid}@wechat.invalid`,
70
76
  image: profile.headimgurl,
71
77
  emailVerified: false,
72
78
  ...userMap
@@ -119,7 +119,8 @@ declare const zoom: (userOptions: ZoomOptions) => {
119
119
  createAuthorizationURL: ({
120
120
  state,
121
121
  redirectURI,
122
- codeVerifier
122
+ codeVerifier,
123
+ additionalParams
123
124
  }: {
124
125
  state: string;
125
126
  codeVerifier: string;
@@ -127,6 +128,8 @@ declare const zoom: (userOptions: ZoomOptions) => {
127
128
  redirectURI: string;
128
129
  display?: string | undefined;
129
130
  loginHint?: string | undefined;
131
+ idTokenNonce?: string | undefined;
132
+ additionalParams?: Record<string, string> | undefined;
130
133
  }) => Promise<URL>;
131
134
  validateAuthorizationCode: ({
132
135
  code,
@@ -140,6 +143,7 @@ declare const zoom: (userOptions: ZoomOptions) => {
140
143
  }) => Promise<OAuth2Tokens>;
141
144
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
142
145
  getUserInfo(token: OAuth2Tokens & {
146
+ expectedIdTokenNonce?: string | undefined;
143
147
  user?: {
144
148
  name?: {
145
149
  firstName?: string;
@@ -1,4 +1,4 @@
1
- import { generateCodeChallenge } from "../oauth2/utils.mjs";
1
+ import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
2
2
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
3
3
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
4
4
  import { betterFetch } from "@better-fetch/fetch";
@@ -11,22 +11,15 @@ const zoom = (userOptions) => {
11
11
  return {
12
12
  id: "zoom",
13
13
  name: "Zoom",
14
- createAuthorizationURL: async ({ state, redirectURI, codeVerifier }) => {
15
- const params = new URLSearchParams({
16
- response_type: "code",
17
- redirect_uri: options.redirectURI ? options.redirectURI : redirectURI,
18
- client_id: options.clientId,
19
- state
20
- });
21
- if (options.pkce) {
22
- const codeChallenge = await generateCodeChallenge(codeVerifier);
23
- params.set("code_challenge_method", "S256");
24
- params.set("code_challenge", codeChallenge);
25
- }
26
- const url = new URL("https://zoom.us/oauth/authorize");
27
- url.search = params.toString();
28
- return url;
29
- },
14
+ createAuthorizationURL: async ({ state, redirectURI, codeVerifier, additionalParams }) => createAuthorizationURL({
15
+ id: "zoom",
16
+ options,
17
+ authorizationEndpoint: "https://zoom.us/oauth/authorize",
18
+ state,
19
+ redirectURI,
20
+ codeVerifier: options.pkce ? codeVerifier : void 0,
21
+ additionalParams
22
+ }),
30
23
  validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
31
24
  return validateAuthorizationCode({
32
25
  code,
@@ -6,7 +6,7 @@ import { Verification } from "../db/schema/verification.mjs";
6
6
  import { createLogger } from "../env/logger.mjs";
7
7
  import { Awaitable, LiteralString } from "./helper.mjs";
8
8
  import { BetterAuthPlugin } from "./plugin.mjs";
9
- import { BetterAuthOptions, BetterAuthRateLimitOptions } from "./init-options.mjs";
9
+ import { BetterAuthOptions, BetterAuthRateLimitOptions, UserProvisioningSource } from "./init-options.mjs";
10
10
  import { Account } from "../db/schema/account.mjs";
11
11
  import { BetterAuthCookie, BetterAuthCookies } from "./cookie.mjs";
12
12
  import { SecretConfig } from "./secret.mjs";
@@ -58,7 +58,13 @@ interface InternalAdapter<_Options extends BetterAuthOptions = BetterAuthOptions
58
58
  user: User;
59
59
  account: Account;
60
60
  }>;
61
- createUser<T extends Record<string, any>>(user: Omit<User, "id" | "createdAt" | "updatedAt" | "emailVerified"> & Partial<User> & Record<string, any>): Promise<T & User>;
61
+ createUser<T extends Record<string, any>>(user: Omit<User, "id" | "createdAt" | "updatedAt" | "emailVerified"> & Partial<User> & Record<string, any>,
62
+ /**
63
+ * Provisioning source. The creation seam adds `action: "create-user"` and
64
+ * runs the `user.validateUserInfo` gate.
65
+ */
66
+
67
+ source: UserProvisioningSource): Promise<T & User>;
62
68
  createAccount<T extends Record<string, any>>(account: Omit<Account, "id" | "createdAt" | "updatedAt"> & Partial<Account> & T): Promise<T & Account>;
63
69
  listSessions(userId: string, options?: {
64
70
  onlyActiveSessions?: boolean | undefined;
@@ -83,8 +89,20 @@ interface InternalAdapter<_Options extends BetterAuthOptions = BetterAuthOptions
83
89
  updateSession(sessionToken: string, session: Partial<Session> & Record<string, any>): Promise<Session | null>;
84
90
  deleteSession(token: string): Promise<void>;
85
91
  deleteAccounts(userId: string): Promise<void>;
86
- deleteAccount(accountId: string): Promise<void>;
87
- deleteSessions(userIdOrSessionTokens: string | string[]): Promise<void>;
92
+ /**
93
+ * Delete an account by its primary key.
94
+ *
95
+ * @param id - The account row's primary key (the `id` column, not the `accountId` column).
96
+ */
97
+ deleteAccount(id: string): Promise<void>;
98
+ /**
99
+ * Delete every session belonging to a user.
100
+ */
101
+ deleteUserSessions(userId: string): Promise<void>;
102
+ /**
103
+ * Delete sessions by their session tokens.
104
+ */
105
+ deleteSessions(sessionTokens: string[]): Promise<void>;
88
106
  findOAuthUser(email: string, accountId: string, providerId: string): Promise<{
89
107
  user: User;
90
108
  linkedAccount: Account | null;
@@ -102,14 +120,45 @@ interface InternalAdapter<_Options extends BetterAuthOptions = BetterAuthOptions
102
120
  updateUserByEmail<T extends Record<string, any>>(email: string, data: Partial<User & Record<string, any>>): Promise<User & T>;
103
121
  updatePassword(userId: string, password: string): Promise<void>;
104
122
  findAccounts(userId: string): Promise<Account[]>;
105
- findAccount(accountId: string): Promise<Account | null>;
106
123
  findAccountByProviderId(accountId: string, providerId: string): Promise<Account | null>;
107
124
  findAccountByUserId(userId: string): Promise<Account[]>;
108
125
  updateAccount(id: string, data: Partial<Account>): Promise<Account>;
109
126
  createVerificationValue(data: Omit<Verification, "createdAt" | "id" | "updatedAt"> & Partial<Verification>): Promise<Verification>;
110
127
  findVerificationValue(identifier: string): Promise<Verification | null>;
111
128
  deleteVerificationByIdentifier(identifier: string): Promise<void>;
129
+ /**
130
+ * Atomically consume a single-use verification row by `identifier` and
131
+ * return it. Only the first concurrent caller receives the latest row;
132
+ * subsequent callers receive `null`. Consuming one row invalidates the
133
+ * whole identifier so stale rows cannot be replayed. Rows past their
134
+ * `expiresAt` are treated as already invalid: the row is deleted but
135
+ * `null` is returned, so callers do not need to gate on `expiresAt`
136
+ * themselves. Callers MUST gate any state change (issue session, mint
137
+ * token, change password) on a non-null result.
138
+ *
139
+ * Replaces the racy `findVerificationValue` + `deleteVerificationByIdentifier`
140
+ * pair at single-use credential consumption sites.
141
+ */
142
+ consumeVerificationValue(identifier: string): Promise<Verification | null>;
143
+ /**
144
+ * First-writer-wins create keyed by a deterministic primary key derived from
145
+ * `identifier`. Returns `true` when this caller created the row and `false`
146
+ * when a row for the same identifier already existed.
147
+ *
148
+ * The dual of `consumeVerificationValue`: reserve races to create a marker
149
+ * exactly once, where consume races to delete one exactly once. Use it for
150
+ * replay tombstones (a SAML assertion id, a JWT `jti`) where the first caller
151
+ * wins. The database path is atomic via the primary key. Secondary-storage-only
152
+ * verification is not supported for reservation and runtime implementations
153
+ * should fail closed unless verification is backed by the database.
154
+ */
155
+ reserveVerificationValue(data: {
156
+ identifier: string;
157
+ value: string;
158
+ expiresAt: Date;
159
+ }): Promise<boolean>;
112
160
  updateVerificationByIdentifier(identifier: string, data: Partial<Verification>): Promise<Verification>;
161
+ refreshUserSessions(user: User): Promise<void>;
113
162
  }
114
163
  type CreateCookieGetterFn = (cookieName: string, overrideAttributes?: Partial<CookieOptions> | undefined) => BetterAuthCookie;
115
164
  type CheckPasswordFn<Options extends BetterAuthOptions = BetterAuthOptions> = (userId: string, ctx: GenericEndpointContext<Options>) => Promise<boolean>;
@@ -165,7 +214,7 @@ type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> = Plugin
165
214
  * - "cookie": Store state in an encrypted cookie (stateless)
166
215
  * - "database": Store state in the database
167
216
  *
168
- * @default "cookie"
217
+ * @default "database" when `database` or `secondaryStorage` is configured, "cookie" otherwise
169
218
  */
170
219
  storeStateStrategy: "database" | "cookie";
171
220
  };
@@ -1,6 +1,6 @@
1
1
  import { Awaitable, AwaitableFunction, LiteralString, LiteralUnion, Prettify, Primitive, UnionToIntersection } from "./helper.mjs";
2
2
  import { BetterAuthPlugin, BetterAuthPluginErrorCodePart, HookEndpointContext } from "./plugin.mjs";
3
- import { BaseURLConfig, BetterAuthAdvancedOptions, BetterAuthDBOptions, BetterAuthOptions, BetterAuthRateLimitOptions, BetterAuthRateLimitRule, BetterAuthRateLimitStorage, DynamicBaseURLConfig, GenerateIdFn, StoreIdentifierOption } from "./init-options.mjs";
3
+ import { BaseURLConfig, BetterAuthAdvancedOptions, BetterAuthDBOptions, BetterAuthOptions, BetterAuthRateLimitOptions, BetterAuthRateLimitRule, BetterAuthRateLimitStorage, DynamicBaseURLConfig, GenerateIdFn, StoreIdentifierOption, UserProvisioningSource, ValidateUserInfoAction, ValidateUserInfoMethod, ValidateUserInfoOAuthInfo, ValidateUserInfoResult, ValidateUserInfoSSOInfo, ValidateUserInfoSource } from "./init-options.mjs";
4
4
  import { BetterAuthCookie, BetterAuthCookies } from "./cookie.mjs";
5
5
  import { SecretConfig } from "./secret.mjs";
6
6
  import { AuthContext, BetterAuthPluginRegistry, BetterAuthPluginRegistryIdentifier, GenericEndpointContext, InfoContext, InternalAdapter, PluginContext } from "./context.mjs";
@@ -1,6 +1,6 @@
1
1
  import { DBFieldAttribute, ModelNames, SecondaryStorage } from "../db/type.mjs";
2
2
  import { DBAdapterDebugLogOption, DBAdapterInstance } from "../db/adapter/index.mjs";
3
- import { BaseRateLimit, RateLimit } from "../db/schema/rate-limit.mjs";
3
+ import { BaseRateLimit } from "../db/schema/rate-limit.mjs";
4
4
  import { BaseSession, Session } from "../db/schema/session.mjs";
5
5
  import { BaseUser, User } from "../db/schema/user.mjs";
6
6
  import { BaseVerification, Verification } from "../db/schema/verification.mjs";
@@ -27,6 +27,73 @@ type GenerateIdFn = (options: {
27
27
  model: ModelNames;
28
28
  size?: number | undefined;
29
29
  }) => string | false;
30
+ /**
31
+ * What Better Auth is about to do with an incoming identity when
32
+ * {@link BetterAuthOptions.user}'s `validateUserInfo` runs.
33
+ *
34
+ * - `create-user`: a brand-new user record is about to be created.
35
+ * - `link-account`: a new provider account is about to be linked to an
36
+ * already-existing user.
37
+ * - `sign-in`: an existing OAuth or SSO user is signing in again. This is the
38
+ * one case where the provider can assert *changed* data, so the hook receives
39
+ * the fresh provider email and profile (not the stored row), letting a domain
40
+ * or org policy reject a user whose provider identity moved out of bounds.
41
+ *
42
+ * Non-provider returning sign-ins are not re-validated: they carry only the
43
+ * stored row, which has not changed since `create-user` gated it. Use the admin
44
+ * plugin's ban controls or a `databaseHooks.session.create.before` hook to
45
+ * block those.
46
+ */
47
+ type ValidateUserInfoAction = "create-user" | "link-account" | "sign-in";
48
+ /**
49
+ * The authentication method that produced the incoming user info. The named
50
+ * methods cover Better Auth's built-ins; the open `string` keeps it extensible
51
+ * for plugins (for example `"scim"`).
52
+ */
53
+ type ValidateUserInfoMethod = "oauth" | "sso-oidc" | "sso-saml" | "email-password" | "magic-link" | "email-otp" | "anonymous" | "siwe" | "phone-number" | "admin" | (string & {});
54
+ /** OAuth-specific provisioning context; present only when `method` is `"oauth"`. */
55
+ type ValidateUserInfoOAuthInfo = {
56
+ /** The social or generic OAuth provider id (e.g. `"google"`). */providerId: string; /** The raw provider profile (userinfo or id-token claims), unmapped. */
57
+ profile?: Record<string, unknown> | undefined;
58
+ };
59
+ /** SSO-specific provisioning context; present for OIDC and SAML SSO methods. */
60
+ type ValidateUserInfoSSOInfo = {
61
+ /** The configured SSO provider id. */providerId: string; /** The raw OIDC claims or SAML assertion attributes, unmapped. */
62
+ profile?: Record<string, unknown> | undefined;
63
+ };
64
+ /** Provisioning origin passed to `createUser`; the creation seam adds `action: "create-user"` to build {@link ValidateUserInfoSource}. */
65
+ type UserProvisioningSource = {
66
+ method: ValidateUserInfoMethod; /** Provider id and raw profile; present iff `method` is `"oauth"`. */
67
+ oauth?: ValidateUserInfoOAuthInfo | undefined; /** Provider id and raw profile; present iff `method` is `"sso-oidc"` or `"sso-saml"`. */
68
+ sso?: ValidateUserInfoSSOInfo | undefined;
69
+ };
70
+ /**
71
+ * The context passed to `validateUserInfo`: the lifecycle
72
+ * {@link ValidateUserInfoAction}, the {@link ValidateUserInfoMethod}, and (for
73
+ * OAuth/SSO provider methods) protocol-specific provider metadata.
74
+ *
75
+ * ```ts
76
+ * // Scope to one OAuth provider:
77
+ * if (source.oauth?.providerId !== "google") return;
78
+ * // Branch on the method:
79
+ * if (source.method === "anonymous") return { error: "no_anonymous" };
80
+ * // Inspect SSO claims:
81
+ * if (source.method === "sso-saml" && source.sso?.profile?.department !== "eng") {
82
+ * return { error: "invalid_department" };
83
+ * }
84
+ * ```
85
+ */
86
+ type ValidateUserInfoSource = UserProvisioningSource & {
87
+ action: ValidateUserInfoAction;
88
+ };
89
+ type ValidateUserInfoResult = {
90
+ /** A short, machine-readable rejection code, surfaced to the client. */error: string;
91
+ /**
92
+ * A human-readable reason, surfaced to the client. Do not put sensitive
93
+ * details here.
94
+ */
95
+ errorDescription?: string | undefined;
96
+ };
30
97
  /**
31
98
  * Configuration for dynamic base URL resolution.
32
99
  * Allows Better Auth to work with multiple domains (e.g., Vercel preview deployments).
@@ -71,8 +138,30 @@ type DynamicBaseURLConfig = {
71
138
  */
72
139
  type BaseURLConfig = string | DynamicBaseURLConfig;
73
140
  interface BetterAuthRateLimitStorage {
74
- get: (key: string) => Promise<RateLimit | null | undefined>;
75
- set: (key: string, value: RateLimit, update?: boolean | undefined) => Promise<void>;
141
+ /**
142
+ * Atomically records one request against `key` within the rolling `window`
143
+ * (in seconds) and reports whether it is allowed.
144
+ *
145
+ * When `allowed` is true the count was incremented within the active window,
146
+ * or the window had elapsed and was reset to start at 1. When `allowed` is
147
+ * false the limit was already reached and `retryAfter` is the number of
148
+ * seconds until the window frees up.
149
+ *
150
+ * Performing the check and the increment in a single step closes the
151
+ * concurrent-bypass gap of the separate `get`/`set` path: N simultaneous
152
+ * requests can no longer all pass a stale read before any increment lands.
153
+ *
154
+ * Custom storages must implement this operation directly. Better Auth no
155
+ * longer accepts separate `get`/`set` rate-limit storage because that shape
156
+ * cannot enforce a distributed limit under concurrent requests.
157
+ */
158
+ consume: (key: string, rule: {
159
+ window: number;
160
+ max: number;
161
+ }) => Promise<{
162
+ allowed: boolean;
163
+ retryAfter: number | null;
164
+ }>;
76
165
  }
77
166
  type BetterAuthRateLimitRule = {
78
167
  /**
@@ -147,7 +236,7 @@ type BetterAuthAdvancedOptions = {
147
236
  * @example ["x-client-ip", "x-forwarded-for", "cf-connecting-ip"]
148
237
  *
149
238
  * @default
150
- * @link https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/utils/get-request-ip.ts#L8
239
+ * @link https://github.com/better-auth/better-auth/blob/main/packages/core/src/utils/ip.ts
151
240
  */
152
241
  ipAddressHeaders?: string[];
153
242
  /**
@@ -157,12 +246,28 @@ type BetterAuthAdvancedOptions = {
157
246
  */
158
247
  disableIpTracking?: boolean;
159
248
  /**
160
- * IPv6 subnet prefix length for rate limiting.
161
- * IPv6 addresses will be normalized to this subnet.
249
+ * IPv6 prefix length used to collapse addresses before rate-limit keying.
250
+ * Any integer from 0 to 128 is accepted; common values are 32, 48, 56, 64, 128.
251
+ * Out-of-range values fall back to safe behavior (negative -> mask all, > 128 -> no mask).
162
252
  *
163
253
  * @default 64
164
254
  */
165
- ipv6Subnet?: 128 | 64 | 48 | 32;
255
+ ipv6Subnet?: number;
256
+ /**
257
+ * Trusted reverse-proxy IPs or CIDR ranges. When set, a forwarded IP
258
+ * chain is walked right to left, trusted hops are skipped, and the
259
+ * first untrusted address is the client IP. Unset trusts only
260
+ * single-value IP headers. Use the actual address or subnet of your
261
+ * proxies, not a broad private range that also covers clients.
262
+ *
263
+ * This only interprets the forwarded header chain and cannot verify
264
+ * the direct sender. It is safe only when your origin is reachable
265
+ * through these proxies and clients cannot set forwarded headers
266
+ * directly.
267
+ *
268
+ * @example ["192.0.2.10", "10.0.0.0/24"]
269
+ */
270
+ trustedProxies?: string[];
166
271
  } | undefined;
167
272
  /**
168
273
  * Force cookies to always use the `Secure` attribute. By default,
@@ -683,6 +788,30 @@ type BetterAuthOptions = {
683
788
  * User configuration
684
789
  */
685
790
  user?: (BetterAuthDBOptions<"user", keyof BaseUser> & {
791
+ /**
792
+ * Gate which identities Better Auth admits. Called just before
793
+ * `create-user`, `link-account`, and (for OAuth) `sign-in`, across
794
+ * every authentication method, including stateless setups with no
795
+ * persistent database. On `sign-in` the hook receives the *fresh*
796
+ * provider email and profile, so a domain policy can reject a user
797
+ * whose provider identity moved out of bounds.
798
+ *
799
+ * Non-provider returning sign-ins are not re-validated; use the admin
800
+ * plugin's ban controls or a `databaseHooks.session.create.before`
801
+ * hook for those.
802
+ *
803
+ * Return nothing to allow; return `{ error }` to reject. Browser flows
804
+ * redirect to the configured error URL; programmatic flows surface a
805
+ * `403`.
806
+ *
807
+ * TODO: rename to `validateUser` (and the `ValidateUserInfo*` types).
808
+ * "UserInfo" is the OIDC term and misleads for the email/password,
809
+ * SIWE, phone, and admin methods.
810
+ */
811
+ validateUserInfo?: (data: {
812
+ user: Partial<User> & Record<string, unknown>;
813
+ source: ValidateUserInfoSource;
814
+ }, context: GenericEndpointContext) => Awaitable<void | ValidateUserInfoResult>;
686
815
  /**
687
816
  * Changing email configuration
688
817
  */
@@ -824,6 +953,20 @@ type BetterAuthOptions = {
824
953
  * @default "compact"
825
954
  */
826
955
  strategy?: "compact" | "jwt" | "jwe";
956
+ /**
957
+ * JWT-specific configuration for `strategy: "jwt"`.
958
+ */
959
+ jwt?: {
960
+ /**
961
+ * Which signing key is used for cookie-cache JWTs.
962
+ *
963
+ * - `"secret"`: uses the Better Auth secret with HS256.
964
+ * - `"jwt-plugin"`: uses the installed `jwt()` plugin's asymmetric signing keys.
965
+ *
966
+ * @default "secret"
967
+ */
968
+ signingKey?: "secret" | "jwt-plugin";
969
+ };
827
970
  /**
828
971
  * Controls stateless cookie cache refresh behavior.
829
972
  *
@@ -906,6 +1049,25 @@ type BetterAuthOptions = {
906
1049
  * @default false
907
1050
  */
908
1051
  disableImplicitLinking?: boolean;
1052
+ /**
1053
+ * Require the existing local user row to have
1054
+ * `emailVerified: true` before implicit account linking
1055
+ * uses the IdP's `email_verified` claim as ownership
1056
+ * proof. Defaults to `true` so an attacker who
1057
+ * pre-registers an unverified account at a victim's
1058
+ * email cannot have the victim's OAuth identity linked
1059
+ * into the attacker-owned row on first sign-in. Set to
1060
+ * `false` for backward compatibility on apps whose
1061
+ * users sign up via OAuth without verifying their email
1062
+ * locally; understand the takeover risk before doing
1063
+ * so.
1064
+ *
1065
+ * @default true
1066
+ *
1067
+ * @deprecated The option will be removed on the next
1068
+ * minor; the gate will become unconditional.
1069
+ */
1070
+ requireLocalEmailVerified?: boolean;
909
1071
  /**
910
1072
  * List of trusted providers. Can be a static array or a function
911
1073
  * that returns providers dynamically. The function is called
@@ -943,7 +1105,11 @@ type BetterAuthOptions = {
943
1105
  */
944
1106
  allowUnlinkingAll?: boolean;
945
1107
  /**
946
- * If enabled (true), this will update the user information based on the newly linked account
1108
+ * When enabled, linking an account copies the provider's profile onto
1109
+ * the local user, matching the fields persisted on sign-up (`name`,
1110
+ * `image`, and any `mapProfileToUser` fields). The local `email` and
1111
+ * `emailVerified` are never changed, so a link cannot rebind the
1112
+ * account's identity.
947
1113
  *
948
1114
  * @default false
949
1115
  */
@@ -976,13 +1142,17 @@ type BetterAuthOptions = {
976
1142
  * - "cookie": Store state in an encrypted cookie (stateless)
977
1143
  * - "database": Store state in the database
978
1144
  *
979
- * @default "cookie"
1145
+ * @default "database" when `database` or `secondaryStorage` is configured, "cookie" otherwise
980
1146
  */
981
1147
  storeStateStrategy?: "database" | "cookie";
982
1148
  /**
983
- * Store account data after oauth flow on a cookie
1149
+ * Store provider account data after an OAuth flow in an encrypted
1150
+ * cookie. This includes OAuth token material such as access tokens,
1151
+ * refresh tokens, ID tokens, scopes, and token expiry.
984
1152
  *
985
- * This is useful for database-less flow
1153
+ * This is useful for database-less flows, but large provider tokens can
1154
+ * still hit browser or proxy cookie/header limits even though Better Auth
1155
+ * chunks oversized account cookies.
986
1156
  *
987
1157
  * @default false
988
1158
  *
@@ -1355,4 +1525,4 @@ type BetterAuthOptions = {
1355
1525
  };
1356
1526
  };
1357
1527
  //#endregion
1358
- export { BaseURLConfig, BetterAuthAdvancedOptions, BetterAuthDBOptions, BetterAuthOptions, BetterAuthRateLimitOptions, BetterAuthRateLimitRule, BetterAuthRateLimitStorage, DynamicBaseURLConfig, GenerateIdFn, StoreIdentifierOption };
1528
+ export { BaseURLConfig, BetterAuthAdvancedOptions, BetterAuthDBOptions, BetterAuthOptions, BetterAuthRateLimitOptions, BetterAuthRateLimitRule, BetterAuthRateLimitStorage, DynamicBaseURLConfig, GenerateIdFn, StoreIdentifierOption, UserProvisioningSource, ValidateUserInfoAction, ValidateUserInfoMethod, ValidateUserInfoOAuthInfo, ValidateUserInfoResult, ValidateUserInfoSSOInfo, ValidateUserInfoSource };
@@ -1,10 +1,20 @@
1
1
  import { LiteralString } from "./helper.mjs";
2
- import { BetterAuthPlugin } from "./plugin.mjs";
3
2
  import { BetterAuthOptions } from "./init-options.mjs";
4
3
  import { BetterFetch, BetterFetchOption, BetterFetchPlugin } from "@better-fetch/fetch";
5
4
  import { Atom, WritableAtom } from "nanostores";
6
5
 
7
6
  //#region src/types/plugin-client.d.ts
7
+ type InferableServerPlugin = {
8
+ id?: LiteralString | undefined;
9
+ endpoints?: Record<string, unknown> | undefined;
10
+ schema?: Record<string, {
11
+ fields: Record<string, unknown>;
12
+ }> | undefined;
13
+ $ERROR_CODES?: Record<string, {
14
+ readonly code: string;
15
+ message: string;
16
+ }> | undefined;
17
+ };
8
18
  interface ClientStore {
9
19
  notify: (signal: string) => void;
10
20
  listen: (signal: string, listener: () => void) => void;
@@ -71,7 +81,7 @@ interface BetterAuthClientPlugin {
71
81
  * only used for type inference. don't pass the
72
82
  * actual plugin
73
83
  */
74
- $InferServerPlugin?: BetterAuthPlugin | undefined;
84
+ $InferServerPlugin?: InferableServerPlugin | undefined;
75
85
  /**
76
86
  * Custom actions
77
87
  */
@@ -0,0 +1,22 @@
1
+ import { Awaitable } from "../types/helper.mjs";
2
+
3
+ //#region src/utils/async.d.ts
4
+ interface MapConcurrentOptions {
5
+ /**
6
+ * Max in-flight mappers. Non-integer values are floored, then clamped
7
+ * to the range `[1, items.length]`. `NaN` falls back to 1.
8
+ */
9
+ concurrency: number;
10
+ /**
11
+ * Rejects with `signal.reason` when aborted. In-flight mappers keep
12
+ * running but their results are not returned.
13
+ */
14
+ signal?: AbortSignal;
15
+ }
16
+ /**
17
+ * Run an async mapper over items with bounded concurrency.
18
+ * Preserves input order in the result. Fails fast on the first rejection.
19
+ */
20
+ declare function mapConcurrent<T, R>(items: readonly T[], fn: (item: T, index: number) => Awaitable<R>, options: MapConcurrentOptions): Promise<R[]>;
21
+ //#endregion
22
+ export { MapConcurrentOptions, mapConcurrent };
@@ -0,0 +1,32 @@
1
+ //#region src/utils/async.ts
2
+ /**
3
+ * Run an async mapper over items with bounded concurrency.
4
+ * Preserves input order in the result. Fails fast on the first rejection.
5
+ */
6
+ async function mapConcurrent(items, fn, options) {
7
+ const n = items.length;
8
+ if (n === 0) return [];
9
+ const { signal } = options;
10
+ if (signal?.aborted) throw signal.reason;
11
+ const raw = Math.floor(options.concurrency);
12
+ const width = Math.min(n, raw >= 1 ? raw : 1);
13
+ const results = new Array(n);
14
+ let idx = 0;
15
+ let failed = false;
16
+ const worker = async () => {
17
+ while (!failed && idx < n) {
18
+ if (signal?.aborted) throw signal.reason;
19
+ const i = idx++;
20
+ try {
21
+ results[i] = await fn(items[i], i);
22
+ } catch (error) {
23
+ failed = true;
24
+ throw error;
25
+ }
26
+ }
27
+ };
28
+ await Promise.all(Array.from({ length: width }, worker));
29
+ return results;
30
+ }
31
+ //#endregion
32
+ export { mapConcurrent };