@better-auth/core 1.5.0-beta.9 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/README.md +17 -0
  2. package/dist/api/index.d.mts +144 -41
  3. package/dist/api/index.mjs +2 -1
  4. package/dist/api/index.mjs.map +1 -0
  5. package/dist/async_hooks/index.d.mts +2 -1
  6. package/dist/async_hooks/index.mjs +2 -1
  7. package/dist/async_hooks/index.mjs.map +1 -0
  8. package/dist/async_hooks/pure.index.d.mts +2 -1
  9. package/dist/async_hooks/pure.index.mjs +2 -1
  10. package/dist/async_hooks/pure.index.mjs.map +1 -0
  11. package/dist/context/endpoint-context.d.mts +2 -1
  12. package/dist/context/endpoint-context.mjs +4 -3
  13. package/dist/context/endpoint-context.mjs.map +1 -0
  14. package/dist/context/global.d.mts +2 -2
  15. package/dist/context/global.mjs +3 -2
  16. package/dist/context/global.mjs.map +1 -0
  17. package/dist/context/request-state.d.mts +2 -1
  18. package/dist/context/request-state.mjs +4 -3
  19. package/dist/context/request-state.mjs.map +1 -0
  20. package/dist/context/transaction.d.mts +2 -1
  21. package/dist/context/transaction.mjs +4 -3
  22. package/dist/context/transaction.mjs.map +1 -0
  23. package/dist/db/adapter/factory.d.mts +6 -13
  24. package/dist/db/adapter/factory.mjs +44 -57
  25. package/dist/db/adapter/factory.mjs.map +1 -0
  26. package/dist/db/adapter/get-default-field-name.d.mts +2 -1
  27. package/dist/db/adapter/get-default-field-name.mjs +3 -2
  28. package/dist/db/adapter/get-default-field-name.mjs.map +1 -0
  29. package/dist/db/adapter/get-default-model-name.d.mts +2 -1
  30. package/dist/db/adapter/get-default-model-name.mjs +5 -4
  31. package/dist/db/adapter/get-default-model-name.mjs.map +1 -0
  32. package/dist/db/adapter/get-field-attributes.d.mts +3 -2
  33. package/dist/db/adapter/get-field-attributes.mjs +2 -1
  34. package/dist/db/adapter/get-field-attributes.mjs.map +1 -0
  35. package/dist/db/adapter/get-field-name.d.mts +2 -1
  36. package/dist/db/adapter/get-field-name.mjs +2 -1
  37. package/dist/db/adapter/get-field-name.mjs.map +1 -0
  38. package/dist/db/adapter/get-id-field.d.mts +3 -2
  39. package/dist/db/adapter/get-id-field.mjs +3 -2
  40. package/dist/db/adapter/get-id-field.mjs.map +1 -0
  41. package/dist/db/adapter/get-model-name.d.mts +2 -1
  42. package/dist/db/adapter/get-model-name.mjs +2 -1
  43. package/dist/db/adapter/get-model-name.mjs.map +1 -0
  44. package/dist/db/adapter/index.d.mts +10 -4
  45. package/dist/db/adapter/index.mjs +19 -2
  46. package/dist/db/adapter/index.mjs.map +1 -0
  47. package/dist/db/adapter/types.d.mts +3 -34
  48. package/dist/db/adapter/utils.d.mts +2 -1
  49. package/dist/db/adapter/utils.mjs +2 -1
  50. package/dist/db/adapter/utils.mjs.map +1 -0
  51. package/dist/db/get-tables.d.mts +2 -1
  52. package/dist/db/get-tables.mjs +46 -39
  53. package/dist/db/get-tables.mjs.map +1 -0
  54. package/dist/db/index.d.mts +7 -7
  55. package/dist/db/plugin.d.mts +2 -1
  56. package/dist/db/schema/account.d.mts +8 -4
  57. package/dist/db/schema/account.mjs +2 -1
  58. package/dist/db/schema/account.mjs.map +1 -0
  59. package/dist/db/schema/rate-limit.d.mts +8 -2
  60. package/dist/db/schema/rate-limit.mjs +2 -1
  61. package/dist/db/schema/rate-limit.mjs.map +1 -0
  62. package/dist/db/schema/session.d.mts +8 -4
  63. package/dist/db/schema/session.mjs +2 -1
  64. package/dist/db/schema/session.mjs.map +1 -0
  65. package/dist/db/schema/shared.d.mts +2 -1
  66. package/dist/db/schema/shared.mjs +2 -1
  67. package/dist/db/schema/shared.mjs.map +1 -0
  68. package/dist/db/schema/user.d.mts +8 -4
  69. package/dist/db/schema/user.mjs +2 -1
  70. package/dist/db/schema/user.mjs.map +1 -0
  71. package/dist/db/schema/verification.d.mts +8 -4
  72. package/dist/db/schema/verification.mjs +2 -1
  73. package/dist/db/schema/verification.mjs.map +1 -0
  74. package/dist/db/type.d.mts +28 -2
  75. package/dist/env/color-depth.d.mts +2 -1
  76. package/dist/env/color-depth.mjs +2 -1
  77. package/dist/env/color-depth.mjs.map +1 -0
  78. package/dist/env/env-impl.d.mts +3 -2
  79. package/dist/env/env-impl.mjs +9 -8
  80. package/dist/env/env-impl.mjs.map +1 -0
  81. package/dist/env/logger.d.mts +2 -1
  82. package/dist/env/logger.mjs +3 -2
  83. package/dist/env/logger.mjs.map +1 -0
  84. package/dist/error/codes.d.mts +64 -181
  85. package/dist/error/codes.mjs +6 -2
  86. package/dist/error/codes.mjs.map +1 -0
  87. package/dist/error/index.d.mts +2 -1
  88. package/dist/error/index.mjs +2 -1
  89. package/dist/error/index.mjs.map +1 -0
  90. package/dist/index.d.mts +5 -4
  91. package/dist/oauth2/client-credentials-token.d.mts +25 -3
  92. package/dist/oauth2/client-credentials-token.mjs +15 -2
  93. package/dist/oauth2/client-credentials-token.mjs.map +1 -0
  94. package/dist/oauth2/create-authorization-url.d.mts +5 -2
  95. package/dist/oauth2/create-authorization-url.mjs +3 -1
  96. package/dist/oauth2/create-authorization-url.mjs.map +1 -0
  97. package/dist/oauth2/index.d.mts +4 -4
  98. package/dist/oauth2/index.mjs +4 -4
  99. package/dist/oauth2/oauth-provider.d.mts +3 -2
  100. package/dist/oauth2/refresh-access-token.d.mts +24 -4
  101. package/dist/oauth2/refresh-access-token.mjs +20 -2
  102. package/dist/oauth2/refresh-access-token.mjs.map +1 -0
  103. package/dist/oauth2/utils.d.mts +2 -1
  104. package/dist/oauth2/utils.mjs +2 -1
  105. package/dist/oauth2/utils.mjs.map +1 -0
  106. package/dist/oauth2/validate-authorization-code.d.mts +37 -4
  107. package/dist/oauth2/validate-authorization-code.mjs +25 -13
  108. package/dist/oauth2/validate-authorization-code.mjs.map +1 -0
  109. package/dist/oauth2/verify.d.mts +7 -13
  110. package/dist/oauth2/verify.mjs +2 -1
  111. package/dist/oauth2/verify.mjs.map +1 -0
  112. package/dist/social-providers/apple.d.mts +2 -1
  113. package/dist/social-providers/apple.mjs +22 -21
  114. package/dist/social-providers/apple.mjs.map +1 -0
  115. package/dist/social-providers/atlassian.d.mts +2 -1
  116. package/dist/social-providers/atlassian.mjs +2 -1
  117. package/dist/social-providers/atlassian.mjs.map +1 -0
  118. package/dist/social-providers/cognito.d.mts +2 -1
  119. package/dist/social-providers/cognito.mjs +4 -3
  120. package/dist/social-providers/cognito.mjs.map +1 -0
  121. package/dist/social-providers/discord.d.mts +2 -1
  122. package/dist/social-providers/discord.mjs +2 -1
  123. package/dist/social-providers/discord.mjs.map +1 -0
  124. package/dist/social-providers/dropbox.d.mts +2 -1
  125. package/dist/social-providers/dropbox.mjs +2 -1
  126. package/dist/social-providers/dropbox.mjs.map +1 -0
  127. package/dist/social-providers/facebook.d.mts +2 -1
  128. package/dist/social-providers/facebook.mjs +13 -12
  129. package/dist/social-providers/facebook.mjs.map +1 -0
  130. package/dist/social-providers/figma.d.mts +2 -1
  131. package/dist/social-providers/figma.mjs +2 -1
  132. package/dist/social-providers/figma.mjs.map +1 -0
  133. package/dist/social-providers/github.d.mts +3 -2
  134. package/dist/social-providers/github.mjs +23 -6
  135. package/dist/social-providers/github.mjs.map +1 -0
  136. package/dist/social-providers/gitlab.d.mts +2 -1
  137. package/dist/social-providers/gitlab.mjs +3 -2
  138. package/dist/social-providers/gitlab.mjs.map +1 -0
  139. package/dist/social-providers/google.d.mts +2 -1
  140. package/dist/social-providers/google.mjs +18 -13
  141. package/dist/social-providers/google.mjs.map +1 -0
  142. package/dist/social-providers/huggingface.d.mts +2 -1
  143. package/dist/social-providers/huggingface.mjs +3 -2
  144. package/dist/social-providers/huggingface.mjs.map +1 -0
  145. package/dist/social-providers/index.d.mts +61 -8
  146. package/dist/social-providers/index.mjs +5 -2
  147. package/dist/social-providers/index.mjs.map +1 -0
  148. package/dist/social-providers/kakao.d.mts +3 -2
  149. package/dist/social-providers/kakao.mjs +3 -2
  150. package/dist/social-providers/kakao.mjs.map +1 -0
  151. package/dist/social-providers/kick.d.mts +2 -1
  152. package/dist/social-providers/kick.mjs +2 -1
  153. package/dist/social-providers/kick.mjs.map +1 -0
  154. package/dist/social-providers/line.d.mts +2 -1
  155. package/dist/social-providers/line.mjs +3 -2
  156. package/dist/social-providers/line.mjs.map +1 -0
  157. package/dist/social-providers/linear.d.mts +2 -1
  158. package/dist/social-providers/linear.mjs +2 -1
  159. package/dist/social-providers/linear.mjs.map +1 -0
  160. package/dist/social-providers/linkedin.d.mts +2 -1
  161. package/dist/social-providers/linkedin.mjs +2 -1
  162. package/dist/social-providers/linkedin.mjs.map +1 -0
  163. package/dist/social-providers/microsoft-entra-id.d.mts +4 -1
  164. package/dist/social-providers/microsoft-entra-id.mjs +36 -2
  165. package/dist/social-providers/microsoft-entra-id.mjs.map +1 -0
  166. package/dist/social-providers/naver.d.mts +11 -20
  167. package/dist/social-providers/naver.mjs +3 -2
  168. package/dist/social-providers/naver.mjs.map +1 -0
  169. package/dist/social-providers/notion.d.mts +2 -1
  170. package/dist/social-providers/notion.mjs +3 -2
  171. package/dist/social-providers/notion.mjs.map +1 -0
  172. package/dist/social-providers/paybin.d.mts +2 -1
  173. package/dist/social-providers/paybin.mjs +3 -2
  174. package/dist/social-providers/paybin.mjs.map +1 -0
  175. package/dist/social-providers/paypal.d.mts +2 -1
  176. package/dist/social-providers/paypal.mjs +2 -1
  177. package/dist/social-providers/paypal.mjs.map +1 -0
  178. package/dist/social-providers/polar.d.mts +2 -1
  179. package/dist/social-providers/polar.mjs +3 -2
  180. package/dist/social-providers/polar.mjs.map +1 -0
  181. package/dist/social-providers/railway.d.mts +68 -0
  182. package/dist/social-providers/railway.mjs +78 -0
  183. package/dist/social-providers/railway.mjs.map +1 -0
  184. package/dist/social-providers/reddit.d.mts +2 -1
  185. package/dist/social-providers/reddit.mjs +2 -1
  186. package/dist/social-providers/reddit.mjs.map +1 -0
  187. package/dist/social-providers/roblox.d.mts +2 -1
  188. package/dist/social-providers/roblox.mjs +2 -1
  189. package/dist/social-providers/roblox.mjs.map +1 -0
  190. package/dist/social-providers/salesforce.d.mts +2 -1
  191. package/dist/social-providers/salesforce.mjs +2 -1
  192. package/dist/social-providers/salesforce.mjs.map +1 -0
  193. package/dist/social-providers/slack.d.mts +2 -1
  194. package/dist/social-providers/slack.mjs +2 -1
  195. package/dist/social-providers/slack.mjs.map +1 -0
  196. package/dist/social-providers/spotify.d.mts +2 -1
  197. package/dist/social-providers/spotify.mjs +2 -1
  198. package/dist/social-providers/spotify.mjs.map +1 -0
  199. package/dist/social-providers/tiktok.d.mts +3 -3
  200. package/dist/social-providers/tiktok.mjs +3 -2
  201. package/dist/social-providers/tiktok.mjs.map +1 -0
  202. package/dist/social-providers/twitch.d.mts +2 -1
  203. package/dist/social-providers/twitch.mjs +2 -1
  204. package/dist/social-providers/twitch.mjs.map +1 -0
  205. package/dist/social-providers/twitter.d.mts +14 -25
  206. package/dist/social-providers/twitter.mjs +2 -1
  207. package/dist/social-providers/twitter.mjs.map +1 -0
  208. package/dist/social-providers/vercel.d.mts +2 -1
  209. package/dist/social-providers/vercel.mjs +3 -2
  210. package/dist/social-providers/vercel.mjs.map +1 -0
  211. package/dist/social-providers/vk.d.mts +2 -1
  212. package/dist/social-providers/vk.mjs +2 -1
  213. package/dist/social-providers/vk.mjs.map +1 -0
  214. package/dist/social-providers/zoom.d.mts +3 -10
  215. package/dist/social-providers/zoom.mjs +2 -1
  216. package/dist/social-providers/zoom.mjs.map +1 -0
  217. package/dist/types/context.d.mts +53 -21
  218. package/dist/types/cookie.d.mts +2 -1
  219. package/dist/types/helper.d.mts +4 -1
  220. package/dist/types/index.d.mts +4 -3
  221. package/dist/types/init-options.d.mts +231 -159
  222. package/dist/types/plugin-client.d.mts +4 -1
  223. package/dist/types/plugin.d.mts +12 -11
  224. package/dist/types/secret.d.mts +12 -0
  225. package/dist/utils/db.d.mts +12 -0
  226. package/dist/utils/db.mjs +17 -0
  227. package/dist/utils/db.mjs.map +1 -0
  228. package/dist/utils/deprecate.d.mts +2 -2
  229. package/dist/utils/deprecate.mjs +2 -1
  230. package/dist/utils/deprecate.mjs.map +1 -0
  231. package/dist/utils/error-codes.d.mts +8 -6
  232. package/dist/utils/error-codes.mjs +3 -2
  233. package/dist/utils/error-codes.mjs.map +1 -0
  234. package/dist/utils/id.d.mts +2 -1
  235. package/dist/utils/id.mjs +2 -1
  236. package/dist/utils/id.mjs.map +1 -0
  237. package/dist/utils/ip.d.mts +2 -1
  238. package/dist/utils/ip.mjs +2 -1
  239. package/dist/utils/ip.mjs.map +1 -0
  240. package/dist/utils/json.d.mts +2 -1
  241. package/dist/utils/json.mjs +2 -1
  242. package/dist/utils/json.mjs.map +1 -0
  243. package/dist/utils/string.d.mts +2 -1
  244. package/dist/utils/string.mjs +2 -1
  245. package/dist/utils/string.mjs.map +1 -0
  246. package/dist/utils/url.d.mts +2 -1
  247. package/dist/utils/url.mjs +2 -1
  248. package/dist/utils/url.mjs.map +1 -0
  249. package/package.json +35 -13
  250. package/src/db/adapter/factory.ts +41 -73
  251. package/src/db/adapter/get-id-field.ts +1 -3
  252. package/src/db/adapter/index.ts +20 -15
  253. package/src/db/adapter/types.ts +2 -41
  254. package/src/db/get-tables.ts +48 -37
  255. package/src/db/index.ts +30 -5
  256. package/src/db/schema/account.ts +16 -3
  257. package/src/db/schema/rate-limit.ts +16 -1
  258. package/src/db/schema/session.ts +15 -3
  259. package/src/db/schema/user.ts +15 -3
  260. package/src/db/schema/verification.ts +16 -3
  261. package/src/db/test/get-tables.test.ts +33 -0
  262. package/src/db/type.ts +154 -1
  263. package/src/env/env-impl.ts +2 -2
  264. package/src/env/logger.ts +1 -1
  265. package/src/error/codes.ts +17 -0
  266. package/src/oauth2/client-credentials-token.ts +26 -2
  267. package/src/oauth2/create-authorization-url.ts +3 -1
  268. package/src/oauth2/index.ts +3 -0
  269. package/src/oauth2/oauth-provider.ts +1 -1
  270. package/src/oauth2/refresh-access-token.test.ts +90 -0
  271. package/src/oauth2/refresh-access-token.ts +37 -4
  272. package/src/oauth2/validate-authorization-code.ts +55 -24
  273. package/src/oauth2/validate-token.test.ts +107 -52
  274. package/src/social-providers/apple.ts +29 -29
  275. package/src/social-providers/cognito.ts +6 -5
  276. package/src/social-providers/facebook.ts +3 -3
  277. package/src/social-providers/github.ts +26 -4
  278. package/src/social-providers/gitlab.ts +1 -1
  279. package/src/social-providers/google.ts +18 -14
  280. package/src/social-providers/huggingface.ts +1 -1
  281. package/src/social-providers/index.ts +9 -5
  282. package/src/social-providers/kakao.ts +1 -1
  283. package/src/social-providers/line.ts +1 -1
  284. package/src/social-providers/microsoft-entra-id.ts +84 -1
  285. package/src/social-providers/naver.ts +1 -1
  286. package/src/social-providers/notion.ts +1 -1
  287. package/src/social-providers/paybin.ts +1 -5
  288. package/src/social-providers/polar.ts +1 -1
  289. package/src/social-providers/railway.ts +100 -0
  290. package/src/social-providers/tiktok.ts +2 -1
  291. package/src/social-providers/vercel.ts +1 -1
  292. package/src/social-providers/zoom.ts +0 -8
  293. package/src/types/context.ts +74 -14
  294. package/src/types/helper.ts +9 -0
  295. package/src/types/index.ts +14 -2
  296. package/src/types/init-options.ts +294 -186
  297. package/src/types/plugin-client.ts +1 -0
  298. package/src/types/plugin.ts +11 -6
  299. package/src/types/secret.ts +8 -0
  300. package/src/utils/db.ts +20 -0
  301. package/src/utils/deprecate.test.ts +0 -1
  302. package/src/utils/error-codes.ts +12 -9
  303. package/.turbo/turbo-build.log +0 -182
  304. package/tsconfig.json +0 -7
  305. package/tsdown.config.ts +0 -32
  306. package/vitest.config.ts +0 -3
