@better-auth/core 1.4.17 → 1.4.19

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 (261) hide show
  1. package/.turbo/turbo-build.log +253 -176
  2. package/dist/api/index.d.mts +11 -5
  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 +2 -1
  13. package/dist/context/endpoint-context.mjs.map +1 -0
  14. package/dist/context/global.d.mts +2 -1
  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 +2 -1
  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 +2 -1
  22. package/dist/context/transaction.mjs.map +1 -0
  23. package/dist/db/adapter/factory.d.mts +2 -1
  24. package/dist/db/adapter/factory.mjs +9 -27
  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 +2 -1
  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 +2 -1
  31. package/dist/db/adapter/get-default-model-name.mjs.map +1 -0
  32. package/dist/db/adapter/get-field-attributes.d.mts +2 -1
  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 +2 -1
  39. package/dist/db/adapter/get-id-field.mjs +2 -1
  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 +5 -1
  45. package/dist/db/adapter/types.d.mts +2 -1
  46. package/dist/db/adapter/utils.d.mts +2 -1
  47. package/dist/db/adapter/utils.mjs +2 -1
  48. package/dist/db/adapter/utils.mjs.map +1 -0
  49. package/dist/db/get-tables.d.mts +2 -1
  50. package/dist/db/get-tables.mjs +8 -2
  51. package/dist/db/get-tables.mjs.map +1 -0
  52. package/dist/db/plugin.d.mts +2 -1
  53. package/dist/db/schema/account.d.mts +2 -1
  54. package/dist/db/schema/account.mjs +2 -1
  55. package/dist/db/schema/account.mjs.map +1 -0
  56. package/dist/db/schema/rate-limit.d.mts +2 -1
  57. package/dist/db/schema/rate-limit.mjs +2 -1
  58. package/dist/db/schema/rate-limit.mjs.map +1 -0
  59. package/dist/db/schema/session.d.mts +2 -1
  60. package/dist/db/schema/session.mjs +2 -1
  61. package/dist/db/schema/session.mjs.map +1 -0
  62. package/dist/db/schema/shared.d.mts +2 -1
  63. package/dist/db/schema/shared.mjs +2 -1
  64. package/dist/db/schema/shared.mjs.map +1 -0
  65. package/dist/db/schema/user.d.mts +2 -1
  66. package/dist/db/schema/user.mjs +2 -1
  67. package/dist/db/schema/user.mjs.map +1 -0
  68. package/dist/db/schema/verification.d.mts +2 -1
  69. package/dist/db/schema/verification.mjs +2 -1
  70. package/dist/db/schema/verification.mjs.map +1 -0
  71. package/dist/db/type.d.mts +2 -1
  72. package/dist/env/color-depth.d.mts +2 -1
  73. package/dist/env/color-depth.mjs +2 -1
  74. package/dist/env/color-depth.mjs.map +1 -0
  75. package/dist/env/env-impl.d.mts +3 -2
  76. package/dist/env/env-impl.mjs +3 -2
  77. package/dist/env/env-impl.mjs.map +1 -0
  78. package/dist/env/logger.d.mts +2 -1
  79. package/dist/env/logger.mjs +3 -2
  80. package/dist/env/logger.mjs.map +1 -0
  81. package/dist/error/codes.d.mts +2 -1
  82. package/dist/error/codes.mjs +2 -1
  83. package/dist/error/codes.mjs.map +1 -0
  84. package/dist/error/index.d.mts +2 -1
  85. package/dist/error/index.mjs +2 -1
  86. package/dist/error/index.mjs.map +1 -0
  87. package/dist/index.d.mts +2 -2
  88. package/dist/oauth2/client-credentials-token.d.mts +2 -1
  89. package/dist/oauth2/client-credentials-token.mjs +2 -1
  90. package/dist/oauth2/client-credentials-token.mjs.map +1 -0
  91. package/dist/oauth2/create-authorization-url.d.mts +2 -1
  92. package/dist/oauth2/create-authorization-url.mjs +2 -1
  93. package/dist/oauth2/create-authorization-url.mjs.map +1 -0
  94. package/dist/oauth2/oauth-provider.d.mts +3 -2
  95. package/dist/oauth2/refresh-access-token.d.mts +2 -1
  96. package/dist/oauth2/refresh-access-token.mjs +2 -1
  97. package/dist/oauth2/refresh-access-token.mjs.map +1 -0
  98. package/dist/oauth2/utils.d.mts +2 -1
  99. package/dist/oauth2/utils.mjs +2 -1
  100. package/dist/oauth2/utils.mjs.map +1 -0
  101. package/dist/oauth2/validate-authorization-code.d.mts +6 -2
  102. package/dist/oauth2/validate-authorization-code.mjs +7 -12
  103. package/dist/oauth2/validate-authorization-code.mjs.map +1 -0
  104. package/dist/oauth2/verify.d.mts +2 -1
  105. package/dist/oauth2/verify.mjs +2 -1
  106. package/dist/oauth2/verify.mjs.map +1 -0
  107. package/dist/social-providers/apple.d.mts +2 -1
  108. package/dist/social-providers/apple.mjs +22 -15
  109. package/dist/social-providers/apple.mjs.map +1 -0
  110. package/dist/social-providers/atlassian.d.mts +2 -1
  111. package/dist/social-providers/atlassian.mjs +2 -1
  112. package/dist/social-providers/atlassian.mjs.map +1 -0
  113. package/dist/social-providers/cognito.d.mts +2 -1
  114. package/dist/social-providers/cognito.mjs +2 -1
  115. package/dist/social-providers/cognito.mjs.map +1 -0
  116. package/dist/social-providers/discord.d.mts +2 -1
  117. package/dist/social-providers/discord.mjs +2 -1
  118. package/dist/social-providers/discord.mjs.map +1 -0
  119. package/dist/social-providers/dropbox.d.mts +2 -1
  120. package/dist/social-providers/dropbox.mjs +2 -1
  121. package/dist/social-providers/dropbox.mjs.map +1 -0
  122. package/dist/social-providers/facebook.d.mts +2 -1
  123. package/dist/social-providers/facebook.mjs +5 -4
  124. package/dist/social-providers/facebook.mjs.map +1 -0
  125. package/dist/social-providers/figma.d.mts +2 -1
  126. package/dist/social-providers/figma.mjs +2 -1
  127. package/dist/social-providers/figma.mjs.map +1 -0
  128. package/dist/social-providers/github.d.mts +3 -2
  129. package/dist/social-providers/github.mjs +22 -5
  130. package/dist/social-providers/github.mjs.map +1 -0
  131. package/dist/social-providers/gitlab.d.mts +2 -1
  132. package/dist/social-providers/gitlab.mjs +2 -1
  133. package/dist/social-providers/gitlab.mjs.map +1 -0
  134. package/dist/social-providers/google.d.mts +2 -1
  135. package/dist/social-providers/google.mjs +18 -13
  136. package/dist/social-providers/google.mjs.map +1 -0
  137. package/dist/social-providers/huggingface.d.mts +2 -1
  138. package/dist/social-providers/huggingface.mjs +2 -1
  139. package/dist/social-providers/huggingface.mjs.map +1 -0
  140. package/dist/social-providers/index.d.mts +5 -3
  141. package/dist/social-providers/index.mjs +3 -2
  142. package/dist/social-providers/index.mjs.map +1 -0
  143. package/dist/social-providers/kakao.d.mts +2 -1
  144. package/dist/social-providers/kakao.mjs +2 -1
  145. package/dist/social-providers/kakao.mjs.map +1 -0
  146. package/dist/social-providers/kick.d.mts +2 -1
  147. package/dist/social-providers/kick.mjs +2 -1
  148. package/dist/social-providers/kick.mjs.map +1 -0
  149. package/dist/social-providers/line.d.mts +2 -1
  150. package/dist/social-providers/line.mjs +2 -1
  151. package/dist/social-providers/line.mjs.map +1 -0
  152. package/dist/social-providers/linear.d.mts +2 -1
  153. package/dist/social-providers/linear.mjs +2 -1
  154. package/dist/social-providers/linear.mjs.map +1 -0
  155. package/dist/social-providers/linkedin.d.mts +2 -1
  156. package/dist/social-providers/linkedin.mjs +2 -1
  157. package/dist/social-providers/linkedin.mjs.map +1 -0
  158. package/dist/social-providers/microsoft-entra-id.d.mts +4 -1
  159. package/dist/social-providers/microsoft-entra-id.mjs +36 -2
  160. package/dist/social-providers/microsoft-entra-id.mjs.map +1 -0
  161. package/dist/social-providers/naver.d.mts +2 -1
  162. package/dist/social-providers/naver.mjs +2 -1
  163. package/dist/social-providers/naver.mjs.map +1 -0
  164. package/dist/social-providers/notion.d.mts +2 -1
  165. package/dist/social-providers/notion.mjs +2 -1
  166. package/dist/social-providers/notion.mjs.map +1 -0
  167. package/dist/social-providers/paybin.d.mts +2 -1
  168. package/dist/social-providers/paybin.mjs +2 -1
  169. package/dist/social-providers/paybin.mjs.map +1 -0
  170. package/dist/social-providers/paypal.d.mts +2 -1
  171. package/dist/social-providers/paypal.mjs +2 -1
  172. package/dist/social-providers/paypal.mjs.map +1 -0
  173. package/dist/social-providers/polar.d.mts +2 -1
  174. package/dist/social-providers/polar.mjs +2 -1
  175. package/dist/social-providers/polar.mjs.map +1 -0
  176. package/dist/social-providers/reddit.d.mts +2 -1
  177. package/dist/social-providers/reddit.mjs +2 -1
  178. package/dist/social-providers/reddit.mjs.map +1 -0
  179. package/dist/social-providers/roblox.d.mts +2 -1
  180. package/dist/social-providers/roblox.mjs +2 -1
  181. package/dist/social-providers/roblox.mjs.map +1 -0
  182. package/dist/social-providers/salesforce.d.mts +2 -1
  183. package/dist/social-providers/salesforce.mjs +2 -1
  184. package/dist/social-providers/salesforce.mjs.map +1 -0
  185. package/dist/social-providers/slack.d.mts +2 -1
  186. package/dist/social-providers/slack.mjs +2 -1
  187. package/dist/social-providers/slack.mjs.map +1 -0
  188. package/dist/social-providers/spotify.d.mts +2 -1
  189. package/dist/social-providers/spotify.mjs +2 -1
  190. package/dist/social-providers/spotify.mjs.map +1 -0
  191. package/dist/social-providers/tiktok.d.mts +2 -1
  192. package/dist/social-providers/tiktok.mjs +2 -1
  193. package/dist/social-providers/tiktok.mjs.map +1 -0
  194. package/dist/social-providers/twitch.d.mts +2 -1
  195. package/dist/social-providers/twitch.mjs +2 -1
  196. package/dist/social-providers/twitch.mjs.map +1 -0
  197. package/dist/social-providers/twitter.d.mts +3 -2
  198. package/dist/social-providers/twitter.mjs +2 -1
  199. package/dist/social-providers/twitter.mjs.map +1 -0
  200. package/dist/social-providers/vercel.d.mts +2 -1
  201. package/dist/social-providers/vercel.mjs +2 -1
  202. package/dist/social-providers/vercel.mjs.map +1 -0
  203. package/dist/social-providers/vk.d.mts +2 -1
  204. package/dist/social-providers/vk.mjs +2 -1
  205. package/dist/social-providers/vk.mjs.map +1 -0
  206. package/dist/social-providers/zoom.d.mts +3 -3
  207. package/dist/social-providers/zoom.mjs +2 -1
  208. package/dist/social-providers/zoom.mjs.map +1 -0
  209. package/dist/types/context.d.mts +7 -3
  210. package/dist/types/cookie.d.mts +2 -1
  211. package/dist/types/helper.d.mts +2 -1
  212. package/dist/types/index.d.mts +1 -1
  213. package/dist/types/init-options.d.mts +36 -33
  214. package/dist/types/plugin-client.d.mts +2 -1
  215. package/dist/types/plugin.d.mts +2 -1
  216. package/dist/utils/db.d.mts +13 -0
  217. package/dist/utils/db.mjs +17 -0
  218. package/dist/utils/db.mjs.map +1 -0
  219. package/dist/utils/deprecate.d.mts +2 -1
  220. package/dist/utils/deprecate.mjs +2 -1
  221. package/dist/utils/deprecate.mjs.map +1 -0
  222. package/dist/utils/error-codes.d.mts +2 -1
  223. package/dist/utils/error-codes.mjs +2 -1
  224. package/dist/utils/error-codes.mjs.map +1 -0
  225. package/dist/utils/id.d.mts +2 -1
  226. package/dist/utils/id.mjs +2 -1
  227. package/dist/utils/id.mjs.map +1 -0
  228. package/dist/utils/index.d.mts +2 -1
  229. package/dist/utils/index.mjs +2 -1
  230. package/dist/utils/ip.d.mts +2 -1
  231. package/dist/utils/ip.mjs +2 -1
  232. package/dist/utils/ip.mjs.map +1 -0
  233. package/dist/utils/json.d.mts +2 -1
  234. package/dist/utils/json.mjs +2 -1
  235. package/dist/utils/json.mjs.map +1 -0
  236. package/dist/utils/string.d.mts +2 -1
  237. package/dist/utils/string.mjs +2 -1
  238. package/dist/utils/string.mjs.map +1 -0
  239. package/dist/utils/url.d.mts +2 -1
  240. package/dist/utils/url.mjs +2 -1
  241. package/dist/utils/url.mjs.map +1 -0
  242. package/package.json +1 -1
  243. package/src/db/adapter/factory.ts +5 -41
  244. package/src/db/adapter/index.ts +3 -0
  245. package/src/db/get-tables.ts +5 -0
  246. package/src/env/env-impl.ts +2 -2
  247. package/src/env/logger.ts +1 -1
  248. package/src/oauth2/oauth-provider.ts +1 -1
  249. package/src/oauth2/validate-authorization-code.ts +13 -26
  250. package/src/oauth2/validate-token.test.ts +241 -0
  251. package/src/social-providers/apple.ts +37 -24
  252. package/src/social-providers/facebook.ts +3 -3
  253. package/src/social-providers/github.ts +25 -3
  254. package/src/social-providers/google.ts +18 -14
  255. package/src/social-providers/microsoft-entra-id.ts +84 -1
  256. package/src/types/context.ts +6 -3
  257. package/src/types/index.ts +6 -1
  258. package/src/types/init-options.ts +44 -36
  259. package/src/utils/db.ts +20 -0
  260. package/src/utils/index.ts +1 -0
  261. package/tsdown.config.ts +3 -0
