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

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 (353) hide show
  1. package/.turbo/turbo-build.log +266 -0
  2. package/.turbo/turbo-test.log +2 -0
  3. package/LICENSE.md +20 -0
  4. package/dist/api/index.d.mts +181 -0
  5. package/dist/api/index.mjs +34 -0
  6. package/dist/api/index.mjs.map +1 -0
  7. package/dist/async_hooks/index.d.mts +7 -0
  8. package/dist/async_hooks/index.mjs +22 -0
  9. package/dist/async_hooks/index.mjs.map +1 -0
  10. package/dist/async_hooks/pure.index.d.mts +7 -0
  11. package/dist/async_hooks/pure.index.mjs +35 -0
  12. package/dist/async_hooks/pure.index.mjs.map +1 -0
  13. package/dist/context/endpoint-context.d.mts +19 -0
  14. package/dist/context/endpoint-context.mjs +32 -0
  15. package/dist/context/endpoint-context.mjs.map +1 -0
  16. package/dist/context/global.d.mts +7 -0
  17. package/dist/context/global.mjs +38 -0
  18. package/dist/context/global.mjs.map +1 -0
  19. package/dist/context/index.d.mts +5 -0
  20. package/dist/context/index.mjs +6 -0
  21. package/dist/context/request-state.d.mts +26 -0
  22. package/dist/context/request-state.mjs +50 -0
  23. package/dist/context/request-state.mjs.map +1 -0
  24. package/dist/context/transaction.d.mts +25 -0
  25. package/dist/context/transaction.mjs +96 -0
  26. package/dist/context/transaction.mjs.map +1 -0
  27. package/dist/db/adapter/factory.d.mts +28 -0
  28. package/dist/db/adapter/factory.mjs +716 -0
  29. package/dist/db/adapter/factory.mjs.map +1 -0
  30. package/dist/db/adapter/get-default-field-name.d.mts +19 -0
  31. package/dist/db/adapter/get-default-field-name.mjs +39 -0
  32. package/dist/db/adapter/get-default-field-name.mjs.map +1 -0
  33. package/dist/db/adapter/get-default-model-name.d.mts +13 -0
  34. package/dist/db/adapter/get-default-model-name.mjs +33 -0
  35. package/dist/db/adapter/get-default-model-name.mjs.map +1 -0
  36. package/dist/db/adapter/get-field-attributes.d.mts +30 -0
  37. package/dist/db/adapter/get-field-attributes.mjs +40 -0
  38. package/dist/db/adapter/get-field-attributes.mjs.map +1 -0
  39. package/dist/db/adapter/get-field-name.d.mts +19 -0
  40. package/dist/db/adapter/get-field-name.mjs +34 -0
  41. package/dist/db/adapter/get-field-name.mjs.map +1 -0
  42. package/dist/db/adapter/get-id-field.d.mts +40 -0
  43. package/dist/db/adapter/get-id-field.mjs +68 -0
  44. package/dist/db/adapter/get-id-field.mjs.map +1 -0
  45. package/dist/db/adapter/get-model-name.d.mts +13 -0
  46. package/dist/db/adapter/get-model-name.mjs +24 -0
  47. package/dist/db/adapter/get-model-name.mjs.map +1 -0
  48. package/dist/db/adapter/index.d.mts +515 -0
  49. package/dist/db/adapter/index.mjs +10 -0
  50. package/dist/db/adapter/types.d.mts +140 -0
  51. package/dist/db/adapter/utils.d.mts +8 -0
  52. package/dist/db/adapter/utils.mjs +39 -0
  53. package/dist/db/adapter/utils.mjs.map +1 -0
  54. package/dist/db/get-tables.d.mts +9 -0
  55. package/dist/db/get-tables.mjs +267 -0
  56. package/dist/db/get-tables.mjs.map +1 -0
  57. package/dist/db/index.d.mts +10 -0
  58. package/dist/db/index.mjs +9 -0
  59. package/dist/db/plugin.d.mts +13 -0
  60. package/dist/db/schema/account.d.mts +27 -0
  61. package/dist/db/schema/account.mjs +20 -0
  62. package/dist/db/schema/account.mjs.map +1 -0
  63. package/dist/db/schema/rate-limit.d.mts +15 -0
  64. package/dist/db/schema/rate-limit.mjs +12 -0
  65. package/dist/db/schema/rate-limit.mjs.map +1 -0
  66. package/dist/db/schema/session.d.mts +22 -0
  67. package/dist/db/schema/session.mjs +15 -0
  68. package/dist/db/schema/session.mjs.map +1 -0
  69. package/dist/db/schema/shared.d.mts +11 -0
  70. package/dist/db/schema/shared.mjs +12 -0
  71. package/dist/db/schema/shared.mjs.map +1 -0
  72. package/dist/db/schema/user.d.mts +21 -0
  73. package/dist/db/schema/user.mjs +14 -0
  74. package/dist/db/schema/user.mjs.map +1 -0
  75. package/dist/db/schema/verification.d.mts +20 -0
  76. package/dist/db/schema/verification.mjs +13 -0
  77. package/dist/db/schema/verification.mjs.map +1 -0
  78. package/dist/db/type.d.mts +147 -0
  79. package/dist/env/color-depth.d.mts +5 -0
  80. package/dist/env/color-depth.mjs +89 -0
  81. package/dist/env/color-depth.mjs.map +1 -0
  82. package/dist/env/env-impl.d.mts +33 -0
  83. package/dist/env/env-impl.mjs +83 -0
  84. package/dist/env/env-impl.mjs.map +1 -0
  85. package/dist/env/index.d.mts +4 -0
  86. package/dist/env/index.mjs +5 -0
  87. package/dist/env/logger.d.mts +49 -0
  88. package/dist/env/logger.mjs +82 -0
  89. package/dist/env/logger.mjs.map +1 -0
  90. package/dist/error/codes.d.mts +199 -0
  91. package/dist/error/codes.mjs +57 -0
  92. package/dist/error/codes.mjs.map +1 -0
  93. package/dist/error/index.d.mts +20 -0
  94. package/dist/error/index.mjs +30 -0
  95. package/dist/error/index.mjs.map +1 -0
  96. package/dist/index.d.mts +8 -0
  97. package/dist/index.mjs +1 -0
  98. package/dist/oauth2/client-credentials-token.d.mts +37 -0
  99. package/dist/oauth2/client-credentials-token.mjs +55 -0
  100. package/dist/oauth2/client-credentials-token.mjs.map +1 -0
  101. package/dist/oauth2/create-authorization-url.d.mts +46 -0
  102. package/dist/oauth2/create-authorization-url.mjs +43 -0
  103. package/dist/oauth2/create-authorization-url.mjs.map +1 -0
  104. package/dist/oauth2/index.d.mts +8 -0
  105. package/dist/oauth2/index.mjs +8 -0
  106. package/dist/oauth2/oauth-provider.d.mts +195 -0
  107. package/dist/oauth2/refresh-access-token.d.mts +36 -0
  108. package/dist/oauth2/refresh-access-token.mjs +59 -0
  109. package/dist/oauth2/refresh-access-token.mjs.map +1 -0
  110. package/dist/oauth2/utils.d.mts +8 -0
  111. package/dist/oauth2/utils.mjs +28 -0
  112. package/dist/oauth2/utils.mjs.map +1 -0
  113. package/dist/oauth2/validate-authorization-code.d.mts +56 -0
  114. package/dist/oauth2/validate-authorization-code.mjs +72 -0
  115. package/dist/oauth2/validate-authorization-code.mjs.map +1 -0
  116. package/dist/oauth2/verify.d.mts +43 -0
  117. package/dist/oauth2/verify.mjs +96 -0
  118. package/dist/oauth2/verify.mjs.map +1 -0
  119. package/dist/social-providers/apple.d.mts +120 -0
  120. package/dist/social-providers/apple.mjs +105 -0
  121. package/dist/social-providers/apple.mjs.map +1 -0
  122. package/dist/social-providers/atlassian.d.mts +73 -0
  123. package/dist/social-providers/atlassian.mjs +84 -0
  124. package/dist/social-providers/atlassian.mjs.map +1 -0
  125. package/dist/social-providers/cognito.d.mts +88 -0
  126. package/dist/social-providers/cognito.mjs +166 -0
  127. package/dist/social-providers/cognito.mjs.map +1 -0
  128. package/dist/social-providers/discord.d.mts +127 -0
  129. package/dist/social-providers/discord.mjs +65 -0
  130. package/dist/social-providers/discord.mjs.map +1 -0
  131. package/dist/social-providers/dropbox.d.mts +72 -0
  132. package/dist/social-providers/dropbox.mjs +76 -0
  133. package/dist/social-providers/dropbox.mjs.map +1 -0
  134. package/dist/social-providers/facebook.d.mts +82 -0
  135. package/dist/social-providers/facebook.mjs +121 -0
  136. package/dist/social-providers/facebook.mjs.map +1 -0
  137. package/dist/social-providers/figma.d.mts +64 -0
  138. package/dist/social-providers/figma.mjs +87 -0
  139. package/dist/social-providers/figma.mjs.map +1 -0
  140. package/dist/social-providers/github.d.mts +105 -0
  141. package/dist/social-providers/github.mjs +97 -0
  142. package/dist/social-providers/github.mjs.map +1 -0
  143. package/dist/social-providers/gitlab.d.mts +126 -0
  144. package/dist/social-providers/gitlab.mjs +83 -0
  145. package/dist/social-providers/gitlab.mjs.map +1 -0
  146. package/dist/social-providers/google.d.mts +100 -0
  147. package/dist/social-providers/google.mjs +109 -0
  148. package/dist/social-providers/google.mjs.map +1 -0
  149. package/dist/social-providers/huggingface.d.mts +86 -0
  150. package/dist/social-providers/huggingface.mjs +76 -0
  151. package/dist/social-providers/huggingface.mjs.map +1 -0
  152. package/dist/social-providers/index.d.mts +1725 -0
  153. package/dist/social-providers/index.mjs +77 -0
  154. package/dist/social-providers/index.mjs.map +1 -0
  155. package/dist/social-providers/kakao.d.mts +164 -0
  156. package/dist/social-providers/kakao.mjs +73 -0
  157. package/dist/social-providers/kakao.mjs.map +1 -0
  158. package/dist/social-providers/kick.d.mts +76 -0
  159. package/dist/social-providers/kick.mjs +72 -0
  160. package/dist/social-providers/kick.mjs.map +1 -0
  161. package/dist/social-providers/line.d.mts +108 -0
  162. package/dist/social-providers/line.mjs +114 -0
  163. package/dist/social-providers/line.mjs.map +1 -0
  164. package/dist/social-providers/linear.d.mts +71 -0
  165. package/dist/social-providers/linear.mjs +89 -0
  166. package/dist/social-providers/linear.mjs.map +1 -0
  167. package/dist/social-providers/linkedin.d.mts +70 -0
  168. package/dist/social-providers/linkedin.mjs +77 -0
  169. package/dist/social-providers/linkedin.mjs.map +1 -0
  170. package/dist/social-providers/microsoft-entra-id.d.mts +175 -0
  171. package/dist/social-providers/microsoft-entra-id.mjs +107 -0
  172. package/dist/social-providers/microsoft-entra-id.mjs.map +1 -0
  173. package/dist/social-providers/naver.d.mts +95 -0
  174. package/dist/social-providers/naver.mjs +68 -0
  175. package/dist/social-providers/naver.mjs.map +1 -0
  176. package/dist/social-providers/notion.d.mts +67 -0
  177. package/dist/social-providers/notion.mjs +76 -0
  178. package/dist/social-providers/notion.mjs.map +1 -0
  179. package/dist/social-providers/paybin.d.mts +74 -0
  180. package/dist/social-providers/paybin.mjs +86 -0
  181. package/dist/social-providers/paybin.mjs.map +1 -0
  182. package/dist/social-providers/paypal.d.mts +132 -0
  183. package/dist/social-providers/paypal.mjs +145 -0
  184. package/dist/social-providers/paypal.mjs.map +1 -0
  185. package/dist/social-providers/polar.d.mts +77 -0
  186. package/dist/social-providers/polar.mjs +74 -0
  187. package/dist/social-providers/polar.mjs.map +1 -0
  188. package/dist/social-providers/reddit.d.mts +65 -0
  189. package/dist/social-providers/reddit.mjs +84 -0
  190. package/dist/social-providers/reddit.mjs.map +1 -0
  191. package/dist/social-providers/roblox.d.mts +73 -0
  192. package/dist/social-providers/roblox.mjs +60 -0
  193. package/dist/social-providers/roblox.mjs.map +1 -0
  194. package/dist/social-providers/salesforce.d.mts +82 -0
  195. package/dist/social-providers/salesforce.mjs +92 -0
  196. package/dist/social-providers/salesforce.mjs.map +1 -0
  197. package/dist/social-providers/slack.d.mts +86 -0
  198. package/dist/social-providers/slack.mjs +69 -0
  199. package/dist/social-providers/slack.mjs.map +1 -0
  200. package/dist/social-providers/spotify.d.mts +66 -0
  201. package/dist/social-providers/spotify.mjs +72 -0
  202. package/dist/social-providers/spotify.mjs.map +1 -0
  203. package/dist/social-providers/tiktok.d.mts +171 -0
  204. package/dist/social-providers/tiktok.mjs +63 -0
  205. package/dist/social-providers/tiktok.mjs.map +1 -0
  206. package/dist/social-providers/twitch.d.mts +82 -0
  207. package/dist/social-providers/twitch.mjs +79 -0
  208. package/dist/social-providers/twitch.mjs.map +1 -0
  209. package/dist/social-providers/twitter.d.mts +129 -0
  210. package/dist/social-providers/twitter.mjs +88 -0
  211. package/dist/social-providers/twitter.mjs.map +1 -0
  212. package/dist/social-providers/vercel.d.mts +65 -0
  213. package/dist/social-providers/vercel.mjs +62 -0
  214. package/dist/social-providers/vercel.mjs.map +1 -0
  215. package/dist/social-providers/vk.d.mts +73 -0
  216. package/dist/social-providers/vk.mjs +84 -0
  217. package/dist/social-providers/vk.mjs.map +1 -0
  218. package/dist/social-providers/zoom.d.mts +173 -0
  219. package/dist/social-providers/zoom.mjs +73 -0
  220. package/dist/social-providers/zoom.mjs.map +1 -0
  221. package/dist/types/context.d.mts +267 -0
  222. package/dist/types/cookie.d.mts +16 -0
  223. package/dist/types/helper.d.mts +10 -0
  224. package/dist/types/index.d.mts +8 -0
  225. package/dist/types/init-options.d.mts +1314 -0
  226. package/dist/types/plugin-client.d.mts +112 -0
  227. package/dist/types/plugin.d.mts +125 -0
  228. package/dist/utils/db.d.mts +12 -0
  229. package/dist/utils/db.mjs +17 -0
  230. package/dist/utils/db.mjs.map +1 -0
  231. package/dist/utils/deprecate.d.mts +10 -0
  232. package/dist/utils/deprecate.mjs +18 -0
  233. package/dist/utils/deprecate.mjs.map +1 -0
  234. package/dist/utils/error-codes.d.mts +13 -0
  235. package/dist/utils/error-codes.mjs +12 -0
  236. package/dist/utils/error-codes.mjs.map +1 -0
  237. package/dist/utils/id.d.mts +5 -0
  238. package/dist/utils/id.mjs +10 -0
  239. package/dist/utils/id.mjs.map +1 -0
  240. package/dist/utils/ip.d.mts +55 -0
  241. package/dist/utils/ip.mjs +119 -0
  242. package/dist/utils/ip.mjs.map +1 -0
  243. package/dist/utils/json.d.mts +5 -0
  244. package/dist/utils/json.mjs +26 -0
  245. package/dist/utils/json.mjs.map +1 -0
  246. package/dist/utils/string.d.mts +5 -0
  247. package/dist/utils/string.mjs +8 -0
  248. package/dist/utils/string.mjs.map +1 -0
  249. package/dist/utils/url.d.mts +21 -0
  250. package/dist/utils/url.mjs +33 -0
  251. package/dist/utils/url.mjs.map +1 -0
  252. package/package.json +147 -0
  253. package/src/api/index.ts +106 -0
  254. package/src/async_hooks/index.ts +40 -0
  255. package/src/async_hooks/pure.index.ts +46 -0
  256. package/src/context/endpoint-context.ts +50 -0
  257. package/src/context/global.ts +57 -0
  258. package/src/context/index.ts +23 -0
  259. package/src/context/request-state.test.ts +94 -0
  260. package/src/context/request-state.ts +91 -0
  261. package/src/context/transaction.ts +136 -0
  262. package/src/db/adapter/factory.ts +1362 -0
  263. package/src/db/adapter/get-default-field-name.ts +59 -0
  264. package/src/db/adapter/get-default-model-name.ts +51 -0
  265. package/src/db/adapter/get-field-attributes.ts +62 -0
  266. package/src/db/adapter/get-field-name.ts +43 -0
  267. package/src/db/adapter/get-id-field.ts +141 -0
  268. package/src/db/adapter/get-model-name.ts +36 -0
  269. package/src/db/adapter/index.ts +554 -0
  270. package/src/db/adapter/types.ts +171 -0
  271. package/src/db/adapter/utils.ts +61 -0
  272. package/src/db/get-tables.ts +296 -0
  273. package/src/db/index.ts +18 -0
  274. package/src/db/plugin.ts +11 -0
  275. package/src/db/schema/account.ts +34 -0
  276. package/src/db/schema/rate-limit.ts +21 -0
  277. package/src/db/schema/session.ts +17 -0
  278. package/src/db/schema/shared.ts +7 -0
  279. package/src/db/schema/user.ts +16 -0
  280. package/src/db/schema/verification.ts +15 -0
  281. package/src/db/test/get-tables.test.ts +116 -0
  282. package/src/db/type.ts +180 -0
  283. package/src/env/color-depth.ts +172 -0
  284. package/src/env/env-impl.ts +124 -0
  285. package/src/env/index.ts +23 -0
  286. package/src/env/logger.test.ts +34 -0
  287. package/src/env/logger.ts +145 -0
  288. package/src/error/codes.ts +58 -0
  289. package/src/error/index.ts +35 -0
  290. package/src/index.ts +1 -0
  291. package/src/oauth2/client-credentials-token.ts +102 -0
  292. package/src/oauth2/create-authorization-url.ts +87 -0
  293. package/src/oauth2/index.ts +26 -0
  294. package/src/oauth2/oauth-provider.ts +222 -0
  295. package/src/oauth2/refresh-access-token.ts +124 -0
  296. package/src/oauth2/utils.ts +38 -0
  297. package/src/oauth2/validate-authorization-code.ts +149 -0
  298. package/src/oauth2/validate-token.test.ts +174 -0
  299. package/src/oauth2/verify.ts +221 -0
  300. package/src/social-providers/apple.ts +223 -0
  301. package/src/social-providers/atlassian.ts +132 -0
  302. package/src/social-providers/cognito.ts +279 -0
  303. package/src/social-providers/discord.ts +169 -0
  304. package/src/social-providers/dropbox.ts +112 -0
  305. package/src/social-providers/facebook.ts +206 -0
  306. package/src/social-providers/figma.ts +117 -0
  307. package/src/social-providers/github.ts +184 -0
  308. package/src/social-providers/gitlab.ts +155 -0
  309. package/src/social-providers/google.ts +199 -0
  310. package/src/social-providers/huggingface.ts +118 -0
  311. package/src/social-providers/index.ts +127 -0
  312. package/src/social-providers/kakao.ts +178 -0
  313. package/src/social-providers/kick.ts +109 -0
  314. package/src/social-providers/line.ts +169 -0
  315. package/src/social-providers/linear.ts +121 -0
  316. package/src/social-providers/linkedin.ts +110 -0
  317. package/src/social-providers/microsoft-entra-id.ts +259 -0
  318. package/src/social-providers/naver.ts +112 -0
  319. package/src/social-providers/notion.ts +108 -0
  320. package/src/social-providers/paybin.ts +122 -0
  321. package/src/social-providers/paypal.ts +263 -0
  322. package/src/social-providers/polar.ts +110 -0
  323. package/src/social-providers/reddit.ts +122 -0
  324. package/src/social-providers/roblox.ts +111 -0
  325. package/src/social-providers/salesforce.ts +159 -0
  326. package/src/social-providers/slack.ts +111 -0
  327. package/src/social-providers/spotify.ts +93 -0
  328. package/src/social-providers/tiktok.ts +209 -0
  329. package/src/social-providers/twitch.ts +111 -0
  330. package/src/social-providers/twitter.ts +198 -0
  331. package/src/social-providers/vercel.ts +87 -0
  332. package/src/social-providers/vk.ts +124 -0
  333. package/src/social-providers/zoom.ts +238 -0
  334. package/src/types/context.ts +396 -0
  335. package/src/types/cookie.ts +10 -0
  336. package/src/types/helper.ts +26 -0
  337. package/src/types/index.ts +32 -0
  338. package/src/types/init-options.ts +1529 -0
  339. package/src/types/plugin-client.ts +127 -0
  340. package/src/types/plugin.ts +157 -0
  341. package/src/utils/db.ts +20 -0
  342. package/src/utils/deprecate.test.ts +72 -0
  343. package/src/utils/deprecate.ts +21 -0
  344. package/src/utils/error-codes.ts +65 -0
  345. package/src/utils/id.ts +5 -0
  346. package/src/utils/ip.test.ts +255 -0
  347. package/src/utils/ip.ts +211 -0
  348. package/src/utils/json.ts +25 -0
  349. package/src/utils/string.ts +3 -0
  350. package/src/utils/url.ts +43 -0
  351. package/tsconfig.json +7 -0
  352. package/tsdown.config.ts +35 -0
  353. package/vitest.config.ts +3 -0