@@ -1,10 +1,12 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
+ import { logger } from "../env";
2
3
  import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
4
  import {
4
5
  createAuthorizationURL,
6
+ getOAuth2Tokens,
5
7
  refreshAccessToken,
6
- validateAuthorizationCode,
7
8
  } from "../oauth2";
9
+ import { createAuthorizationCodeRequest } from "../oauth2/validate-authorization-code";
8
10
 
9
11
  export interface GithubProfile {
10
12
  login: string;
@@ -86,13 +88,33 @@ export const github = (options: GithubOptions) => {
86
88
  });
87
89
  },
88
90
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
89
- return validateAuthorizationCode({
91
+ const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
90
92
  code,
91
93
  codeVerifier,
92
94
  redirectURI,
93
95
  options,
94
- tokenEndpoint,
95
96
  });
97
+
98
+ const { data, error } = await betterFetch<
99
+ | { access_token: string; token_type: string; scope: string }
100
+ | { error: string; error_description?: string; error_uri?: string }
101
+ >(tokenEndpoint, {
102
+ method: "POST",
103
+ body: body,
104
+ headers: requestHeaders,
105
+ });
106
+
107
+ if (error) {
108
+ logger.error("GitHub OAuth token exchange failed:", error);
109
+ return null;
110
+ }
111
+
112
+ if ("error" in data) {
113
+ logger.error("GitHub OAuth token exchange failed:", data);
114
+ return null;
115
+ }
116
+
117
+ return getOAuth2Tokens(data);
96
118
  },