@@ -6,4 +6,5 @@ type InvalidKeyError<K$1 extends string> = `Invalid error code key: "${K$1}" - m
6
6
  type ValidateErrorCodes<T> = { [K in keyof T]: K extends string ? IsValidUpperSnakeCase<K> extends false ? InvalidKeyError<K> : T[K] : T[K] };
7
7
  declare function defineErrorCodes<const T extends Record<string, string>>(codes: ValidateErrorCodes<T>): T;
8
8
  //#endregion
9
- export { defineErrorCodes };
9
+ export { defineErrorCodes };
10
+ //# sourceMappingURL=error-codes.d.mts.map
@@ -4,4 +4,5 @@ function defineErrorCodes(codes) {
4
4
  }
5
5
 
6
6
  //#endregion
7
- export { defineErrorCodes };
7
+ export { defineErrorCodes };
8
+ //# sourceMappingURL=error-codes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-codes.mjs","names":[],"sources":["../../src/utils/error-codes.ts"],"sourcesContent":["type UpperLetter =\n\t| \"A\"\n\t| \"B\"\n\t| \"C\"\n\t| \"D\"\n\t| \"E\"\n\t| \"F\"\n\t| \"G\"\n\t| \"H\"\n\t| \"I\"\n\t| \"J\"\n\t| \"K\"\n\t| \"L\"\n\t| \"M\"\n\t| \"N\"\n\t| \"O\"\n\t| \"P\"\n\t| \"Q\"\n\t| \"R\"\n\t| \"S\"\n\t| \"T\"\n\t| \"U\"\n\t| \"V\"\n\t| \"W\"\n\t| \"X\"\n\t| \"Y\"\n\t| \"Z\";\ntype SpecialCharacter = \"_\";\n\ntype IsValidUpperSnakeCase<S extends string> = S extends `${infer F}${infer R}`\n\t? F extends UpperLetter | SpecialCharacter\n\t\t? IsValidUpperSnakeCase<R>\n\t\t: false\n\t: true;\n\ntype InvalidKeyError<K extends string> =\n\t`Invalid error code key: \"${K}\" - must only contain uppercase letters (A-Z) and underscores (_)`;\n\ntype ValidateErrorCodes<T> = {\n\t[K in keyof T]: K extends string\n\t\t? IsValidUpperSnakeCase<K> extends false\n\t\t\t? InvalidKeyError<K>\n\t\t\t: T[K]\n\t\t: T[K];\n};\n\nexport function defineErrorCodes<const T extends Record<string, string>>(\n\tcodes: ValidateErrorCodes<T>,\n): T {\n\treturn codes as T;\n}\n"],"mappings":";AA8CA,SAAgB,iBACf,OACI;AACJ,QAAO"}
@@ -1,4 +1,5 @@
1
1
  //#region src/utils/id.d.ts
