@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
@@ -0,0 +1,90 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("@better-fetch/fetch", () => ({
4
+ betterFetch: vi.fn(),
5
+ }));
6
+
7
+ import { betterFetch } from "@better-fetch/fetch";
8
+ import { refreshAccessToken } from "./refresh-access-token";
9
+
10
+ const mockedBetterFetch = vi.mocked(betterFetch);
11
+
12
+ describe("refreshAccessToken", () => {
13
+ it("should set accessTokenExpiresAt when expires_in is returned", async () => {
14
+ const now = Date.now();
15
+ mockedBetterFetch.mockResolvedValueOnce({
16
+ data: {
17
+ access_token: "new-access-token",
18
+ refresh_token: "new-refresh-token",
19
+ expires_in: 3600,
20
+ token_type: "Bearer",
21
+ },
22
+ error: null,
23
+ });
24
+
25
+ const tokens = await refreshAccessToken({
26
+ refreshToken: "old-refresh-token",
27
+ options: { clientId: "test-client", clientSecret: "test-secret" },
28
+ tokenEndpoint: "https://example.com/token",
29
+ });
30
+
31
+ expect(tokens.accessToken).toBe("new-access-token");
32
+ expect(tokens.refreshToken).toBe("new-refresh-token");
33
+ expect(tokens.accessTokenExpiresAt).toBeInstanceOf(Date);
34
+ expect(tokens.accessTokenExpiresAt!.getTime()).toBeGreaterThanOrEqual(
35
+ now + 3600 * 1000 - 1000,
36
+ );
37
+ expect(tokens.refreshTokenExpiresAt).toBeUndefined();
38
+ });
39
+
40
+ /**
41
+ * @see https://github.com/better-auth/better-auth/issues/7682
42
+ */
43
+ it("should set refreshTokenExpiresAt when refresh_token_expires_in is returned", async () => {
44
+ const now = Date.now();
45
+ mockedBetterFetch.mockResolvedValueOnce({
46
+ data: {
47
+ access_token: "new-access-token",
48
+ refresh_token: "new-refresh-token",
49
+ expires_in: 3600,
50
+ refresh_token_expires_in: 86400,
51
+ token_type: "Bearer",
52
+ },
53
+ error: null,
54
+ });
55
+
56
+ const tokens = await refreshAccessToken({
57
+ refreshToken: "old-refresh-token",
58
+ options: { clientId: "test-client", clientSecret: "test-secret" },
59
+ tokenEndpoint: "https://example.com/token",
60
+ });
61
+
62
+ expect(tokens.accessToken).toBe("new-access-token");
63
+ expect(tokens.refreshToken).toBe("new-refresh-token");
64
+ expect(tokens.accessTokenExpiresAt).toBeInstanceOf(Date);
65
+ expect(tokens.refreshTokenExpiresAt).toBeInstanceOf(Date);
66
+ expect(tokens.refreshTokenExpiresAt!.getTime()).toBeGreaterThanOrEqual(
67
+ now + 86400 * 1000 - 1000,
68
+ );
69
+ });
70
+
71
+ it("should not set refreshTokenExpiresAt when refresh_token_expires_in is not returned", async () => {
72
+ mockedBetterFetch.mockResolvedValueOnce({
73
+ data: {
74
+ access_token: "new-access-token",
75
+ refresh_token: "new-refresh-token",
76
+ expires_in: 3600,
77
+ token_type: "Bearer",
78
+ },
79
+ error: null,
80
+ });
81
+
82
+ const tokens = await refreshAccessToken({
83
+ refreshToken: "old-refresh-token",
84
+ options: { clientId: "test-client", clientSecret: "test-secret" },
85
+ tokenEndpoint: "https://example.com/token",
86
+ });
87
+
88
+ expect(tokens.refreshTokenExpiresAt).toBeUndefined();
89
+ });
90
+ });
@@ -1,7 +1,34 @@
1
1
  import { base64 } from "@better-auth/utils/base64";
2
2
  import { betterFetch } from "@better-fetch/fetch";
3
+ import type { AwaitableFunction } from "../types";
3
4
  import type { OAuth2Tokens, ProviderOptions } from "./oauth-provider";
4
5
 