@@ -0,0 +1,255 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createRateLimitKey, isValidIP, normalizeIP } from "./ip";
3
+
4
+ describe("IP Normalization", () => {
5
+ describe("isValidIP", () => {
6
+ it("should validate IPv4 addresses", () => {
7
+ expect(isValidIP("192.168.1.1")).toBe(true);
8
+ expect(isValidIP("127.0.0.1")).toBe(true);
9
+ expect(isValidIP("0.0.0.0")).toBe(true);
10
+ expect(isValidIP("255.255.255.255")).toBe(true);
11
+ });
12
+
13
+ it("should validate IPv6 addresses", () => {
14
+ expect(isValidIP("2001:db8::1")).toBe(true);
15
+ expect(isValidIP("::1")).toBe(true);
16
+ expect(isValidIP("::")).toBe(true);
17
+ expect(isValidIP("2001:0db8:0000:0000:0000:0000:0000:0001")).toBe(true);
18
+ });
19
+
20
+ it("should reject invalid IPs", () => {
21
+ expect(isValidIP("not-an-ip")).toBe(false);
22
+ expect(isValidIP("999.999.999.999")).toBe(false);
23
+ expect(isValidIP("gggg::1")).toBe(false);
24
+ });
25
+ });
26
+
27
+ describe("IPv4 Normalization", () => {
28
+ it("should return IPv4 addresses unchanged", () => {
29
+ expect(normalizeIP("192.168.1.1")).toBe("192.168.1.1");
30
+ expect(normalizeIP("127.0.0.1")).toBe("127.0.0.1");
31
+ expect(normalizeIP("10.0.0.1")).toBe("10.0.0.1");
32
+ });
33
+ });
34
+
35
+ describe("IPv6 Normalization", () => {
36
+ it("should normalize compressed IPv6 to full form", () => {
37
+ expect(normalizeIP("2001:db8::1", { ipv6Subnet: 128 })).toBe(
38
+ "2001:0db8:0000:0000:0000:0000:0000:0001",
39
+ );
40
+ expect(normalizeIP("::1", { ipv6Subnet: 128 })).toBe(
41
+ "0000:0000:0000:0000:0000:0000:0000:0001",
42
+ );
43
+ expect(normalizeIP("::", { ipv6Subnet: 128 })).toBe(
44
+ "0000:0000:0000:0000:0000:0000:0000:0000",
45
+ );
46
+ });
47
+
48
+ it("should normalize uppercase to lowercase", () => {
49
+ expect(normalizeIP("2001:DB8::1", { ipv6Subnet: 128 })).toBe(
50
+ "2001:0db8:0000:0000:0000:0000:0000:0001",
51
+ );
52
+ expect(normalizeIP("2001:0DB8:ABCD:EF00::1", { ipv6Subnet: 128 })).toBe(
53
+ "2001:0db8:abcd:ef00:0000:0000:0000:0001",
54
+ );
55
+ });
56
+
57
+ it("should handle various IPv6 formats consistently", () => {
58
+ // All these represent the same address
59
+ const normalized = "2001:0db8:0000:0000:0000:0000:0000:0001";
60
+ expect(normalizeIP("2001:db8::1", { ipv6Subnet: 128 })).toBe(normalized);
61
+ expect(normalizeIP("2001:0db8:0:0:0:0:0:1", { ipv6Subnet: 128 })).toBe(
62
+ normalized,
63
+ );
64
+ expect(normalizeIP("2001:db8:0::1", { ipv6Subnet: 128 })).toBe(
65
+ normalized,
66
+ );
67
+ expect(normalizeIP("2001:0db8::0:0:0:1", { ipv6Subnet: 128 })).toBe(
68
+ normalized,
69
+ );
70
+ });
71
+
72
+ it("should handle IPv6 with :: at different positions", () => {
73
+ expect(
74
+ normalizeIP("2001:db8:85a3::8a2e:370:7334", { ipv6Subnet: 128 }),
75
+ ).toBe("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
76
+ expect(normalizeIP("::ffff:192.0.2.1")).not.toContain("::");
77
+ });
78
+ });
79
+
80
+ describe("IPv4-mapped IPv6 Conversion", () => {
81
+ it("should convert IPv4-mapped IPv6 to IPv4", () => {
82
+ expect(normalizeIP("::ffff:192.0.2.1")).toBe("192.0.2.1");
83
+ expect(normalizeIP("::ffff:127.0.0.1")).toBe("127.0.0.1");
84
+ expect(normalizeIP("::FFFF:10.0.0.1")).toBe("10.0.0.1");
85
+ });
86
+
87
+ it("should handle hex-encoded IPv4 in mapped addresses", () => {
88
+ // ::ffff:c000:0201 = ::ffff:192.0.2.1 = 192.0.2.1
89
+ expect(normalizeIP("::ffff:c000:0201")).toBe("192.0.2.1");
90
+ // ::ffff:7f00:0001 = ::ffff:127.0.0.1 = 127.0.0.1
91
+ expect(normalizeIP("::ffff:7f00:0001")).toBe("127.0.0.1");
92
+ });
93
+
94
+ it("should handle full form IPv4-mapped IPv6", () => {
95
+ expect(normalizeIP("0:0:0:0:0:ffff:192.0.2.1")).toBe("192.0.2.1");
96
+ });
97
+ });
98
+
99
+ describe("IPv6 Subnet Support", () => {
100
+ it("should extract /64 subnet", () => {
101
+ /* cspell:disable-next-line */
102
+ const ip1 = normalizeIP("2001:db8:0:0:1234:5678:90ab:cdef", {
103
+ ipv6Subnet: 64,
104
+ });
105
+ const ip2 = normalizeIP("2001:db8:0:0:ffff:ffff:ffff:ffff", {
106
+ ipv6Subnet: 64,
107
+ });
108
+ // Both should have same /64 prefix
109
+ expect(ip1).toBe("2001:0db8:0000:0000:0000:0000:0000:0000");
110
+ expect(ip2).toBe("2001:0db8:0000:0000:0000:0000:0000:0000");
111
+ expect(ip1).toBe(ip2);
112
+ });
113
+
114
+ it("should extract /48 subnet", () => {
115
+ /* cspell:disable-next-line */
116
+ const ip1 = normalizeIP("2001:db8:1234:5678:90ab:cdef:1234:5678", {
117
+ ipv6Subnet: 48,
118
+ });
119
+ const ip2 = normalizeIP("2001:db8:1234:ffff:ffff:ffff:ffff:ffff", {
120
+ ipv6Subnet: 48,
121
+ });
122
+ // Both should have same /48 prefix
123
+ expect(ip1).toBe("2001:0db8:1234:0000:0000:0000:0000:0000");
124
+ expect(ip2).toBe("2001:0db8:1234:0000:0000:0000:0000:0000");
125
+ expect(ip1).toBe(ip2);
126
+ });
127
+
128
+ it("should extract /32 subnet", () => {
129
+ /* cspell:disable-next-line */
130
+ const ip1 = normalizeIP("2001:db8:1234:5678:90ab:cdef:1234:5678", {
131
+ ipv6Subnet: 32,
132
+ });
133
+ const ip2 = normalizeIP("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff", {
134
+ ipv6Subnet: 32,
135
+ });
136
+ // Both should have same /32 prefix
137
+ expect(ip1).toBe("2001:0db8:0000:0000:0000:0000:0000:0000");
138
+ expect(ip2).toBe("2001:0db8:0000:0000:0000:0000:0000:0000");
139
+ expect(ip1).toBe(ip2);
140
+ });
141
+
142
+ it("should handle /64 subnet by default", () => {
143
+ const ip1 = normalizeIP("2001:db8::1");
144
+ const ip2 = normalizeIP("2001:db8::1", { ipv6Subnet: 64 });
145
+ expect(ip1).toBe(ip2);
146
+ expect(ip1).toBe("2001:0db8:0000:0000:0000:0000:0000:0000");
147
+ });
148
+
149
+ it("should not affect IPv4 addresses when ipv6Subnet is set", () => {
150
+ expect(normalizeIP("192.168.1.1", { ipv6Subnet: 64 })).toBe(
151
+ "192.168.1.1",
152
+ );
153
+ });
154
+ });
155
+
156
+ describe("Rate Limit Key Creation", () => {
157
+ it("should create keys with separator", () => {
158
+ expect(createRateLimitKey("192.168.1.1", "/sign-in")).toBe(
159
+ "192.168.1.1|/sign-in",
160
+ );
161
+ expect(createRateLimitKey("2001:db8::1", "/api/auth")).toBe(
162
+ "2001:db8::1|/api/auth",
163
+ );
164
+ });
165
+
166
+ it("should prevent collision attacks", () => {
167
+ // Without separator: "192.0.2.1" + "/sign-in" = "192.0.2.1/sign-in"
168
+ // "192.0.2" + ".1/sign-in" = "192.0.2.1/sign-in"
169
+ // With separator: they're different
170
+ const key1 = createRateLimitKey("192.0.2.1", "/sign-in");
171
+ const key2 = createRateLimitKey("192.0.2", ".1/sign-in");
172
+ expect(key1).not.toBe(key2);
173
+ expect(key1).toBe("192.0.2.1|/sign-in");
174
+ expect(key2).toBe("192.0.2|.1/sign-in");
175
+ });
176
+ });
177
+
178
+ describe("Security: Bypass Prevention", () => {
179
+ it("should prevent IPv6 representation bypass", () => {
180
+ // Attacker tries different representations of same address
181
+ const representations = [
182
+ "2001:db8::1",
183
+ "2001:DB8::1",
184
+ "2001:0db8::1",
185
+ "2001:db8:0::1",
186
+ "2001:0db8:0:0:0:0:0:1",
187
+ "2001:db8::0:1",
188
+ ];
189
+
190
+ const normalized = representations.map((ip) =>
191
+ normalizeIP(ip, { ipv6Subnet: 128 }),
192
+ );
193
+ // All should normalize to the same value
194
+ const uniqueValues = new Set(normalized);
195
+ expect(uniqueValues.size).toBe(1);
196
+ expect(normalized[0]).toBe("2001:0db8:0000:0000:0000:0000:0000:0001");
197
+ });
198
+
199
+ it("should prevent IPv4-mapped bypass", () => {
200
+ // Attacker switches between IPv4 and IPv4-mapped IPv6
201
+ const ip1 = normalizeIP("192.0.2.1");
202
+ const ip2 = normalizeIP("::ffff:192.0.2.1");
203
+ const ip3 = normalizeIP("::FFFF:192.0.2.1");
204
+ const ip4 = normalizeIP("::ffff:c000:0201");
205
+
206
+ // All should normalize to the same IPv4
207
+ expect(ip1).toBe("192.0.2.1");
208
+ expect(ip2).toBe("192.0.2.1");
209
+ expect(ip3).toBe("192.0.2.1");
210
+ expect(ip4).toBe("192.0.2.1");
211
+ });
212
+
213
+ it("should group IPv6 subnet attacks", () => {
214
+ // Attacker rotates through addresses in their /64 allocation
215
+ const attackIPs = [
216
+ "2001:db8:abcd:1234:0000:0000:0000:0001",
217
+ "2001:db8:abcd:1234:1111:2222:3333:4444",
218
+ "2001:db8:abcd:1234:ffff:ffff:ffff:ffff",
219
+ "2001:db8:abcd:1234:aaaa:bbbb:cccc:dddd",
220
+ ];
221
+
222
+ const normalized = attackIPs.map((ip) =>
223
+ normalizeIP(ip, { ipv6Subnet: 64 }),
224
+ );
225
+
226
+ // All should map to same /64 subnet
227
+ const uniqueValues = new Set(normalized);
228
+ expect(uniqueValues.size).toBe(1);
229
+ expect(normalized[0]).toBe("2001:0db8:abcd:1234:0000:0000:0000:0000");
230
+ });
231
+ });
232
+
233
+ describe("Edge Cases", () => {
234
+ it("should handle localhost addresses", () => {
235
+ expect(normalizeIP("127.0.0.1")).toBe("127.0.0.1");
236
+ expect(normalizeIP("::1", { ipv6Subnet: 128 })).toBe(
237
+ "0000:0000:0000:0000:0000:0000:0000:0001",
238
+ );
239
+ });
240
+
241
+ it("should handle all-zeros address", () => {
242
+ expect(normalizeIP("0.0.0.0")).toBe("0.0.0.0");
243
+ expect(normalizeIP("::", { ipv6Subnet: 128 })).toBe(
244
+ "0000:0000:0000:0000:0000:0000:0000:0000",
245
+ );
246
+ });
247
+
248
+ it("should handle link-local addresses", () => {
249
+ expect(normalizeIP("169.254.0.1")).toBe("169.254.0.1");
250
+ expect(normalizeIP("fe80::1", { ipv6Subnet: 128 })).toBe(
251
+ "fe80:0000:0000:0000:0000:0000:0000:0001",
252
+ );
253
+ });
254
+ });
255
+ });
@@ -0,0 +1,211 @@
1
+ import * as z from "zod";
2
+
3
+ /**
4
+ * Normalizes an IP address for consistent rate limiting.
5
+ *
6
+ * Features:
7
+ * - Normalizes IPv6 to canonical lowercase form
8
+ * - Converts IPv4-mapped IPv6 to IPv4
9
+ * - Supports IPv6 subnet extraction
10
+ * - Handles all edge cases (::1, ::, etc.)
11
+ */
12
+
13
+ interface NormalizeIPOptions {
14
+ /**
15
+ * For IPv6 addresses, extract the subnet prefix instead of full address.
16
+ * Common values: 32, 48, 64, 128 (default: 128 = full address)
17
+ *
18
+ * @default 128
19
+ */
20
+ ipv6Subnet?: 128 | 64 | 48 | 32;
21
+ }
22
+
23
+ /**
24
+ * Checks if an IP is valid IPv4 or IPv6
25
+ */
26
+ export function isValidIP(ip: string): boolean {
27
+ return z.ipv4().safeParse(ip).success || z.ipv6().safeParse(ip).success;
28
+ }
29
+
30
+ /**
31
+ * Checks if an IP is IPv6
32
+ */
33
+ function isIPv6(ip: string): boolean {
34
+ return z.ipv6().safeParse(ip).success;
35
+ }
36
+
37
+ /**
38
+ * Converts IPv4-mapped IPv6 address to IPv4
39
+ * e.g., "::ffff:192.0.2.1" -> "192.0.2.1"
40
+ */
41
+ function extractIPv4FromMapped(ipv6: string): string | null {
42
+ const lower = ipv6.toLowerCase();
43
+
44
+ // Handle ::ffff:192.0.2.1 format
45
+ if (lower.startsWith("::ffff:")) {
46
+ const ipv4Part = lower.substring(7);
47
+ // Check if it's a valid IPv4
48
+ if (z.ipv4().safeParse(ipv4Part).success) {
49
+ return ipv4Part;
50
+ }
51
+ }
52
+
53
+ // Handle full form: 0:0:0:0:0:ffff:192.0.2.1
54
+ const parts = ipv6.split(":");
55
+ if (parts.length === 7 && parts[5]?.toLowerCase() === "ffff") {
56
+ const ipv4Part = parts[6];
57
+ if (ipv4Part && z.ipv4().safeParse(ipv4Part).success) {
58
+ return ipv4Part;
59
+ }
60
+ }
61
+
62
+ // Handle hex-encoded IPv4 in mapped address
63
+ // e.g., ::ffff:c000:0201 -> 192.0.2.1
64
+ if (lower.includes("::ffff:") || lower.includes(":ffff:")) {
65
+ const groups = expandIPv6(ipv6);
66
+ if (
67
+ groups.length === 8 &&
68
+ groups[0] === "0000" &&
69
+ groups[1] === "0000" &&
70
+ groups[2] === "0000" &&
71
+ groups[3] === "0000" &&
72
+ groups[4] === "0000" &&
73
+ groups[5] === "ffff" &&
74
+ groups[6] &&
75
+ groups[7]
76
+ ) {
77
+ // Convert last two groups to IPv4
78
+ const byte1 = Number.parseInt(groups[6].substring(0, 2), 16);
79
+ const byte2 = Number.parseInt(groups[6].substring(2, 4), 16);
80
+ const byte3 = Number.parseInt(groups[7].substring(0, 2), 16);
81
+ const byte4 = Number.parseInt(groups[7].substring(2, 4), 16);
82
+ return `${byte1}.${byte2}.${byte3}.${byte4}`;
83
+ }
84
+ }
85
+
86
+ return null;
87
+ }
88
+
89
+ /**
90
+ * Expands a compressed IPv6 address to full form
91
+ * e.g., "2001:db8::1" -> ["2001", "0db8", "0000", "0000", "0000", "0000", "0000", "0001"]
92
+ */
93
+ function expandIPv6(ipv6: string): string[] {
94
+ // Handle :: notation (zero compression)
95
+ if (ipv6.includes("::")) {
96
+ const sides = ipv6.split("::");
97
+ const left = sides[0] ? sides[0].split(":") : [];
98
+ const right = sides[1] ? sides[1].split(":") : [];
99
+
100
+ // Calculate missing groups
101
+ const totalGroups = 8;
102
+ const missingGroups = totalGroups - left.length - right.length;
103
+ const zeros = Array(missingGroups).fill("0000");
104
+
105
+ // Pad existing groups to 4 digits
106
+ const paddedLeft = left.map((g) => g.padStart(4, "0"));
107
+ const paddedRight = right.map((g) => g.padStart(4, "0"));
108
+
109
+ return [...paddedLeft, ...zeros, ...paddedRight];
110
+ }
111
+
112
+ // No compression, just pad each group
113
+ return ipv6.split(":").map((g) => g.padStart(4, "0"));
114
+ }
115
+
116
+ /**
117
+ * Normalizes an IPv6 address to canonical form
118
+ * e.g., "2001:DB8::1" -> "2001:0db8:0000:0000:0000:0000:0000:0001"
119
+ */
120
+ function normalizeIPv6(
121
+ ipv6: string,
122
+ subnetPrefix?: 128 | 32 | 48 | 64,
123
+ ): string {
124
+ const groups = expandIPv6(ipv6);
125
+
126
+ if (subnetPrefix && subnetPrefix < 128) {
127
+ // Apply subnet mask
128
+ const prefix = subnetPrefix;
129
+ let bitsRemaining: number = prefix;
130
+
131
+ const maskedGroups = groups.map((group) => {
132
+ if (bitsRemaining <= 0) {
133
+ return "0000";
134
+ }
135
+ if (bitsRemaining >= 16) {
136
+ bitsRemaining -= 16;
137
+ return group;
138
+ }
139
+
140
+ // Partial mask for this group
141
+ const value = Number.parseInt(group, 16);
142
+ const mask = (0xffff << (16 - bitsRemaining)) & 0xffff;
143
+ const masked = value & mask;
144
+ bitsRemaining = 0;
145
+ return masked.toString(16).padStart(4, "0");
146
+ });
147
+
148
+ return maskedGroups.join(":").toLowerCase();
149
+ }
150
+
151
+ return groups.join(":").toLowerCase();
152
+ }
153
+
154
+ /**
155
+ * Normalizes an IP address (IPv4 or IPv6) for consistent rate limiting.
156
+ *
157
+ * @param ip - The IP address to normalize
158
+ * @param options - Normalization options
159
+ * @returns Normalized IP address
160
+ *
161
+ * @example
162
+ * normalizeIP("2001:DB8::1")
163
+ * // -> "2001:0db8:0000:0000:0000:0000:0000:0000"
164
+ *
165
+ * @example
166
+ * normalizeIP("::ffff:192.0.2.1")
167
+ * // -> "192.0.2.1" (converted to IPv4)
168
+ *
169
+ * @example
170
+ * normalizeIP("2001:db8::1", { ipv6Subnet: 64 })
171
+ * // -> "2001:0db8:0000:0000:0000:0000:0000:0000" (subnet /64)
172
+ */
173
+ export function normalizeIP(
174
+ ip: string,
175
+ options: NormalizeIPOptions = {},
176
+ ): string {
177
+ // IPv4 addresses are already normalized
178
+ if (z.ipv4().safeParse(ip).success) {
179
+ return ip.toLowerCase();
180
+ }
181
+
182
+ // Check if it's IPv6
183
+ if (!isIPv6(ip)) {
184
+ // Return as-is if not valid (shouldn't happen due to prior validation)
185
+ return ip.toLowerCase();
186
+ }
187
+
188
+ // Check for IPv4-mapped IPv6
189
+ const ipv4 = extractIPv4FromMapped(ip);
190
+ if (ipv4) {
191
+ return ipv4.toLowerCase();
192
+ }
193
+
194
+ // Normalize IPv6
195
+ const subnetPrefix = options.ipv6Subnet || 64;
196
+ return normalizeIPv6(ip, subnetPrefix);
197
+ }
198
+
199
+ /**
200
+ * Creates a rate limit key from IP and path
201
+ * Uses a separator to prevent collision attacks
202
+ *
203
+ * @param ip - The IP address (should be normalized)
204
+ * @param path - The request path
205
+ * @returns Rate limit key
206
+ */
207
+ export function createRateLimitKey(ip: string, path: string): string {
208
+ // Use | as separator to prevent collision attacks
209
+ // e.g., "192.0.2.1" + "/sign-in" vs "192.0.2" + ".1/sign-in"
210
+ return `${ip}|${path}`;
211
+ }
@@ -0,0 +1,25 @@
1
+ import { logger } from "../env";
2
+
3
+ export function safeJSONParse<T>(data: unknown): T | null {
4
+ function reviver(_: string, value: any): any {
5
+ if (typeof value === "string") {
6
+ const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
7
+ if (iso8601Regex.test(value)) {
8
+ const date = new Date(value);
9
+ if (!isNaN(date.getTime())) {
10
+ return date;
11
+ }
12
+ }
13
+ }
14
+ return value;
15
+ }
16
+ try {
17
+ if (typeof data !== "string") {
18
+ return data as T;
19
+ }
20
+ return JSON.parse(data, reviver);
21
+ } catch (e) {
22
+ logger.error("Error parsing JSON", { error: e });
23
+ return null;
24
+ }
25
+ }
@@ -0,0 +1,3 @@
1
+ export function capitalizeFirstLetter(str: string) {
2
+ return str.charAt(0).toUpperCase() + str.slice(1);
3
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Normalizes a request pathname by removing the basePath prefix and trailing slashes.
3
+ * This is useful for matching paths against configured path lists.
4
+ *
5
+ * @param requestUrl - The full request URL
6
+ * @param basePath - The base path of the auth API (e.g., "/api/auth")
7
+ * @returns The normalized path without basePath prefix or trailing slashes,
8
+ * or "/" if URL parsing fails
9
+ *
10
+ * @example
11
+ * normalizePathname("http://localhost:3000/api/auth/sso/saml2/callback/provider1", "/api/auth")
12
+ * // Returns: "/sso/saml2/callback/provider1"
13
+ *
14
+ * normalizePathname("http://localhost:3000/sso/saml2/callback/provider1/", "/")
15
+ * // Returns: "/sso/saml2/callback/provider1"
16
+ */
17
+ export function normalizePathname(
18
+ requestUrl: string,
19
+ basePath: string,
20
+ ): string {
21
+ let pathname: string;
22
+ try {
23
+ pathname = new URL(requestUrl).pathname.replace(/\/+$/, "") || "/";
24
+ } catch {
25
+ return "/";
26
+ }
27
+
28
+ if (basePath === "/" || basePath === "") {
29
+ return pathname;
30
+ }
31
+
32
+ // Check for exact match or proper path boundary (basePath followed by "/" or end)
33
+ // This prevents "/api/auth" from matching "/api/authevil/..."
34
+ if (pathname === basePath) {
35
+ return "/";
36
+ }
37
+
38
+ if (pathname.startsWith(basePath + "/")) {
39
+ return pathname.slice(basePath.length).replace(/\/+$/, "") || "/";
40
+ }
41
+
42
+ return pathname;
43
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "lib": ["esnext", "dom", "dom.iterable"],
5
+ "types": ["node"]
6
+ }
7
+ }
@@ -0,0 +1,35 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { defineConfig } from "tsdown";
3
+
4
+ const packageJson = JSON.parse(
5
+ await readFile(new URL("./package.json", import.meta.url), "utf-8"),
6
+ );
7
+
8
+ export default defineConfig({
9
+ dts: { build: true, incremental: true },
10
+ format: ["esm"],
11
+ entry: [
12
+ "./src/index.ts",
13
+ "./src/db/index.ts",
14
+ "./src/db/adapter/index.ts",
15
+ "./src/async_hooks/index.ts",
16
+ "./src/async_hooks/pure.index.ts",
17
+ "./src/context/index.ts",
18
+ "./src/env/index.ts",
19
+ "./src/oauth2/index.ts",
20
+ "./src/api/index.ts",
21
+ "./src/social-providers/index.ts",
22
+ "./src/utils/*.ts",
23
+ "!./src/utils/*.test.ts",
24
+ "./src/error/index.ts",
25
+ ],
26
+ external: ["@better-auth/core/async_hooks"],
27
+ env: {
28
+ BETTER_AUTH_VERSION: packageJson.version,
29
+ BETTER_AUTH_TELEMETRY_ENDPOINT:
30
+ process.env.BETTER_AUTH_TELEMETRY_ENDPOINT ?? "",
31
+ },
32
+ sourcemap: true,
33
+ unbundle: true,
34
+ clean: true,
35
+ });
@@ -0,0 +1,3 @@
1
+ import { defineProject } from "vitest/config";
2
+
3
+ export default defineProject({});