2
2
  declare const generateId: (size?: number) => string;
3
3
  //#endregion
4
- export { generateId };
4
+ export { generateId };
5
+ //# sourceMappingURL=id.d.mts.map
package/dist/utils/id.mjs CHANGED
@@ -6,4 +6,5 @@ const generateId = (size) => {
6
6
  };
7
7
 
8
8
  //#endregion
9
- export { generateId };
9
+ export { generateId };
10
+ //# sourceMappingURL=id.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.mjs","names":[],"sources":["../../src/utils/id.ts"],"sourcesContent":["import { createRandomStringGenerator } from \"@better-auth/utils/random\";\n\nexport const generateId = (size?: number) => {\n\treturn createRandomStringGenerator(\"a-z\", \"A-Z\", \"0-9\")(size || 32);\n};\n"],"mappings":";;;AAEA,MAAa,cAAc,SAAkB;AAC5C,QAAO,4BAA4B,OAAO,OAAO,MAAM,CAAC,QAAQ,GAAG"}
@@ -1,3 +1,4 @@
1
+ import { filterOutputFields } from "./db.mjs";
1
2
  import { deprecate } from "./deprecate.mjs";
2
3
  import { defineErrorCodes } from "./error-codes.mjs";
3
4
  import { generateId } from "./id.mjs";
@@ -5,4 +6,4 @@ import { createRateLimitKey, isValidIP, normalizeIP } from "./ip.mjs";
5
6
  import { safeJSONParse } from "./json.mjs";
6
7
  import { capitalizeFirstLetter } from "./string.mjs";
7
8
  import { normalizePathname } from "./url.mjs";
8
- export { capitalizeFirstLetter, createRateLimitKey, defineErrorCodes, deprecate, generateId, isValidIP, normalizeIP, normalizePathname, safeJSONParse };
9
+ export { capitalizeFirstLetter, createRateLimitKey, defineErrorCodes, deprecate, filterOutputFields, generateId, isValidIP, normalizeIP, normalizePathname, safeJSONParse };
@@ -1,3 +1,4 @@
1
+ import { filterOutputFields } from "./db.mjs";
1
2
  import { deprecate } from "./deprecate.mjs";
2
3
  import { defineErrorCodes } from "./error-codes.mjs";
3
4
  import { generateId } from "./id.mjs";
@@ -6,4 +7,4 @@ import { safeJSONParse } from "./json.mjs";
6
7
  import { capitalizeFirstLetter } from "./string.mjs";
7
8
  import { normalizePathname } from "./url.mjs";