6
+ export async function refreshAccessTokenRequest({
7
+ refreshToken,
8
+ options,
9
+ authentication,
10
+ extraParams,
11
+ resource,
12
+ }: {
13
+ refreshToken: string;
14
+ options: AwaitableFunction<Partial<ProviderOptions>>;
15
+ authentication?: ("basic" | "post") | undefined;
16
+ extraParams?: Record<string, string> | undefined;
17
+ resource?: (string | string[]) | undefined;
18
+ }) {
19
+ options = typeof options === "function" ? await options() : options;
20
+ return createRefreshAccessTokenRequest({
21
+ refreshToken,
22
+ options,
23
+ authentication,
24
+ extraParams,
25
+ resource,
26
+ });
27
+ }
28
+
29
+ /**
30
+ * @deprecated use async'd refreshAccessTokenRequest instead
31
+ */
5
32
  export function createRefreshAccessTokenRequest({
6
33
  refreshToken,
7
34
  options,
@@ -10,7 +37,7 @@ export function createRefreshAccessTokenRequest({
10
37
  resource,
11
38
  }: {
12
39
  refreshToken: string;
13
- options: Partial<ProviderOptions>;
40
+ options: ProviderOptions;
14
41
  authentication?: ("basic" | "post") | undefined;
15
42
  extraParams?: Record<string, string> | undefined;
16
43
  resource?: (string | string[]) | undefined;
@@ -80,10 +107,8 @@ export async function refreshAccessToken({
80
107
  tokenEndpoint: string;
81
108
  authentication?: ("basic" | "post") | undefined;
82
109
  extraParams?: Record<string, string> | undefined;
83
- /** @deprecated always "refresh_token" */
84
- grantType?: string | undefined;
85
110
  }): Promise<OAuth2Tokens> {
86
- const { body, headers } = createRefreshAccessTokenRequest({
111
+ const { body, headers } = await createRefreshAccessTokenRequest({
87
112
  refreshToken,
88
113
  options,
89
114
  authentication,
@@ -94,6 +119,7 @@ export async function refreshAccessToken({
94
119
  access_token: string;
95
120
  refresh_token?: string | undefined;
96
121
  expires_in?: number | undefined;
122
+ refresh_token_expires_in?: number | undefined;
97
123
  token_type?: string | undefined;
98
124
  scope?: string | undefined;
99
125
  id_token?: string | undefined;
@@ -120,5 +146,12 @@ export async function refreshAccessToken({
120
146
  );
121
147
  }
122
148
 
149
+ if (data.refresh_token_expires_in) {
150
+ const now = new Date();
151
+ tokens.refreshTokenExpiresAt = new Date(
152
+ now.getTime() + data.refresh_token_expires_in * 1000,
153
+ );
154
+ }
155
+
123
156
  return tokens;
124
157
  }
@@ -1,10 +1,48 @@
1
1
  import { base64 } from "@better-auth/utils/base64";
2
2
  import { betterFetch } from "@better-fetch/fetch";
3
- import type { JWK } from "jose";
4
- import { decodeProtectedHeader, importJWK, jwtVerify } from "jose";
3
+ import { createRemoteJWKSet, jwtVerify } from "jose";
4
+ import type { AwaitableFunction } from "../types";
5
5
  import type { ProviderOptions } from "./index";
6
6
  import { getOAuth2Tokens } from "./index";
7
7
 
8
+ export async function authorizationCodeRequest({
9
+ code,
10
+ codeVerifier,
11
+ redirectURI,
12
+ options,
13
+ authentication,
14
+ deviceId,
15
+ headers,
16
+ additionalParams = {},
17
+ resource,
18
+ }: {
19
+ code: string;
20
+ redirectURI: string;
21
+ options: AwaitableFunction<Partial<ProviderOptions>>;
22
+ codeVerifier?: string | undefined;
23
+ deviceId?: string | undefined;
24
+ authentication?: ("basic" | "post") | undefined;
25
+ headers?: Record<string, string> | undefined;
26
+ additionalParams?: Record<string, string> | undefined;
27
+ resource?: (string | string[]) | undefined;
28
+ }) {
29
+ options = typeof options === "function" ? await options() : options;
30
+ return createAuthorizationCodeRequest({
31
+ code,
32
+ codeVerifier,
33
+ redirectURI,
34
+ options,
35
+ authentication,
36
+ deviceId,
37
+ headers,
38
+ additionalParams,
39
+ resource,
40
+ });
41
+ }
42
+
43
+ /**
44
+ * @deprecated use async'd authorizationCodeRequest instead
45
+ */
8
46
  export function createAuthorizationCodeRequest({
9
47
  code,
10
48
  codeVerifier,
@@ -32,6 +70,7 @@ export function createAuthorizationCodeRequest({
32
70
  accept: "application/json",
33
71
  ...headers,
34
72
  };
73
+
35
74
  body.set("grant_type", "authorization_code");
36
75
  body.set("code", code);
37
76
  codeVerifier && body.set("code_verifier", codeVerifier);
@@ -91,7 +130,7 @@ export async function validateAuthorizationCode({
91
130
  }: {
92
131
  code: string;
93
132
  redirectURI: string;
94
- options: Partial<ProviderOptions>;
133
+ options: AwaitableFunction<Partial<ProviderOptions>>;
95
134
  codeVerifier?: string | undefined;
96
135
  deviceId?: string | undefined;
97
136
  tokenEndpoint: string;
@@ -100,7 +139,7 @@ export async function validateAuthorizationCode({
100
139
  additionalParams?: Record<string, string> | undefined;
101
140
  resource?: (string | string[]) | undefined;
102
141
  }) {
103
- const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
142
+ const { body, headers: requestHeaders } = await authorizationCodeRequest({
104
143
  code,
105
144
  codeVerifier,
106
145
  redirectURI,
@@ -117,7 +156,6 @@ export async function validateAuthorizationCode({
117
156
  body: body,
118
157
  headers: requestHeaders,
119
158
  });
120
-
121
159
  if (error) {
122
160
  throw error;
123
161
  }
@@ -125,25 +163,18 @@ export async function validateAuthorizationCode({
125
163
  return tokens;
126
164
  }
127
165
 
128
- export async function validateToken(token: string, jwksEndpoint: string) {
129
- const { data, error } = await betterFetch<{
130
- keys: JWK[];
131
- }>(jwksEndpoint, {
132
- method: "GET",
133
- headers: {
134
- accept: "application/json",
135
- },
166
+ export async function validateToken(
167
+ token: string,
168
+ jwksEndpoint: string,
169
+ options?: {
170
+ audience?: string | string[];
171
+ issuer?: string | string[];
172
+ },
173
+ ) {
174
+ const jwks = createRemoteJWKSet(new URL(jwksEndpoint));
175
+ const verified = await jwtVerify(token, jwks, {
176
+ audience: options?.audience,
177
+ issuer: options?.issuer,
136
178
  });
137
- if (error) {
138
- throw error;
139
- }
140
- const keys = data["keys"];
141
- const header = decodeProtectedHeader(token);
142
- const key = keys.find((k) => k.kid === header.kid);
143
- if (!key) {
144
- throw new Error("Key not found");
145
- }
146
- const cryptoKey = await importJWK(key, header.alg);
147
- const verified = await jwtVerify(token, cryptoKey);
148
179
  return verified;
149
180
  }
@@ -1,18 +1,19 @@
1
+ import type { JWK } from "jose";
1
2
  import { exportJWK, generateKeyPair, SignJWT } from "jose";
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
3
4
  import { validateToken } from "./validate-authorization-code";
4
5
 
5
- vi.mock("@better-fetch/fetch", () => ({
6
- betterFetch: vi.fn(),
7
- }));
8
-
9
- import { betterFetch } from "@better-fetch/fetch";
6
+ describe("validateToken", () => {
7
+ const originalFetch = globalThis.fetch;
8
+ const mockedFetch = vi.fn() as unknown as typeof fetch &
9
+ ReturnType<typeof vi.fn>;
10
10
 
11
- const mockedBetterFetch = vi.mocked(betterFetch);
11
+ beforeAll(() => {
12
+ globalThis.fetch = mockedFetch;
13
+ });
12
14
 
13
- describe("validateToken", () => {
14
- beforeEach(() => {
15
- vi.clearAllMocks();
15
+ afterAll(() => {
16
+ globalThis.fetch = originalFetch;
16
17
  });
17
18
 
18
19
  async function createTestJWKS(alg: string, crv?: string) {
@@ -24,7 +25,9 @@ describe("validateToken", () => {
24
25
  const privateJWK = await exportJWK(privateKey);
25
26
  const kid = `test-key-${Date.now()}`;
26
27
  publicJWK.kid = kid;
28
+ publicJWK.alg = alg;
27
29
  privateJWK.kid = kid;
30
+ privateJWK.alg = alg;
28
31
  return { publicJWK, privateJWK, kid, publicKey, privateKey };
29
32
  }
30
33
 
@@ -47,14 +50,19 @@ describe("validateToken", () => {
47
50
  .sign(privateKey);
48
51
  }
49
52
 
53
+ function mockJWKSResponse(...publicJWKs: JWK[]) {
54
+ mockedFetch.mockResolvedValueOnce(
55
+ new Response(JSON.stringify({ keys: publicJWKs }), {
56
+ status: 200,
57
+ headers: { "content-type": "application/json" },
58
+ }),
59
+ );
60
+ }
61
+
50
62
  it("should verify RS256 signed token", async () => {
51
63
  const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
52
64
  const token = await createSignedToken(privateKey, "RS256", kid);
53
-
54
- mockedBetterFetch.mockResolvedValueOnce({
55
- data: { keys: [publicJWK] },
56
- error: null,
57
- });
65
+ mockJWKSResponse(publicJWK);
58
66
 
59
67
  const result = await validateToken(
60
68
  token,
@@ -64,20 +72,12 @@ describe("validateToken", () => {
64
72
  expect(result).toBeDefined();
65
73
  expect(result.payload.sub).toBe("user-123");
66
74
  expect(result.payload.email).toBe("test@example.com");
67
- expect(mockedBetterFetch).toHaveBeenCalledWith(
68
- "https://example.com/.well-known/jwks",
69
- expect.objectContaining({ method: "GET" }),
70
- );
71
75
  });
72
76
 
73
77
  it("should verify ES256 signed token", async () => {
74
78
  const { publicJWK, privateKey, kid } = await createTestJWKS("ES256");
75
79
  const token = await createSignedToken(privateKey, "ES256", kid);
76
-
77
- mockedBetterFetch.mockResolvedValueOnce({
78
- data: { keys: [publicJWK] },
79
- error: null,
80
- });
80
+ mockJWKSResponse(publicJWK);
81
81
 
82
82
  const result = await validateToken(
83
83
  token,
@@ -94,11 +94,7 @@ describe("validateToken", () => {
94
94
  "Ed25519",
95
95
  );
96
96
  const token = await createSignedToken(privateKey, "EdDSA", kid);
97
-
98
- mockedBetterFetch.mockResolvedValueOnce({
99
- data: { keys: [publicJWK] },
100
- error: null,
101
- });
97
+ mockJWKSResponse(publicJWK);
102
98
 
103
99
  const result = await validateToken(
104
100
  token,
@@ -109,19 +105,15 @@ describe("validateToken", () => {
109
105
  expect(result.payload.sub).toBe("user-123");
110
106
  });
111
107
 
112
- it("should throw 'Key not found' when kid doesn't match", async () => {
108
+ it("should throw when kid doesn't match any key", async () => {
113
109
  const { publicJWK, privateKey } = await createTestJWKS("RS256");
114
110
  publicJWK.kid = "different-kid";
115
111
  const token = await createSignedToken(privateKey, "RS256", "original-kid");
116
-
117
- mockedBetterFetch.mockResolvedValueOnce({
118
- data: { keys: [publicJWK] },
119
- error: null,
120
- });
112
+ mockJWKSResponse(publicJWK);
121
113
 
122
114
  await expect(
123
115
  validateToken(token, "https://example.com/.well-known/jwks"),
124
- ).rejects.toThrow("Key not found");
116
+ ).rejects.toThrow();
125
117
  });
126
118
 
127
119
  it("should find correct key when multiple keys exist", async () => {
@@ -129,11 +121,7 @@ describe("validateToken", () => {
129
121
  const key2 = await createTestJWKS("RS256");
130
122
  const key3 = await createTestJWKS("ES256");
131
123
  const token = await createSignedToken(key2.privateKey, "RS256", key2.kid);
132
-
133
- mockedBetterFetch.mockResolvedValueOnce({
134
- data: { keys: [key1.publicJWK, key2.publicJWK, key3.publicJWK] },
135
- error: null,
136
- });
124
+ mockJWKSResponse(key1.publicJWK, key2.publicJWK, key3.publicJWK);
137
125
 
138
126
  const result = await validateToken(
139
127
  token,
@@ -147,28 +135,95 @@ describe("validateToken", () => {
147
135
  it("should throw when JWKS returns empty keys array", async () => {
148
136
  const { privateKey, kid } = await createTestJWKS("RS256");
149
137
  const token = await createSignedToken(privateKey, "RS256", kid);
150
-
151
- mockedBetterFetch.mockResolvedValueOnce({
152
- data: { keys: [] },
153
- error: null,
154
- });
138
+ mockJWKSResponse();
155
139
 
156
140
  await expect(
157
141
  validateToken(token, "https://example.com/.well-known/jwks"),
158
- ).rejects.toThrow("Key not found");
142
+ ).rejects.toThrow();
159
143
  });
160
144
 
161
145
  it("should throw when JWKS fetch fails", async () => {
162
146
  const { privateKey, kid } = await createTestJWKS("RS256");
163
147
  const token = await createSignedToken(privateKey, "RS256", kid);
164
-
165
- mockedBetterFetch.mockResolvedValueOnce({
166
- data: null,
167
- error: { status: 500, statusText: "Internal Server Error" },
168
- });
148
+ mockedFetch.mockResolvedValueOnce(
149
+ new Response("Internal Server Error", { status: 500 }),
150
+ );
169
151
 
170
152
  await expect(
171
153
  validateToken(token, "https://example.com/.well-known/jwks"),
172
154
  ).rejects.toBeDefined();
173
155
  });
156
+
157
+ it("should verify token with matching audience", async () => {
158
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
159
+ const token = await createSignedToken(privateKey, "RS256", kid);
160
+ mockJWKSResponse(publicJWK);
161
+
162
+ const result = await validateToken(
163
+ token,
164
+ "https://example.com/.well-known/jwks",
165
+ { audience: "test-client" },
166
+ );
167
+
168
+ expect(result).toBeDefined();
169
+ expect(result.payload.aud).toBe("test-client");
170
+ });
171
+
172
+ it("should reject token with mismatched audience", async () => {
173
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
174
+ const token = await createSignedToken(privateKey, "RS256", kid);
175
+ mockJWKSResponse(publicJWK);
176
+
177
+ await expect(
178
+ validateToken(token, "https://example.com/.well-known/jwks", {
179
+ audience: "wrong-client",
180
+ }),
181
+ ).rejects.toThrow();
182
+ });
183
+
184
+ it("should verify token with matching issuer", async () => {
185
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
186
+ const token = await createSignedToken(privateKey, "RS256", kid);
187
+ mockJWKSResponse(publicJWK);
188
+
189
+ const result = await validateToken(
190
+ token,
191
+ "https://example.com/.well-known/jwks",
192
+ { issuer: "https://example.com" },
193
+ );
194
+
195
+ expect(result).toBeDefined();
196
+ expect(result.payload.iss).toBe("https://example.com");
197
+ });
198
+
199
+ it("should reject token with mismatched issuer", async () => {
200
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
201
+ const token = await createSignedToken(privateKey, "RS256", kid);
202
+ mockJWKSResponse(publicJWK);
203
+
204
+ await expect(
205
+ validateToken(token, "https://example.com/.well-known/jwks", {
206
+ issuer: "https://wrong-issuer.com",
207
+ }),
208
+ ).rejects.toThrow();
209
+ });
210
+
211
+ it("should verify token with both audience and issuer", async () => {
212
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
213
+ const token = await createSignedToken(privateKey, "RS256", kid);
214
+ mockJWKSResponse(publicJWK);
215
+
216
+ const result = await validateToken(
217
+ token,
218
+ "https://example.com/.well-known/jwks",
219
+ {
220
+ audience: "test-client",
221
+ issuer: "https://example.com",
222
+ },
223
+ );
224
+
225
+ expect(result).toBeDefined();
226
+ expect(result.payload.aud).toBe("test-client");
227
+ expect(result.payload.iss).toBe("https://example.com");
228
+ });
174
229
  });
@@ -112,41 +112,41 @@ export const apple = (options: AppleOptions) => {
112
112
  if (options.verifyIdToken) {
113
113
  return options.verifyIdToken(token, nonce);
114
114
  }
115
- const decodedHeader = decodeProtectedHeader(token);
116
- const { kid, alg: jwtAlg } = decodedHeader;
117
- if (!kid || !jwtAlg) return false;
118
- const publicKey = await getApplePublicKey(kid);
119
- const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
120
- algorithms: [jwtAlg],
121
- issuer: "https://appleid.apple.com",
122
- audience:
123
- options.audience && options.audience.length
124
- ? options.audience
125
- : options.appBundleIdentifier
126
- ? options.appBundleIdentifier
127
- : options.clientId,
128
- maxTokenAge: "1h",
129
- });
130
- ["email_verified", "is_private_email"].forEach((field) => {
131
- if (jwtClaims[field] !== undefined) {
132
- jwtClaims[field] = Boolean(jwtClaims[field]);
115
+ try {
116
+ const decodedHeader = decodeProtectedHeader(token);
117
+ const { kid, alg: jwtAlg } = decodedHeader;
118
+ if (!kid || !jwtAlg) return false;
119
+ const publicKey = await getApplePublicKey(kid);
120
+ const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
121
+ algorithms: [jwtAlg],
122
+ issuer: "https://appleid.apple.com",
123
+ audience:
124
+ options.audience && options.audience.length
125
+ ? options.audience
126
+ : options.appBundleIdentifier
127
+ ? options.appBundleIdentifier
128
+ : options.clientId,
129
+ maxTokenAge: "1h",
130
+ });
131
+ ["email_verified", "is_private_email"].forEach((field) => {
132
+ if (jwtClaims[field] !== undefined) {
133
+ jwtClaims[field] = Boolean(jwtClaims[field]);
134
+ }
135
+ });
136
+ if (nonce && jwtClaims.nonce !== nonce) {
137
+ return false;
133
138
  }
134
- });
135
- if (nonce && jwtClaims.nonce !== nonce) {
139
+ return !!jwtClaims;
140
+ } catch {
136
141
  return false;
137
142
  }
138
- return !!jwtClaims;
139
143
  },
140
144
  refreshAccessToken: options.refreshAccessToken
141
145
  ? options.refreshAccessToken
142
146
  : async (refreshToken) => {
143
147
  return refreshAccessToken({
144
148
  refreshToken,
145
- options: {
146
- clientId: options.clientId,
147
- clientKey: options.clientKey,
148
- clientSecret: options.clientSecret,
149
- },
149
+ options,
150
150
  tokenEndpoint: "https://appleid.apple.com/auth/token",
151
151
  });
152
152
  },
@@ -162,15 +162,15 @@ export const apple = (options: AppleOptions) => {
162
162
  return null;
163
163
  }
164
164
 
165
- // TODO: " " masking will be removed when the name field is made optional
165
+ // TODO: "" masking will be removed when the name field is made optional
166
166
  let name: string;
167
167
  if (token.user?.name) {
168
168
  const firstName = token.user.name.firstName || "";
169
169
  const lastName = token.user.name.lastName || "";
170
170
  const fullName = `${firstName} ${lastName}`.trim();
171
- name = fullName || " ";
171
+ name = fullName;
172
172
  } else {
173
- name = profile.name || " ";
173
+ name = profile.name || "";
174
174
  }
175
175
 
176
176
  const emailVerified =
@@ -178,10 +178,7 @@ export const cognito = (options: CognitoOptions) => {
178
178
  return null;
179
179
  }
180
180
  const name =
181
- profile.name ||
182
- profile.given_name ||
183
- profile.username ||
184
- profile.email;
181
+ profile.name || profile.given_name || profile.username || "";
185
182
  const enrichedProfile = {
186
183
  ...profile,
187
184
  name,
@@ -220,7 +217,11 @@ export const cognito = (options: CognitoOptions) => {
220
217
  return {
221
218
  user: {
222
219
  id: userInfo.sub,
223
- name: userInfo.name || userInfo.given_name || userInfo.username,
220
+ name:
221
+ userInfo.name ||
222
+ userInfo.given_name ||
223
+ userInfo.username ||
224
+ "",
224
225
  email: userInfo.email,
225
226
  image: userInfo.picture,
226
227
  emailVerified: userInfo.email_verified,
@@ -49,7 +49,7 @@ export const facebook = (options: FacebookOptions) => {
49
49
  return await createAuthorizationURL({
50
50
  id: "facebook",
51
51
  options,
52
- authorizationEndpoint: "https://www.facebook.com/v21.0/dialog/oauth",
52
+ authorizationEndpoint: "https://www.facebook.com/v24.0/dialog/oauth",
53
53
  scopes: _scopes,
54
54
  state,
55
55
  redirectURI,
@@ -66,7 +66,7 @@ export const facebook = (options: FacebookOptions) => {
66
66
  code,
67
67
  redirectURI,
68
68
  options,
69
- tokenEndpoint: "https://graph.facebook.com/oauth/access_token",
69
+ tokenEndpoint: "https://graph.facebook.com/v24.0/oauth/access_token",
70
70
  });
71
71
  },
72
72
  async verifyIdToken(token, nonce) {
@@ -121,7 +121,7 @@ export const facebook = (options: FacebookOptions) => {
121
121
  clientSecret: options.clientSecret,
122
122
  },
123
123
  tokenEndpoint:
124
- "https://graph.facebook.com/v18.0/oauth/access_token",
124
+ "https://graph.facebook.com/v24.0/oauth/access_token",
125
125
  });
126
126
  },
127
127
  async getUserInfo(token) {