97
119
  refreshAccessToken: options.refreshAccessToken
98
120
  ? options.refreshAccessToken
@@ -148,7 +170,7 @@ export const github = (options: GithubOptions) => {
148
170
  return {
149
171
  user: {
150
172
  id: profile.id,
151
- name: profile.name || profile.login,
173
+ name: profile.name || profile.login || "",
152
174
  email: profile.email,
153
175
  image: profile.avatar_url,
154
176
  emailVerified,
@@ -141,7 +141,7 @@ export const gitlab = (options: GitlabOptions) => {
141
141
  return {
142
142
  user: {
143
143
  id: profile.id,
144
- name: profile.name ?? profile.username,
144
+ name: profile.name ?? profile.username ?? "",
145
145
  email: profile.email,
146
146
  image: profile.avatar_url,
147
147
  emailVerified: profile.email_verified ?? false,
@@ -81,7 +81,7 @@ export const google = (options: GoogleOptions) => {
81
81
  const url = await createAuthorizationURL({
82
82
  id: "google",
83
83
  options,
84
- authorizationEndpoint: "https://accounts.google.com/o/oauth2/auth",
84
+ authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
85
85
  scopes: _scopes,
86
86
  state,
87
87
  codeVerifier,
@@ -116,7 +116,7 @@ export const google = (options: GoogleOptions) => {
116
116
  clientKey: options.clientKey,
117
117
  clientSecret: options.clientSecret,
118
118
  },
119
- tokenEndpoint: "https://www.googleapis.com/oauth2/v4/token",
119
+ tokenEndpoint: "https://oauth2.googleapis.com/token",
120
120
  });
121
121
  },
122
122
  async verifyIdToken(token, nonce) {
@@ -130,22 +130,26 @@ export const google = (options: GoogleOptions) => {
130
130
  // Verify JWT integrity
131
131
  // See https://developers.google.com/identity/sign-in/web/backend-auth#verify-the-integrity-of-the-id-token
132
132
 
133
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
134
- if (!kid || !jwtAlg) return false;
133
+ try {
134
+ const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
135
+ if (!kid || !jwtAlg) return false;
135
136
 
136
- const publicKey = await getGooglePublicKey(kid);
137
- const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
138
- algorithms: [jwtAlg],
139
- issuer: ["https://accounts.google.com", "accounts.google.com"],
140
- audience: options.clientId,
141
- maxTokenAge: "1h",
142
- });
137
+ const publicKey = await getGooglePublicKey(kid);
138
+ const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
139
+ algorithms: [jwtAlg],
140
+ issuer: ["https://accounts.google.com", "accounts.google.com"],
141
+ audience: options.clientId,
142
+ maxTokenAge: "1h",
143
+ });
144
+
145
+ if (nonce && jwtClaims.nonce !== nonce) {
146
+ return false;
147
+ }
143
148
 
144
- if (nonce && jwtClaims.nonce !== nonce) {
149
+ return true;
150
+ } catch {
145
151
  return false;
146
152
  }
147
-
148
- return true;
149
153
  },
150
154
  async getUserInfo(token) {
151
155
  if (options.getUserInfo) {
@@ -104,7 +104,7 @@ export const huggingface = (options: HuggingFaceOptions) => {
104
104
  return {
105
105
  user: {
106
106
  id: profile.sub,
107
- name: profile.name || profile.preferred_username,
107
+ name: profile.name || profile.preferred_username || "",
108
108
  email: profile.email,
109
109
  image: profile.picture,
110
110
  emailVerified: profile.email_verified ?? false,
@@ -1,4 +1,5 @@
1
1
  import * as z from "zod";
2
+ import type { AwaitableFunction } from "../types";
2
3
  import { apple } from "./apple";
3
4
  import { atlassian } from "./atlassian";
4
5
  import { cognito } from "./cognito";
@@ -21,6 +22,7 @@ import { notion } from "./notion";
21
22
  import { paybin } from "./paybin";
22
23
  import { paypal } from "./paypal";
23
24
  import { polar } from "./polar";
25
+ import { railway } from "./railway";
24
26
  import { reddit } from "./reddit";
25
27
  import { roblox } from "./roblox";
26
28
  import { salesforce } from "./salesforce";
@@ -66,6 +68,7 @@ export const socialProviders = {
66
68
  paybin,
67
69
  paypal,
68
70
  polar,
71
+ railway,
69
72
  vercel,
70
73
  };
71
74
 
@@ -81,11 +84,11 @@ export const SocialProviderListEnum = z
81
84
  export type SocialProvider = z.infer<typeof SocialProviderListEnum>;
82
85
 
83
86
  export type SocialProviders = {
84
- [K in SocialProviderList[number]]?: Parameters<
85
- (typeof socialProviders)[K]
86
- >[0] & {
87
- enabled?: boolean | undefined;
88
- };
87
+ [K in SocialProviderList[number]]?: AwaitableFunction<
88
+ Parameters<(typeof socialProviders)[K]>[0] & {
89
+ enabled?: boolean | undefined;
90
+ }
91
+ >;
89
92
  };
90
93
 
91
94
  export * from "./apple";
@@ -112,6 +115,7 @@ export * from "./notion";
112
115
  export * from "./paybin";
113
116
  export * from "./paypal";
114
117
  export * from "./polar";
118
+ export * from "./railway";
115
119
  export * from "./reddit";
116
120
  export * from "./roblox";
117
121
  export * from "./salesforce";
@@ -161,7 +161,7 @@ export const kakao = (options: KakaoOptions) => {
161
161
  const kakaoProfile = account.profile || {};
162
162
  const user = {
163
163
  id: String(profile.id),
164
- name: kakaoProfile.nickname || account.name || undefined,
164
+ name: kakaoProfile.nickname || account.name || "",
165
165
  email: account.email,
166
166
  image:
167
167
  kakaoProfile.profile_image_url || kakaoProfile.thumbnail_image_url,
@@ -147,7 +147,7 @@ export const line = (options: LineOptions) => {
147
147
  const userMap = await options.mapProfileToUser?.(profile as any);
148
148
  // ID preference order
149
149
  const id = (profile as any).sub || (profile as any).userId;
150
- const name = (profile as any).name || (profile as any).displayName;
150
+ const name = (profile as any).name || (profile as any).displayName || "";
151
151
  const image =
152
152
  (profile as any).picture || (profile as any).pictureUrl || undefined;
153
153
  const email = (profile as any).email;
@@ -1,7 +1,8 @@
1
1
  import { base64 } from "@better-auth/utils/base64";
2
2
  import { betterFetch } from "@better-fetch/fetch";
3
- import { decodeJwt } from "jose";
3
+ import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
4
4
  import { logger } from "../env";
5
+ import { APIError } from "../error";
5
6
  import type { OAuthProvider, ProviderOptions } from "../oauth2";
6
7
  import {
7
8
  createAuthorizationURL,
@@ -174,6 +175,56 @@ export const microsoft = (options: MicrosoftOptions) => {
174
175
  tokenEndpoint,
175
176
  });
176
177
  },
178
+ async verifyIdToken(token, nonce) {
179
+ if (options.disableIdTokenSignIn) {
180
+ return false;
181
+ }
182
+ if (options.verifyIdToken) {
183
+ return options.verifyIdToken(token, nonce);
184
+ }
185
+
186
+ try {
187
+ const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
188
+ if (!kid || !jwtAlg) return false;
189
+
190
+ const publicKey = await getMicrosoftPublicKey(kid, tenant, authority);
191
+ const verifyOptions: {
192
+ algorithms: [string];
193
+ audience: string;
194
+ maxTokenAge: string;
195
+ issuer?: string;
196
+ } = {
197
+ algorithms: [jwtAlg],
198
+ audience: options.clientId,
199
+ maxTokenAge: "1h",
200
+ };
201
+ /**
202
+ * Issuer varies per user's tenant for multi-tenant endpoints, so only validate for specific tenants.
203
+ * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
204
+ */
205
+ if (
206
+ tenant !== "common" &&
207
+ tenant !== "organizations" &&
208
+ tenant !== "consumers"
209
+ ) {
210
+ verifyOptions.issuer = `${authority}/${tenant}/v2.0`;
211
+ }
212
+ const { payload: jwtClaims } = await jwtVerify(
213
+ token,
214
+ publicKey,
215
+ verifyOptions,
216
+ );
217
+
218
+ if (nonce && jwtClaims.nonce !== nonce) {
219
+ return false;
220
+ }
221
+
222
+ return true;
223
+ } catch (error) {
224
+ logger.error("Failed to verify ID token:", error);
225
+ return false;
226
+ }
227
+ },
177
228
  async getUserInfo(token) {
178
229
  if (options.getUserInfo) {
179
230
  return options.getUserInfo(token);
@@ -257,3 +308,35 @@ export const microsoft = (options: MicrosoftOptions) => {
257
308
  options,
258
309
  } satisfies OAuthProvider;
259
310
  };
311
+
312
+ export const getMicrosoftPublicKey = async (
313
+ kid: string,
314
+ tenant: string,
315
+ authority: string,
316
+ ) => {
317
+ const { data } = await betterFetch<{
318
+ keys: Array<{
319
+ kid: string;
320
+ alg: string;
321
+ kty: string;
322
+ use: string;
323
+ n: string;
324
+ e: string;
325
+ x5c?: string[];
326
+ x5t?: string;
327
+ }>;
328
+ }>(`${authority}/${tenant}/discovery/v2.0/keys`);
329
+
330
+ if (!data?.keys) {
331
+ throw new APIError("BAD_REQUEST", {
332
+ message: "Keys not found",
333
+ });
334
+ }
335
+
336
+ const jwk = data.keys.find((key) => key.kid === kid);
337
+ if (!jwk) {
338
+ throw new Error(`JWK with kid ${kid} not found`);
339
+ }
340
+
341
+ return await importJWK(jwk, jwk.alg);
342
+ };
@@ -96,7 +96,7 @@ export const naver = (options: NaverOptions) => {
96
96
  const res = profile.response || {};
97
97
  const user = {
98
98
  id: res.id,
99
- name: res.name || res.nickname,
99
+ name: res.name || res.nickname || "",
100
100
  email: res.email,
101
101
  image: res.profile_image,
102
102
  emailVerified: false,
@@ -94,7 +94,7 @@ export const notion = (options: NotionOptions) => {
94
94
  return {
95
95
  user: {
96
96
  id: userProfile.id,
97
- name: userProfile.name || "Notion User",
97
+ name: userProfile.name || "",
98
98
  email: userProfile.person?.email || null,
99
99
  image: userProfile.avatar_url,
100
100
  emailVerified: false,
@@ -104,11 +104,7 @@ export const paybin = (options: PaybinOptions) => {
104
104
  return {
105
105
  user: {
106
106
  id: user.sub,
107
- name:
108
- user.name ||
109
- user.preferred_username ||
110
- (user.email ? user.email.split("@")[0] : "User") ||
111
- "User",
107
+ name: user.name || user.preferred_username || "",
112
108
  email: user.email,
113
109
  image: user.picture,
114
110
  emailVerified: user.email_verified || false,
@@ -96,7 +96,7 @@ export const polar = (options: PolarOptions) => {
96
96
  return {
97
97
  user: {
98
98
  id: profile.id,
99
- name: profile.public_name || profile.username,
99
+ name: profile.public_name || profile.username || "",
100
100
  email: profile.email,
101
101
  image: profile.avatar_url,
102
102
  emailVerified: profile.email_verified ?? false,
@@ -0,0 +1,100 @@
1
+ import { betterFetch } from "@better-fetch/fetch";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
+ import {
4
+ createAuthorizationURL,
5
+ refreshAccessToken,
6
+ validateAuthorizationCode,
7
+ } from "../oauth2";
8
+
9
+ const authorizationEndpoint = "https://backboard.railway.com/oauth/auth";
10
+ const tokenEndpoint = "https://backboard.railway.com/oauth/token";
11
+ const userinfoEndpoint = "https://backboard.railway.com/oauth/me";
12
+
13
+ export interface RailwayProfile {
14
+ /** The user's unique ID (OAuth `sub` claim). */
15
+ sub: string;
16
+ /** The user's email address. */
17
+ email: string;
18
+ /** The user's display name. */
19
+ name: string;
20
+ /** URL of the user's profile picture. */
21
+ picture: string;
22
+ }
23
+
24
+ export interface RailwayOptions extends ProviderOptions<RailwayProfile> {
25
+ clientId: string;
26
+ }
27
+
28
+ export const railway = (options: RailwayOptions) => {
29
+ return {
30
+ id: "railway",
31
+ name: "Railway",
32
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
33
+ const _scopes = options.disableDefaultScope
34
+ ? []
35
+ : ["openid", "email", "profile"];
36
+ if (options.scope) _scopes.push(...options.scope);
37
+ if (scopes) _scopes.push(...scopes);
38
+ return createAuthorizationURL({
39
+ id: "railway",
40
+ options,
41
+ authorizationEndpoint,
42
+ scopes: _scopes,
43
+ state,
44
+ codeVerifier,
45
+ redirectURI,
46
+ });
47
+ },
48
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
49
+ return validateAuthorizationCode({
50
+ code,
51
+ codeVerifier,
52
+ redirectURI,
53
+ options,
54
+ tokenEndpoint,
55
+ authentication: "basic",
56
+ });
57
+ },
58
+ refreshAccessToken: options.refreshAccessToken
59
+ ? options.refreshAccessToken
60
+ : async (refreshToken) => {
61
+ return refreshAccessToken({
62
+ refreshToken,
63
+ options: {
64
+ clientId: options.clientId,
65
+ clientKey: options.clientKey,
66
+ clientSecret: options.clientSecret,
67
+ },
68
+ tokenEndpoint,
69
+ authentication: "basic",
70
+ });
71
+ },
72
+ async getUserInfo(token) {
73
+ if (options.getUserInfo) {
74
+ return options.getUserInfo(token);
75
+ }
76
+ const { data: profile, error } = await betterFetch<RailwayProfile>(
77
+ userinfoEndpoint,
78
+ { headers: { authorization: `Bearer ${token.accessToken}` } },
79
+ );
80
+ if (error || !profile) {
81
+ return null;
82
+ }
83
+ const userMap = await options.mapProfileToUser?.(profile);
84
+ // Railway does not provide an email_verified claim.
85
+ // We default to false for security consistency.
86
+ return {
87
+ user: {
88
+ id: profile.sub,
89
+ name: profile.name,
90
+ email: profile.email,
91
+ image: profile.picture,
92
+ emailVerified: false,
93
+ ...userMap,
94
+ },
95
+ data: profile,
96
+ };
97
+ },
98
+ options,
99
+ } satisfies OAuthProvider<RailwayProfile>;
100
+ };
@@ -197,7 +197,8 @@ export const tiktok = (options: TiktokOptions) => {
197
197
  user: {
198
198
  email: profile.data.user.email || profile.data.user.username,
199
199
  id: profile.data.user.open_id,
200
- name: profile.data.user.display_name || profile.data.user.username,
200
+ name:
201
+ profile.data.user.display_name || profile.data.user.username || "",
201
202
  image: profile.data.user.avatar_large_url,
202
203
  emailVerified: false,
203
204
  },
@@ -73,7 +73,7 @@ export const vercel = (options: VercelOptions) => {
73
73
  return {
74
74
  user: {
75
75
  id: profile.sub,
76
- name: profile.name ?? profile.preferred_username,
76
+ name: profile.name ?? profile.preferred_username ?? "",
77
77
  email: profile.email,
78
78
  image: profile.picture,
79
79
  emailVerified: profile.email_verified ?? false,
@@ -96,11 +96,6 @@ export interface ZoomProfile extends Record<string, any> {
96
96
  login_types: LoginType[];
97
97
  /** User's personal meeting URL (Example: "example.com") */
98
98
  personal_meeting_url: string;
99
- /** This field has been deprecated and will not be supported in the future.
100
- * Use the phone_numbers field instead of this field.
101
- * The user's phone number (Example: "+1 800000000") */
102
- // @deprecated true
103
- phone_number?: string | undefined;
104
99
  /** The URL for user's profile picture (Example: "example.com") */
105
100
  pic_url: string;
106
101
  /** Personal Meeting ID (PMI) (Example: 3542471135) */
@@ -131,9 +126,6 @@ export interface ZoomProfile extends Record<string, any> {
131
126
  employee_unique_id?: string | undefined;
132
127
  /** The manager for the user (Example: "thill@example.com") */
133
128
  manager?: string | undefined;
134
- /** The user's country for the company phone number (Example: "US")
135
- * @deprecated true */
136
- phone_country?: string | undefined;
137
129
  /** The phone number's ISO country code (Example: "+1") */
138
130
  phone_numbers?: PhoneNumber[] | undefined;
139
131
  /** The user's plan type (Example: "1") */
@@ -12,29 +12,66 @@ import type { DBAdapter, Where } from "../db/adapter";
12
12
  import type { createLogger } from "../env";
13
13
  import type { OAuthProvider } from "../oauth2";
14
14
  import type { BetterAuthCookie, BetterAuthCookies } from "./cookie";
15
- import type { LiteralString } from "./helper";
15
+ import type { Awaitable, LiteralString } from "./helper";
16
16
  import type {
17
17
  BetterAuthOptions,
18
18
  BetterAuthRateLimitOptions,
19
19
  } from "./init-options";
20
20
  import type { BetterAuthPlugin } from "./plugin";
21
+ import type { SecretConfig } from "./secret";
22
+
23
+ /**
24
+ * @internal
25
+ */
26
+ type InferPluginID<O extends BetterAuthOptions> =
27
+ O["plugins"] extends Array<infer P>
28
+ ? P extends BetterAuthPlugin
29
+ ? P["id"]
30
+ : never
31
+ : never;
32
+
33
+ /**
34
+ * @internal
35
+ */
36
+ type InferPluginOptions<
37
+ O extends BetterAuthOptions,
38
+ ID extends BetterAuthPluginRegistryIdentifier | LiteralString,
39
+ > =
40
+ O["plugins"] extends Array<infer P>
41
+ ? P extends BetterAuthPlugin
42
+ ? P["id"] extends ID
43
+ ? P extends { options: infer O }
44
+ ? O
45
+ : never
46
+ : never
47
+ : never
48
+ : never;
21
49
 
22
50
  /**
23
51
  * Mutators are defined in each plugin
24
52
  *
25
53
  * @example
26
54
  * ```ts
55
+ * interface MyPluginOptions {
56
+ * useFeature: boolean
57
+ * }
58
+ *
59
+ * const createMyPlugin = <Options extends MyPluginOptions>(options?: Options) => ({
60
+ * id: 'my-plugin',
61
+ * options,
62
+ * } satisfies BetterAuthPlugin);
63
+ *
27
64
  * declare module "@better-auth/core" {
28
- * interface BetterAuthPluginRegistry<Auth, Context> {
29
- * 'jwt': {
30
- * creator: typeof jwt
65
+ * interface BetterAuthPluginRegistry<AuthOptions, Options> {
66
+ * 'my-plugin': {
67
+ * creator: Options extends MyPluginOptions ? typeof createMyPlugin<Options>: typeof createMyPlugin
31
68
  * }
32
69
  * }
33
70
  * }
34
71
  * ```
35
72
  */
36
73
  // biome-ignore lint/correctness/noUnusedVariables: Auth and Context is used in the declaration merging
37
- export interface BetterAuthPluginRegistry<Auth, Context> {}
74
+ export interface BetterAuthPluginRegistry<AuthOptions, Options> {}
38
75
  export type BetterAuthPluginRegistryIdentifier = keyof BetterAuthPluginRegistry<
39
76
  unknown,
40
77
  unknown
@@ -67,7 +104,10 @@ export interface InternalAdapter<
67
104
  T,
68
105
  ): Promise<T & Account>;
69
106
 
70
- listSessions(userId: string): Promise<Session[]>;
107
+ listSessions(
108
+ userId: string,
109
+ options?: { onlyActiveSessions?: boolean | undefined } | undefined,
110
+ ): Promise<Session[]>;
71
111
 
72
112
  listUsers(
73
113
  limit?: number | undefined,
@@ -94,6 +134,11 @@ export interface InternalAdapter<
94
134
 
95
135
  findSessions(
96
136
  sessionTokens: string[],
137
+ options?:
138
+ | {
139
+ onlyActiveSessions?: boolean | undefined;
140
+ }
141
+ | undefined,
97
142
  ): Promise<{ session: Session; user: User }[]>;
98
143
 
99
144
  updateSession(
@@ -183,12 +228,21 @@ type CheckPasswordFn<Options extends BetterAuthOptions = BetterAuthOptions> = (
183
228
  ctx: GenericEndpointContext<Options>,
184
229
  ) => Promise<boolean>;
185
230
 
186
- export type PluginContext = {
187
- getPlugin: <ID extends BetterAuthPluginRegistryIdentifier | LiteralString>(
231
+ export type PluginContext<Options extends BetterAuthOptions> = {
232
+ getPlugin: <
233
+ ID extends BetterAuthPluginRegistryIdentifier | LiteralString,
234
+ PluginOptions extends InferPluginOptions<Options, ID>,
235
+ >(
188
236
  pluginId: ID,
189
237
  ) =>
190
238
  | (ID extends BetterAuthPluginRegistryIdentifier
191
- ? ReturnType<BetterAuthPluginRegistry<unknown, unknown>[ID]["creator"]>
239
+ ? BetterAuthPluginRegistry<Options, PluginOptions>[ID] extends {
240
+ creator: infer C;
241
+ }
242
+ ? C extends (...args: any[]) => infer R
243
+ ? R
244
+ : never
245
+ : never
192
246
  : BetterAuthPlugin)
193
247
  | null;
194
248
  /**
@@ -206,7 +260,7 @@ export type PluginContext = {
206
260
  */
207
261
  hasPlugin: <ID extends BetterAuthPluginRegistryIdentifier | LiteralString>(
208
262
  pluginId: ID,
209
- ) => boolean;
263
+ ) => ID extends InferPluginID<Options> ? true : boolean;
210
264
  };
211
265
 
212
266
  export type InfoContext = {
@@ -216,10 +270,15 @@ export type InfoContext = {
216
270
  };
217
271
 
218
272
  export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
219
- PluginContext &
273
+ PluginContext<Options> &
220
274
  InfoContext & {
221
275
  options: Options;
222
276
  trustedOrigins: string[];
277
+ /**
278
+ * Resolved list of trusted providers for account linking.
279
+ * Populated from "account.accountLinking.trustedProviders" (supports static array or async function).
280
+ */
281
+ trustedProviders: string[];
223
282
  /**
224
283
  * Verifies whether url is a trusted origin according to the "trustedOrigins" configuration
225
284
  * @param url The url to verify against the "trustedOrigins" configuration
@@ -281,6 +340,7 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
281
340
  internalAdapter: InternalAdapter<Options>;
282
341
  createAuthCookie: CreateCookieGetterFn;
283
342
  secret: string;
343
+ secretConfig: string | SecretConfig;
284
344
  sessionConfig: {
285
345
  updateAge: number;
286
346
  expiresIn: number;
@@ -341,7 +401,7 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
341
401
  * This is inferred from the `options.advanced?.backgroundTasks?.handler` option.
342
402
  * Defaults to a no-op that just runs the promise.
343
403
  */
344
- runInBackground: (promise: Promise<void>) => void;
404
+ runInBackground: (promise: Promise<unknown>) => void;
345
405
  /**
346
406
  * Runs a task in the background if `runInBackground` is configured,
347
407
  * otherwise awaits the task directly.
@@ -351,6 +411,6 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
351
411
  * mitigation), but still ensure the operation completes.
352
412
  */
353
413
  runInBackgroundOrAwait: (
354
- promise: Promise<unknown> | Promise<void> | void | unknown,
355
- ) => Promise<unknown>;
414
+ promise: Promise<unknown> | void,
415
+ ) => Awaitable<unknown>;
356
416
  };
@@ -8,6 +8,7 @@ export type Primitive =
8
8
  | undefined;
9
9
 
10
10
  export type Awaitable<T> = T | Promise<T>;
11
+ export type AwaitableFunction<T> = T | (() => Awaitable<T>);
11
12
  export type LiteralString = "" | (string & Record<never, never>);
12
13
  export type LiteralUnion<LiteralType, BaseType extends Primitive> =
13
14
  | LiteralType
@@ -16,3 +17,11 @@ export type LiteralUnion<LiteralType, BaseType extends Primitive> =
16
17
  export type Prettify<T> = {
17
18
  [K in keyof T]: T[K];
18
19
  } & {};
20
+
21
+ export type UnionToIntersection<U> = (
22
+ U extends any
23
+ ? (k: U) => void
24
+ : never
25
+ ) extends (k: infer I) => void
26
+ ? I
27
+ : never;