8
9
 
9
- export { capitalizeFirstLetter, createRateLimitKey, defineErrorCodes, deprecate, generateId, isValidIP, normalizeIP, normalizePathname, safeJSONParse };
10
+ export { capitalizeFirstLetter, createRateLimitKey, defineErrorCodes, deprecate, filterOutputFields, generateId, isValidIP, normalizeIP, normalizePathname, safeJSONParse };
@@ -51,4 +51,5 @@ declare function normalizeIP(ip: string, options?: NormalizeIPOptions): string;
51
51
  */
52
52
  declare function createRateLimitKey(ip: string, path: string): string;
53
53
  //#endregion
54
- export { createRateLimitKey, isValidIP, normalizeIP };
54
+ export { createRateLimitKey, isValidIP, normalizeIP };
55
+ //# sourceMappingURL=ip.d.mts.map
package/dist/utils/ip.mjs CHANGED
@@ -115,4 +115,5 @@ function createRateLimitKey(ip, path) {
115
115
  }
116
116
 
117
117
  //#endregion
118
- export { createRateLimitKey, isValidIP, normalizeIP };
118
+ export { createRateLimitKey, isValidIP, normalizeIP };
119
+ //# sourceMappingURL=ip.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ip.mjs","names":["bitsRemaining: number"],"sources":["../../src/utils/ip.ts"],"sourcesContent":["import * as z from \"zod\";\n\n/**\n * Normalizes an IP address for consistent rate limiting.\n *\n * Features:\n * - Normalizes IPv6 to canonical lowercase form\n * - Converts IPv4-mapped IPv6 to IPv4\n * - Supports IPv6 subnet extraction\n * - Handles all edge cases (::1, ::, etc.)\n */\n\ninterface NormalizeIPOptions {\n\t/**\n\t * For IPv6 addresses, extract the subnet prefix instead of full address.\n\t * Common values: 32, 48, 64, 128 (default: 128 = full address)\n\t *\n\t * @default 128\n\t */\n\tipv6Subnet?: 128 | 64 | 48 | 32;\n}\n\n/**\n * Checks if an IP is valid IPv4 or IPv6\n */\nexport function isValidIP(ip: string): boolean {\n\treturn z.ipv4().safeParse(ip).success || z.ipv6().safeParse(ip).success;\n}\n\n/**\n * Checks if an IP is IPv6\n */\nfunction isIPv6(ip: string): boolean {\n\treturn z.ipv6().safeParse(ip).success;\n}\n\n/**\n * Converts IPv4-mapped IPv6 address to IPv4\n * e.g., \"::ffff:192.0.2.1\" -> \"192.0.2.1\"\n */\nfunction extractIPv4FromMapped(ipv6: string): string | null {\n\tconst lower = ipv6.toLowerCase();\n\n\t// Handle ::ffff:192.0.2.1 format\n\tif (lower.startsWith(\"::ffff:\")) {\n\t\tconst ipv4Part = lower.substring(7);\n\t\t// Check if it's a valid IPv4\n\t\tif (z.ipv4().safeParse(ipv4Part).success) {\n\t\t\treturn ipv4Part;\n\t\t}\n\t}\n\n\t// Handle full form: 0:0:0:0:0:ffff:192.0.2.1\n\tconst parts = ipv6.split(\":\");\n\tif (parts.length === 7 && parts[5]?.toLowerCase() === \"ffff\") {\n\t\tconst ipv4Part = parts[6];\n\t\tif (ipv4Part && z.ipv4().safeParse(ipv4Part).success) {\n\t\t\treturn ipv4Part;\n\t\t}\n\t}\n\n\t// Handle hex-encoded IPv4 in mapped address\n\t// e.g., ::ffff:c000:0201 -> 192.0.2.1\n\tif (lower.includes(\"::ffff:\") || lower.includes(\":ffff:\")) {\n\t\tconst groups = expandIPv6(ipv6);\n\t\tif (\n\t\t\tgroups.length === 8 &&\n\t\t\tgroups[0] === \"0000\" &&\n\t\t\tgroups[1] === \"0000\" &&\n\t\t\tgroups[2] === \"0000\" &&\n\t\t\tgroups[3] === \"0000\" &&\n\t\t\tgroups[4] === \"0000\" &&\n\t\t\tgroups[5] === \"ffff\" &&\n\t\t\tgroups[6] &&\n\t\t\tgroups[7]\n\t\t) {\n\t\t\t// Convert last two groups to IPv4\n\t\t\tconst byte1 = Number.parseInt(groups[6].substring(0, 2), 16);\n\t\t\tconst byte2 = Number.parseInt(groups[6].substring(2, 4), 16);\n\t\t\tconst byte3 = Number.parseInt(groups[7].substring(0, 2), 16);\n\t\t\tconst byte4 = Number.parseInt(groups[7].substring(2, 4), 16);\n\t\t\treturn `${byte1}.${byte2}.${byte3}.${byte4}`;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Expands a compressed IPv6 address to full form\n * e.g., \"2001:db8::1\" -> [\"2001\", \"0db8\", \"0000\", \"0000\", \"0000\", \"0000\", \"0000\", \"0001\"]\n */\nfunction expandIPv6(ipv6: string): string[] {\n\t// Handle :: notation (zero compression)\n\tif (ipv6.includes(\"::\")) {\n\t\tconst sides = ipv6.split(\"::\");\n\t\tconst left = sides[0] ? sides[0].split(\":\") : [];\n\t\tconst right = sides[1] ? sides[1].split(\":\") : [];\n\n\t\t// Calculate missing groups\n\t\tconst totalGroups = 8;\n\t\tconst missingGroups = totalGroups - left.length - right.length;\n\t\tconst zeros = Array(missingGroups).fill(\"0000\");\n\n\t\t// Pad existing groups to 4 digits\n\t\tconst paddedLeft = left.map((g) => g.padStart(4, \"0\"));\n\t\tconst paddedRight = right.map((g) => g.padStart(4, \"0\"));\n\n\t\treturn [...paddedLeft, ...zeros, ...paddedRight];\n\t}\n\n\t// No compression, just pad each group\n\treturn ipv6.split(\":\").map((g) => g.padStart(4, \"0\"));\n}\n\n/**\n * Normalizes an IPv6 address to canonical form\n * e.g., \"2001:DB8::1\" -> \"2001:0db8:0000:0000:0000:0000:0000:0001\"\n */\nfunction normalizeIPv6(\n\tipv6: string,\n\tsubnetPrefix?: 128 | 32 | 48 | 64,\n): string {\n\tconst groups = expandIPv6(ipv6);\n\n\tif (subnetPrefix && subnetPrefix < 128) {\n\t\t// Apply subnet mask\n\t\tconst prefix = subnetPrefix;\n\t\tlet bitsRemaining: number = prefix;\n\n\t\tconst maskedGroups = groups.map((group) => {\n\t\t\tif (bitsRemaining <= 0) {\n\t\t\t\treturn \"0000\";\n\t\t\t}\n\t\t\tif (bitsRemaining >= 16) {\n\t\t\t\tbitsRemaining -= 16;\n\t\t\t\treturn group;\n\t\t\t}\n\n\t\t\t// Partial mask for this group\n\t\t\tconst value = Number.parseInt(group, 16);\n\t\t\tconst mask = (0xffff << (16 - bitsRemaining)) & 0xffff;\n\t\t\tconst masked = value & mask;\n\t\t\tbitsRemaining = 0;\n\t\t\treturn masked.toString(16).padStart(4, \"0\");\n\t\t});\n\n\t\treturn maskedGroups.join(\":\").toLowerCase();\n\t}\n\n\treturn groups.join(\":\").toLowerCase();\n}\n\n/**\n * Normalizes an IP address (IPv4 or IPv6) for consistent rate limiting.\n *\n * @param ip - The IP address to normalize\n * @param options - Normalization options\n * @returns Normalized IP address\n *\n * @example\n * normalizeIP(\"2001:DB8::1\")\n * // -> \"2001:0db8:0000:0000:0000:0000:0000:0000\"\n *\n * @example\n * normalizeIP(\"::ffff:192.0.2.1\")\n * // -> \"192.0.2.1\" (converted to IPv4)\n *\n * @example\n * normalizeIP(\"2001:db8::1\", { ipv6Subnet: 64 })\n * // -> \"2001:0db8:0000:0000:0000:0000:0000:0000\" (subnet /64)\n */\nexport function normalizeIP(\n\tip: string,\n\toptions: NormalizeIPOptions = {},\n): string {\n\t// IPv4 addresses are already normalized\n\tif (z.ipv4().safeParse(ip).success) {\n\t\treturn ip.toLowerCase();\n\t}\n\n\t// Check if it's IPv6\n\tif (!isIPv6(ip)) {\n\t\t// Return as-is if not valid (shouldn't happen due to prior validation)\n\t\treturn ip.toLowerCase();\n\t}\n\n\t// Check for IPv4-mapped IPv6\n\tconst ipv4 = extractIPv4FromMapped(ip);\n\tif (ipv4) {\n\t\treturn ipv4.toLowerCase();\n\t}\n\n\t// Normalize IPv6\n\tconst subnetPrefix = options.ipv6Subnet || 64;\n\treturn normalizeIPv6(ip, subnetPrefix);\n}\n\n/**\n * Creates a rate limit key from IP and path\n * Uses a separator to prevent collision attacks\n *\n * @param ip - The IP address (should be normalized)\n * @param path - The request path\n * @returns Rate limit key\n */\nexport function createRateLimitKey(ip: string, path: string): string {\n\t// Use | as separator to prevent collision attacks\n\t// e.g., \"192.0.2.1\" + \"/sign-in\" vs \"192.0.2\" + \".1/sign-in\"\n\treturn `${ip}|${path}`;\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  //#region src/utils/json.d.ts
2
2
  declare function safeJSONParse<T>(data: unknown): T | null;
3
3
  //#endregion
4
- export { safeJSONParse };
4
+ export { safeJSONParse };
5
+ //# sourceMappingURL=json.d.mts.map
@@ -22,4 +22,5 @@ function safeJSONParse(data) {
22
22
  }
23
23
 
24
24
  //#endregion
25
- export { safeJSONParse };
25
+ export { safeJSONParse };
26
+ //# sourceMappingURL=json.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.mjs","names":[],"sources":["../../src/utils/json.ts"],"sourcesContent":["import { logger } from \"../env\";\n\nexport function safeJSONParse<T>(data: unknown): T | null {\n\tfunction reviver(_: string, value: any): any {\n\t\tif (typeof value === \"string\") {\n\t\t\tconst iso8601Regex = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z$/;\n\t\t\tif (iso8601Regex.test(value)) {\n\t\t\t\tconst date = new Date(value);\n\t\t\t\tif (!isNaN(date.getTime())) {\n\t\t\t\t\treturn date;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn value;\n\t}\n\ttry {\n\t\tif (typeof data !== \"string\") {\n\t\t\treturn data as T;\n\t\t}\n\t\treturn JSON.parse(data, reviver);\n\t} catch (e) {\n\t\tlogger.error(\"Error parsing JSON\", { error: e });\n\t\treturn null;\n\t}\n}\n"],"mappings":";;;;AAEA,SAAgB,cAAiB,MAAyB;CACzD,SAAS,QAAQ,GAAW,OAAiB;AAC5C,MAAI,OAAO,UAAU,UAEpB;OADqB,mDACJ,KAAK,MAAM,EAAE;IAC7B,MAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,QAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CACzB,QAAO;;;AAIV,SAAO;;AAER,KAAI;AACH,MAAI,OAAO,SAAS,SACnB,QAAO;AAER,SAAO,KAAK,MAAM,MAAM,QAAQ;UACxB,GAAG;AACX,SAAO,MAAM,sBAAsB,EAAE,OAAO,GAAG,CAAC;AAChD,SAAO"}
@@ -1,4 +1,5 @@
1
1
  //#region src/utils/string.d.ts
2
2
  declare function capitalizeFirstLetter(str: string): string;
3
3
  //#endregion
4
- export { capitalizeFirstLetter };
4
+ export { capitalizeFirstLetter };
5
+ //# sourceMappingURL=string.d.mts.map
@@ -4,4 +4,5 @@ function capitalizeFirstLetter(str) {
4
4
  }
5
5
 
6
6
  //#endregion
7
- export { capitalizeFirstLetter };
7
+ export { capitalizeFirstLetter };
8
+ //# sourceMappingURL=string.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"string.mjs","names":[],"sources":["../../src/utils/string.ts"],"sourcesContent":["export function capitalizeFirstLetter(str: string) {\n\treturn str.charAt(0).toUpperCase() + str.slice(1);\n}\n"],"mappings":";AAAA,SAAgB,sBAAsB,KAAa;AAClD,QAAO,IAAI,OAAO,EAAE,CAAC,aAAa,GAAG,IAAI,MAAM,EAAE"}
@@ -17,4 +17,5 @@
17
17
  */
18
18
  declare function normalizePathname(requestUrl: string, basePath: string): string;
19
19
  //#endregion
20
- export { normalizePathname };
20
+ export { normalizePathname };
21
+ //# sourceMappingURL=url.d.mts.map
@@ -29,4 +29,5 @@ function normalizePathname(requestUrl, basePath) {
29
29
  }
30
30
 
31
31
  //#endregion
32
- export { normalizePathname };
32
+ export { normalizePathname };
33
+ //# sourceMappingURL=url.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.mjs","names":["pathname: string"],"sources":["../../src/utils/url.ts"],"sourcesContent":["/**\n * Normalizes a request pathname by removing the basePath prefix and trailing slashes.\n * This is useful for matching paths against configured path lists.\n *\n * @param requestUrl - The full request URL\n * @param basePath - The base path of the auth API (e.g., \"/api/auth\")\n * @returns The normalized path without basePath prefix or trailing slashes,\n * or \"/\" if URL parsing fails\n *\n * @example\n * normalizePathname(\"http://localhost:3000/api/auth/sso/saml2/callback/provider1\", \"/api/auth\")\n * // Returns: \"/sso/saml2/callback/provider1\"\n *\n * normalizePathname(\"http://localhost:3000/sso/saml2/callback/provider1/\", \"/\")\n * // Returns: \"/sso/saml2/callback/provider1\"\n */\nexport function normalizePathname(\n\trequestUrl: string,\n\tbasePath: string,\n): string {\n\tlet pathname: string;\n\ttry {\n\t\tpathname = new URL(requestUrl).pathname.replace(/\\/+$/, \"\") || \"/\";\n\t} catch {\n\t\treturn \"/\";\n\t}\n\n\tif (basePath === \"/\" || basePath === \"\") {\n\t\treturn pathname;\n\t}\n\n\t// Check for exact match or proper path boundary (basePath followed by \"/\" or end)\n\t// This prevents \"/api/auth\" from matching \"/api/authevil/...\"\n\tif (pathname === basePath) {\n\t\treturn \"/\";\n\t}\n\n\tif (pathname.startsWith(basePath + \"/\")) {\n\t\treturn pathname.slice(basePath.length).replace(/\\/+$/, \"\") || \"/\";\n\t}\n\n\treturn pathname;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,SAAgB,kBACf,YACA,UACS;CACT,IAAIA;AACJ,KAAI;AACH,aAAW,IAAI,IAAI,WAAW,CAAC,SAAS,QAAQ,QAAQ,GAAG,IAAI;SACxD;AACP,SAAO;;AAGR,KAAI,aAAa,OAAO,aAAa,GACpC,QAAO;AAKR,KAAI,aAAa,SAChB,QAAO;AAGR,KAAI,SAAS,WAAW,WAAW,IAAI,CACtC,QAAO,SAAS,MAAM,SAAS,OAAO,CAAC,QAAQ,QAAQ,GAAG,IAAI;AAG/D,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/core",
3
- "version": "1.4.17",
3
+ "version": "1.4.19",
4
4
  "description": "The most comprehensive authentication framework for TypeScript.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1083,6 +1083,7 @@ export const createAdapterFactory =
1083
1083
  model: unsafeModel,
1084
1084
  where: unsafeWhere,
1085
1085
  limit: unsafeLimit,
1086
+ select,
1086
1087
  sortBy,
1087
1088
  offset,
1088
1089
  join: unsafeJoin,
@@ -1090,6 +1091,7 @@ export const createAdapterFactory =
1090
1091
  model: string;
1091
1092
  where?: Where[];
1092
1093
  limit?: number;
1094
+ select?: string[] | undefined;
1093
1095
  sortBy?: { field: string; direction: "asc" | "desc" };
1094
1096
  offset?: number;
1095
1097
  join?: JoinOption;
@@ -1110,13 +1112,10 @@ export const createAdapterFactory =
1110
1112
  let join: JoinConfig | undefined;
1111
1113
  let passJoinToAdapter = true;
1112
1114
  if (!config.disableTransformJoin) {
1113
- const result = transformJoinClause(
1114
- unsafeModel,
1115
- unsafeJoin,
1116
- undefined,
1117
- );
1115
+ const result = transformJoinClause(unsafeModel, unsafeJoin, select);
1118
1116
  if (result) {
1119
1117
  join = result.join;
1118
+ select = result.select;
1120
1119
  }
1121
1120
  // If adapter doesn't support joins and we have joins, don't pass them to the adapter
1122
1121
  const experimentalJoins = options.experimental?.joins;
@@ -1137,6 +1136,7 @@ export const createAdapterFactory =
1137
1136
  model,
1138
1137
  where,
1139
1138
  limit: limit,
1139
+ select,
1140
1140
  sortBy,
1141
1141
  offset,
1142
1142
  join: passJoinToAdapter ? join : undefined,
@@ -1284,42 +1284,6 @@ export const createAdapterFactory =
1284
1284
  delete tables.session;
1285
1285
  }
1286
1286
 
1287
- if (
1288
- options.rateLimit &&
1289
- options.rateLimit.storage === "database" &&
1290
- // rate-limit will default to enabled in production,
1291
- // and given storage is database, it will try to use the rate-limit table,
1292
- // so we should make sure to generate rate-limit table schema
1293
- (typeof options.rateLimit.enabled === "undefined" ||
1294
- // and of course if they forcefully set to true, then they want rate-limit,
1295
- // thus we should also generate rate-limit table schema
1296
- options.rateLimit.enabled === true)
1297
- ) {
1298
- tables.ratelimit = {
1299
- modelName: options.rateLimit.modelName ?? "ratelimit",
1300
- fields: {
1301
- key: {
1302
- type: "string",
1303
- unique: true,
1304
- required: true,
1305
- fieldName: options.rateLimit.fields?.key ?? "key",
1306
- },
1307
- count: {
1308
- type: "number",
1309
- required: true,
1310
- fieldName: options.rateLimit.fields?.count ?? "count",
1311
- },
1312
- lastRequest: {
1313
- type: "number",
1314
- required: true,
1315
- bigint: true,
1316
- defaultValue: () => Date.now(),
1317
- fieldName:
1318
- options.rateLimit.fields?.lastRequest ?? "lastRequest",
1319
- },
1320
- },
1321
- };
1322
- }
1323
1287
  return adapterInstance.createSchema!({ file, tables });
1324
1288
  }
1325
1289
  : undefined,
@@ -405,6 +405,7 @@ export type DBAdapter<Options extends BetterAuthOptions = BetterAuthOptions> = {
405
405
  model: string;
406
406
  where?: Where[] | undefined;
407
407
  limit?: number | undefined;
408
+ select?: string[] | undefined;
408
409
  sortBy?:
409
410
  | {
410
411
  field: string;
@@ -493,6 +494,7 @@ export interface CustomAdapter {
493
494
  model,
494
495
  where,
495
496
  limit,
497
+ select,
496
498
  sortBy,
497
499
  offset,
498
500
  join,
@@ -500,6 +502,7 @@ export interface CustomAdapter {
500
502
  model: string;
501
503
  where?: CleanedWhere[] | undefined;
502
504
  limit: number;
505
+ select?: string[] | undefined;
503
506
  sortBy?: { field: string; direction: "asc" | "desc" } | undefined;
504
507
  offset?: number | undefined;
505
508
  join?: JoinConfig | undefined;
@@ -32,16 +32,21 @@ export const getAuthTables = (
32
32
  fields: {
33
33
  key: {
34
34
  type: "string",
35
+ unique: true,
36
+ required: true,
35
37
  fieldName: options.rateLimit?.fields?.key || "key",
36
38
  },
37
39
  count: {
38
40
  type: "number",
41
+ required: true,
39
42
  fieldName: options.rateLimit?.fields?.count || "count",
40
43
  },
41
44
  lastRequest: {
42
45
  type: "number",
43
46
  bigint: true,
47
+ required: true,
44
48
  fieldName: options.rateLimit?.fields?.lastRequest || "lastRequest",
49
+ defaultValue: () => Date.now(),
45
50
  },
46
51
  },
47
52
  },
@@ -115,10 +115,10 @@ export const ENV = Object.freeze({
115
115
  get PACKAGE_VERSION() {
116
116
  return getEnvVar("PACKAGE_VERSION", "0.0.0");
117
117
  },
118
- get BETTER_AUTH_TELEMETRY_ENDPOINT() {
118
+ get BETTER_AUTH_TELEMETRY_ENDPOINT(): string | undefined {
119
119
  return getEnvVar(
120
120
  "BETTER_AUTH_TELEMETRY_ENDPOINT",
121
- "https://telemetry.better-auth.com/v1/track",
121
+ import.meta.env.BETTER_AUTH_TELEMETRY_ENDPOINT,
122
122
  );
123
123
  },
124
124
  });
package/src/env/logger.ts CHANGED
@@ -94,7 +94,7 @@ export type InternalLogger = {
94
94
 
95
95
  export const createLogger = (options?: Logger | undefined): InternalLogger => {
96
96
  const enabled = options?.disabled !== true;
97
- const logLevel = options?.level ?? "error";
97
+ const logLevel = options?.level ?? "warn";
98
98
 
99
99
  const isDisableColorsSpecified = options?.disableColors !== undefined;
100
100
  const colorsEnabled = isDisableColorsSpecified
@@ -42,7 +42,7 @@ export interface OAuthProvider<
42
42
  redirectURI: string;
43
43
  codeVerifier?: string | undefined;
44
44
  deviceId?: string | undefined;
45
- }) => Promise<OAuth2Tokens>;
45
+ }) => Promise<OAuth2Tokens | null>;
46
46
  getUserInfo: (
47
47
  token: OAuth2Tokens & {
48
48
  /**
@@ -1,6 +1,6 @@
1
1
  import { base64 } from "@better-auth/utils/base64";
2
2
  import { betterFetch } from "@better-fetch/fetch";
3
- import { jwtVerify } from "jose";
3
+ import { createRemoteJWKSet, jwtVerify } from "jose";
4
4
  import type { ProviderOptions } from "./index";
5
5
  import { getOAuth2Tokens } from "./index";
6
6
 
@@ -124,31 +124,18 @@ export async function validateAuthorizationCode({
124
124
  return tokens;
125
125
  }
126
126
 
127
- export async function validateToken(token: string, jwksEndpoint: string) {
128
- const { data, error } = await betterFetch<{
129
- keys: {
130
- kid: string;
131
- kty: string;
132
- use: string;
133
- n: string;
134
- e: string;
135
- x5c: string[];
136
- }[];
137
- }>(jwksEndpoint, {
138
- method: "GET",
139
- headers: {
140
- accept: "application/json",
141
- },
127
+ export async function validateToken(
128
+ token: string,
129
+ jwksEndpoint: string,
130
+ options?: {
131
+ audience?: string | string[];
132
+ issuer?: string | string[];
133
+ },
134
+ ) {
135
+ const jwks = createRemoteJWKSet(new URL(jwksEndpoint));
136
+ const verified = await jwtVerify(token, jwks, {
137
+ audience: options?.audience,
138
+ issuer: options?.issuer,
142
139
  });
143
- if (error) {
144
- throw error;
145
- }
146
- const keys = data["keys"];
147
- const header = JSON.parse(atob(token.split(".")[0]!));
148
- const key = keys.find((key) => key.kid === header.kid);
149
- if (!key) {
150
- throw new Error("Key not found");
151
- }
152
- const verified = await jwtVerify(token, key);
153
140
  return verified;
154
141
  }
@@ -0,0 +1,241 @@
1
+ import type { JWK } from "jose";
2
+ import { exportJWK, generateKeyPair, SignJWT } from "jose";
3
+ import {
4
+ afterAll,
5
+ beforeAll,
6
+ beforeEach,
7
+ describe,
8
+ expect,
9
+ it,
10
+ vi,
11
+ } from "vitest";
12
+ import { validateToken } from "./validate-authorization-code";
13
+
14
+ describe("validateToken", () => {
15
+ const originalFetch = globalThis.fetch;
16
+ const mockedFetch = vi.fn() as unknown as typeof fetch &
17
+ ReturnType<typeof vi.fn>;
18
+
19
+ beforeAll(() => {
20
+ globalThis.fetch = mockedFetch;
21
+ });
22
+
23
+ afterAll(() => {
24
+ globalThis.fetch = originalFetch;
25
+ });
26
+
27
+ beforeEach(() => {
28
+ mockedFetch.mockReset();
29
+ });
30
+
31
+ async function createTestJWKS(alg: string, crv?: string) {
32
+ const { publicKey, privateKey } = await generateKeyPair(alg, {
33
+ crv,
34
+ extractable: true,
35
+ });
36
+ const publicJWK = await exportJWK(publicKey);
37
+ const privateJWK = await exportJWK(privateKey);
38
+ const kid = `test-key-${Date.now()}`;
39
+ publicJWK.kid = kid;
40
+ publicJWK.alg = alg;
41
+ privateJWK.kid = kid;
42
+ privateJWK.alg = alg;
43
+ return { publicJWK, privateJWK, kid, publicKey, privateKey };
44
+ }
45
+
46
+ async function createSignedToken(
47
+ privateKey: CryptoKey,
48
+ alg: string,
49
+ kid: string,
50
+ payload: Record<string, unknown> = {},
51
+ ) {
52
+ return await new SignJWT({
53
+ sub: "user-123",
54
+ email: "test@example.com",
55
+ iss: "https://example.com",
56
+ aud: "test-client",
57
+ ...payload,
58
+ })
59
+ .setProtectedHeader({ alg, kid })
60
+ .setIssuedAt()
61
+ .setExpirationTime("1h")
62
+ .sign(privateKey);
63
+ }
64
+
65
+ function mockJWKSResponse(...publicJWKs: JWK[]) {
66
+ mockedFetch.mockResolvedValueOnce(
67
+ new Response(JSON.stringify({ keys: publicJWKs }), {
68
+ status: 200,
69
+ headers: { "content-type": "application/json" },
70
+ }),
71
+ );
72
+ }
73
+
74
+ it("should verify RS256 signed token", async () => {
75
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
76
+ const token = await createSignedToken(privateKey, "RS256", kid);
77
+ mockJWKSResponse(publicJWK);
78
+
79
+ const result = await validateToken(
80
+ token,
81
+ "https://example.com/.well-known/jwks",
82
+ );
83
+
84
+ expect(result).toBeDefined();
85
+ expect(result.payload.sub).toBe("user-123");
86
+ expect(result.payload.email).toBe("test@example.com");
87
+ });
88
+
89
+ it("should verify ES256 signed token", async () => {
90
+ const { publicJWK, privateKey, kid } = await createTestJWKS("ES256");
91
+ const token = await createSignedToken(privateKey, "ES256", kid);
92
+ mockJWKSResponse(publicJWK);
93
+
94
+ const result = await validateToken(
95
+ token,
96
+ "https://example.com/.well-known/jwks",
97
+ );
98
+
99
+ expect(result).toBeDefined();
100
+ expect(result.payload.sub).toBe("user-123");
101
+ });
102
+
103
+ it("should verify EdDSA (Ed25519) signed token", async () => {
104
+ const { publicJWK, privateKey, kid } = await createTestJWKS(
105
+ "EdDSA",
106
+ "Ed25519",
107
+ );
108
+ const token = await createSignedToken(privateKey, "EdDSA", kid);
109
+ mockJWKSResponse(publicJWK);
110
+
111
+ const result = await validateToken(
112
+ token,
113
+ "https://example.com/.well-known/jwks",
114
+ );
115
+
116
+ expect(result).toBeDefined();
117
+ expect(result.payload.sub).toBe("user-123");
118
+ });
119
+
120
+ it("should throw when kid doesn't match any key", async () => {
121
+ const { publicJWK, privateKey } = await createTestJWKS("RS256");
122
+ publicJWK.kid = "different-kid";
123
+ const token = await createSignedToken(privateKey, "RS256", "original-kid");
124
+ mockJWKSResponse(publicJWK);
125
+
126
+ await expect(
127
+ validateToken(token, "https://example.com/.well-known/jwks"),
128
+ ).rejects.toThrow();
129
+ });
130
+
131
+ it("should find correct key when multiple keys exist", async () => {
132
+ const key1 = await createTestJWKS("RS256");
133
+ const key2 = await createTestJWKS("RS256");
134
+ const key3 = await createTestJWKS("ES256");
135
+ const token = await createSignedToken(key2.privateKey, "RS256", key2.kid);
136
+ mockJWKSResponse(key1.publicJWK, key2.publicJWK, key3.publicJWK);
137
+
138
+ const result = await validateToken(
139
+ token,
140
+ "https://example.com/.well-known/jwks",
141
+ );
142
+
143
+ expect(result).toBeDefined();
144
+ expect(result.payload.sub).toBe("user-123");
145
+ });
146
+
147
+ it("should throw when JWKS returns empty keys array", async () => {
148
+ const { privateKey, kid } = await createTestJWKS("RS256");
149
+ const token = await createSignedToken(privateKey, "RS256", kid);
150
+ mockJWKSResponse();
151
+
152
+ await expect(
153
+ validateToken(token, "https://example.com/.well-known/jwks"),
154
+ ).rejects.toThrow();
155
+ });
156
+
157
+ it("should throw when JWKS fetch fails", async () => {
158
+ const { privateKey, kid } = await createTestJWKS("RS256");
159
+ const token = await createSignedToken(privateKey, "RS256", kid);
160
+ mockedFetch.mockResolvedValueOnce(
161
+ new Response("Internal Server Error", { status: 500 }),
162
+ );
163
+
164
+ await expect(
165
+ validateToken(token, "https://example.com/.well-known/jwks"),
166
+ ).rejects.toBeDefined();
167
+ });
168
+
169
+ it("should verify token with matching audience", async () => {
170
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
171
+ const token = await createSignedToken(privateKey, "RS256", kid);
172
+ mockJWKSResponse(publicJWK);
173
+
174
+ const result = await validateToken(
175
+ token,
176
+ "https://example.com/.well-known/jwks",
177
+ { audience: "test-client" },
178
+ );
179
+
180
+ expect(result).toBeDefined();
181
+ expect(result.payload.aud).toBe("test-client");
182
+ });
183
+
184
+ it("should reject token with mismatched audience", async () => {
185
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
186
+ const token = await createSignedToken(privateKey, "RS256", kid);
187
+ mockJWKSResponse(publicJWK);
188
+
189
+ await expect(
190
+ validateToken(token, "https://example.com/.well-known/jwks", {
191
+ audience: "wrong-client",
192
+ }),
193
+ ).rejects.toThrow();
194
+ });
195
+
196
+ it("should verify token with matching issuer", async () => {
197
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
198
+ const token = await createSignedToken(privateKey, "RS256", kid);
199
+ mockJWKSResponse(publicJWK);
200
+
201
+ const result = await validateToken(
202
+ token,
203
+ "https://example.com/.well-known/jwks",
204
+ { issuer: "https://example.com" },
205
+ );
206
+
207
+ expect(result).toBeDefined();
208
+ expect(result.payload.iss).toBe("https://example.com");
209
+ });
210
+
211
+ it("should reject token with mismatched issuer", async () => {
212
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
213
+ const token = await createSignedToken(privateKey, "RS256", kid);
214
+ mockJWKSResponse(publicJWK);
215
+
216
+ await expect(
217
+ validateToken(token, "https://example.com/.well-known/jwks", {
218
+ issuer: "https://wrong-issuer.com",
219
+ }),
220
+ ).rejects.toThrow();
221
+ });
222
+
223
+ it("should verify token with both audience and issuer", async () => {
224
+ const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
225
+ const token = await createSignedToken(privateKey, "RS256", kid);
226
+ mockJWKSResponse(publicJWK);
227
+
228
+ const result = await validateToken(
229
+ token,
230
+ "https://example.com/.well-known/jwks",
231
+ {
232
+ audience: "test-client",
233
+ issuer: "https://example.com",
234
+ },
235
+ );
236
+
237
+ expect(result).toBeDefined();
238
+ expect(result.payload.aud).toBe("test-client");
239
+ expect(result.payload.iss).toBe("https://example.com");
240
+ });
241
+ });