better-auth 1.7.0-beta.2 → 1.7.0-beta.4

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 (282) hide show
  1. package/README.md +14 -15
  2. package/dist/_virtual/_rolldown/runtime.mjs +10 -1
  3. package/dist/api/index.d.mts +18 -48
  4. package/dist/api/routes/account.d.mts +4 -23
  5. package/dist/api/routes/account.mjs +102 -75
  6. package/dist/api/routes/callback.mjs +42 -20
  7. package/dist/api/routes/email-verification.d.mts +1 -0
  8. package/dist/api/routes/email-verification.mjs +6 -5
  9. package/dist/api/routes/error.mjs +1 -1
  10. package/dist/api/routes/password.mjs +1 -1
  11. package/dist/api/routes/session.mjs +15 -10
  12. package/dist/api/routes/sign-in.d.mts +3 -1
  13. package/dist/api/routes/sign-in.mjs +13 -15
  14. package/dist/api/routes/sign-up.d.mts +1 -0
  15. package/dist/api/routes/sign-up.mjs +10 -8
  16. package/dist/api/routes/update-user.mjs +8 -8
  17. package/dist/api/to-auth-endpoints.mjs +7 -1
  18. package/dist/auth/base.mjs +30 -27
  19. package/dist/client/config.mjs +8 -1
  20. package/dist/client/fetch-plugins.mjs +2 -1
  21. package/dist/client/index.d.mts +2 -2
  22. package/dist/client/lynx/index.d.mts +13 -0
  23. package/dist/client/lynx/index.mjs +2 -1
  24. package/dist/client/parser.mjs +0 -1
  25. package/dist/client/plugins/index.d.mts +4 -17
  26. package/dist/client/plugins/index.mjs +1 -4
  27. package/dist/client/proxy.mjs +2 -1
  28. package/dist/client/react/index.d.mts +13 -0
  29. package/dist/client/react/index.mjs +2 -1
  30. package/dist/client/session-atom.mjs +12 -1
  31. package/dist/client/solid/index.d.mts +13 -0
  32. package/dist/client/solid/index.mjs +3 -2
  33. package/dist/client/svelte/index.d.mts +13 -0
  34. package/dist/client/svelte/index.mjs +2 -1
  35. package/dist/client/vanilla.d.mts +13 -0
  36. package/dist/client/vanilla.mjs +2 -1
  37. package/dist/client/vue/index.d.mts +13 -0
  38. package/dist/client/vue/index.mjs +2 -1
  39. package/dist/context/create-context.mjs +10 -14
  40. package/dist/context/helpers.mjs +3 -2
  41. package/dist/cookies/cookie-utils.d.mts +33 -1
  42. package/dist/cookies/cookie-utils.mjs +95 -14
  43. package/dist/cookies/index.d.mts +2 -3
  44. package/dist/cookies/index.mjs +39 -11
  45. package/dist/cookies/session-store.mjs +4 -23
  46. package/dist/db/get-migration.mjs +4 -4
  47. package/dist/db/index.d.mts +2 -2
  48. package/dist/db/index.mjs +3 -2
  49. package/dist/db/internal-adapter.mjs +129 -27
  50. package/dist/db/schema.d.mts +14 -1
  51. package/dist/db/schema.mjs +26 -1
  52. package/dist/db/with-hooks.d.mts +1 -0
  53. package/dist/db/with-hooks.mjs +58 -1
  54. package/dist/index.d.mts +2 -2
  55. package/dist/index.mjs +2 -2
  56. package/dist/integrations/cookie-plugin-guard.mjs +18 -0
  57. package/dist/integrations/next-js.mjs +6 -0
  58. package/dist/integrations/svelte-kit.mjs +6 -0
  59. package/dist/integrations/tanstack-start-solid.mjs +6 -0
  60. package/dist/integrations/tanstack-start.mjs +6 -0
  61. package/dist/node_modules/.pnpm/clipboardy@4.0.0/node_modules/clipboardy/index.mjs +32 -0
  62. package/dist/node_modules/.pnpm/clipboardy@4.0.0/node_modules/clipboardy/lib/linux.mjs +55 -0
  63. package/dist/node_modules/.pnpm/clipboardy@4.0.0/node_modules/clipboardy/lib/macos.mjs +26 -0
  64. package/dist/node_modules/.pnpm/clipboardy@4.0.0/node_modules/clipboardy/lib/termux.mjs +39 -0
  65. package/dist/node_modules/.pnpm/clipboardy@4.0.0/node_modules/clipboardy/lib/windows.mjs +19 -0
  66. package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/chunks/prompt.mjs +845 -0
  67. package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/core.mjs +386 -0
  68. package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/index.mjs +320 -0
  69. package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/shared/consola.DRwqZj3T.mjs +62 -0
  70. package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/shared/consola.DXBYu-KD.mjs +190 -0
  71. package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/utils.mjs +2 -0
  72. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.mjs +29 -0
  73. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.mjs +42 -0
  74. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.mjs +67 -0
  75. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.mjs +23 -0
  76. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.mjs +22 -0
  77. package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.mjs +36 -0
  78. package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/adapters/node.d.mts +292 -0
  79. package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/adapters/node.mjs +127 -0
  80. package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/index.d.mts +145 -0
  81. package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/shared/crossws.BQXMA5bH.d.mts +298 -0
  82. package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/shared/crossws.By9qWDAI.mjs +9 -0
  83. package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/shared/crossws.CipVM6lf.mjs +3549 -0
  84. package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/shared/crossws.D9ehKjSh.mjs +66 -0
  85. package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/shared/crossws.DfCzGthR.mjs +227 -0
  86. package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/index.mjs +224 -0
  87. package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/command.mjs +52 -0
  88. package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/error.mjs +54 -0
  89. package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/kill.mjs +62 -0
  90. package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/pipe.mjs +26 -0
  91. package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/promise.mjs +32 -0
  92. package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/stdio.mjs +19 -0
  93. package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/stream.mjs +98 -0
  94. package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/verbose.mjs +15 -0
  95. package/dist/node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.d.mts +15 -0
  96. package/dist/node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.mjs +220 -0
  97. package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/array-buffer.mjs +54 -0
  98. package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/array.mjs +2 -0
  99. package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/buffer.mjs +14 -0
  100. package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/contents.mjs +76 -0
  101. package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/index.mjs +6 -0
  102. package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/string.mjs +35 -0
  103. package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/utils.mjs +10 -0
  104. package/dist/node_modules/.pnpm/http-shutdown@1.2.2/node_modules/http-shutdown/index.mjs +76 -0
  105. package/dist/node_modules/.pnpm/human-signals@5.0.0/node_modules/human-signals/build/src/core.mjs +274 -0
  106. package/dist/node_modules/.pnpm/human-signals@5.0.0/node_modules/human-signals/build/src/main.mjs +44 -0
  107. package/dist/node_modules/.pnpm/human-signals@5.0.0/node_modules/human-signals/build/src/realtime.mjs +15 -0
  108. package/dist/node_modules/.pnpm/human-signals@5.0.0/node_modules/human-signals/build/src/signals.mjs +23 -0
  109. package/dist/node_modules/.pnpm/is-docker@3.0.0/node_modules/is-docker/index.mjs +24 -0
  110. package/dist/node_modules/.pnpm/is-inside-container@1.0.0/node_modules/is-inside-container/index.mjs +18 -0
  111. package/dist/node_modules/.pnpm/is-stream@3.0.0/node_modules/is-stream/index.mjs +9 -0
  112. package/dist/node_modules/.pnpm/is-wsl@3.1.1/node_modules/is-wsl/index.mjs +20 -0
  113. package/dist/node_modules/.pnpm/is64bit@2.0.0/node_modules/is64bit/index.mjs +13 -0
  114. package/dist/node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.mjs +47 -0
  115. package/dist/node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.mjs +33 -0
  116. package/dist/node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.mjs +33 -0
  117. package/dist/node_modules/.pnpm/listhen@1.9.0/node_modules/listhen/dist/chunks/xdg-open.mjs +1070 -0
  118. package/dist/node_modules/.pnpm/listhen@1.9.0/node_modules/listhen/dist/index.mjs +619 -0
  119. package/dist/node_modules/.pnpm/listhen@1.9.0/node_modules/listhen/dist/shared/listhen.1c46e31d.d.mts +83 -0
  120. package/dist/node_modules/.pnpm/merge-stream@2.0.0/node_modules/merge-stream/index.mjs +38 -0
  121. package/dist/node_modules/.pnpm/mimic-fn@4.0.0/node_modules/mimic-fn/index.mjs +38 -0
  122. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/aes.mjs +597 -0
  123. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/aesCipherSuites.mjs +195 -0
  124. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/asn1-validator.mjs +76 -0
  125. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/asn1.mjs +967 -0
  126. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/baseN.mjs +137 -0
  127. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/cipher.mjs +186 -0
  128. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/cipherModes.mjs +597 -0
  129. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/des.mjs +1187 -0
  130. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/ed25519.mjs +1029 -0
  131. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/forge.mjs +15 -0
  132. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/hmac.mjs +107 -0
  133. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/index.mjs +66 -0
  134. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/jsbn.mjs +1334 -0
  135. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/kem.mjs +146 -0
  136. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/log.mjs +241 -0
  137. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/md.all.mjs +24 -0
  138. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/md.mjs +18 -0
  139. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/md5.mjs +324 -0
  140. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/mgf.mjs +20 -0
  141. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/mgf1.mjs +44 -0
  142. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/oids.mjs +154 -0
  143. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pbe.mjs +815 -0
  144. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pbkdf2.mjs +125 -0
  145. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pem.mjs +175 -0
  146. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pkcs1.mjs +200 -0
  147. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pkcs12.mjs +724 -0
  148. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pkcs7.mjs +642 -0
  149. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pkcs7asn1.mjs +405 -0
  150. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pki.mjs +101 -0
  151. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/prime.mjs +193 -0
  152. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/prng.mjs +290 -0
  153. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pss.mjs +141 -0
  154. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/random.mjs +141 -0
  155. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/rc2.mjs +538 -0
  156. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/rsa.mjs +1309 -0
  157. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/sha1.mjs +230 -0
  158. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/sha256.mjs +267 -0
  159. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/sha512.mjs +413 -0
  160. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/ssh.mjs +194 -0
  161. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/tls.mjs +3655 -0
  162. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/util.mjs +2117 -0
  163. package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/x509.mjs +2168 -0
  164. package/dist/node_modules/.pnpm/npm-run-path@5.3.0/node_modules/npm-run-path/index.mjs +34 -0
  165. package/dist/node_modules/.pnpm/onetime@6.0.0/node_modules/onetime/index.mjs +26 -0
  166. package/dist/node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.mjs +14 -0
  167. package/dist/node_modules/.pnpm/path-key@4.0.0/node_modules/path-key/index.mjs +8 -0
  168. package/dist/node_modules/.pnpm/pathe@1.1.2/node_modules/pathe/dist/shared/pathe.ff20891b.mjs +176 -0
  169. package/dist/node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.mjs +17 -0
  170. package/dist/node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.mjs +8 -0
  171. package/dist/node_modules/.pnpm/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/index.mjs +169 -0
  172. package/dist/node_modules/.pnpm/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/signals.mjs +33 -0
  173. package/dist/node_modules/.pnpm/std-env@3.10.0/node_modules/std-env/dist/index.mjs +171 -0
  174. package/dist/node_modules/.pnpm/strip-final-newline@3.0.0/node_modules/strip-final-newline/index.mjs +10 -0
  175. package/dist/node_modules/.pnpm/system-architecture@0.1.0/node_modules/system-architecture/index.mjs +16 -0
  176. package/dist/node_modules/.pnpm/uncrypto@0.1.3/node_modules/uncrypto/dist/crypto.node.mjs +7 -0
  177. package/dist/node_modules/.pnpm/untun@0.1.3/node_modules/untun/dist/chunks/index.mjs +154 -0
  178. package/dist/node_modules/.pnpm/untun@0.1.3/node_modules/untun/dist/index.mjs +34 -0
  179. package/dist/node_modules/.pnpm/uqr@0.1.2/node_modules/uqr/dist/index.mjs +896 -0
  180. package/dist/node_modules/.pnpm/which@2.0.2/node_modules/which/which.mjs +76 -0
  181. package/dist/oauth2/errors.mjs +27 -0
  182. package/dist/oauth2/index.d.mts +2 -2
  183. package/dist/oauth2/index.mjs +3 -3
  184. package/dist/oauth2/link-account.d.mts +27 -1
  185. package/dist/oauth2/link-account.mjs +30 -5
  186. package/dist/oauth2/state.mjs +8 -2
  187. package/dist/{package.mjs → packages/better-auth/package.mjs} +1 -1
  188. package/dist/plugins/access/access.d.mts +3 -15
  189. package/dist/plugins/access/access.mjs +11 -6
  190. package/dist/plugins/access/index.d.mts +2 -2
  191. package/dist/plugins/access/types.d.mts +11 -4
  192. package/dist/plugins/admin/access/statement.d.mts +29 -93
  193. package/dist/plugins/admin/admin.mjs +0 -4
  194. package/dist/plugins/admin/client.d.mts +6 -1
  195. package/dist/plugins/admin/client.mjs +5 -0
  196. package/dist/plugins/admin/routes.mjs +3 -2
  197. package/dist/plugins/anonymous/client.d.mts +1 -0
  198. package/dist/plugins/anonymous/error-codes.d.mts +1 -0
  199. package/dist/plugins/anonymous/error-codes.mjs +1 -0
  200. package/dist/plugins/anonymous/index.d.mts +1 -0
  201. package/dist/plugins/anonymous/index.mjs +16 -2
  202. package/dist/plugins/bearer/index.mjs +5 -12
  203. package/dist/plugins/captcha/index.mjs +14 -1
  204. package/dist/plugins/device-authorization/error-codes.mjs +1 -0
  205. package/dist/plugins/device-authorization/index.d.mts +1 -0
  206. package/dist/plugins/device-authorization/routes.mjs +34 -3
  207. package/dist/plugins/email-otp/routes.mjs +4 -4
  208. package/dist/plugins/generic-oauth/error-codes.mjs +0 -1
  209. package/dist/plugins/generic-oauth/index.d.mts +2 -4
  210. package/dist/plugins/generic-oauth/index.mjs +23 -17
  211. package/dist/plugins/generic-oauth/providers/auth0.mjs +1 -0
  212. package/dist/plugins/generic-oauth/providers/gumroad.mjs +1 -0
  213. package/dist/plugins/generic-oauth/providers/hubspot.mjs +1 -0
  214. package/dist/plugins/generic-oauth/providers/keycloak.mjs +1 -0
  215. package/dist/plugins/generic-oauth/providers/line.mjs +1 -0
  216. package/dist/plugins/generic-oauth/providers/microsoft-entra-id.mjs +1 -0
  217. package/dist/plugins/generic-oauth/providers/okta.mjs +1 -0
  218. package/dist/plugins/generic-oauth/providers/patreon.mjs +1 -0
  219. package/dist/plugins/generic-oauth/providers/slack.mjs +1 -0
  220. package/dist/plugins/generic-oauth/types.d.mts +30 -8
  221. package/dist/plugins/index.d.mts +2 -2
  222. package/dist/plugins/jwt/utils.d.mts +1 -1
  223. package/dist/plugins/last-login-method/client.mjs +2 -2
  224. package/dist/plugins/magic-link/index.d.mts +8 -1
  225. package/dist/plugins/magic-link/index.mjs +4 -17
  226. package/dist/plugins/mcp/authorize.mjs +8 -2
  227. package/dist/plugins/mcp/index.mjs +75 -35
  228. package/dist/plugins/multi-session/index.mjs +2 -2
  229. package/dist/plugins/oauth-proxy/index.mjs +45 -32
  230. package/dist/plugins/oauth-proxy/utils.mjs +3 -10
  231. package/dist/plugins/oidc-provider/authorize.mjs +8 -2
  232. package/dist/plugins/oidc-provider/index.mjs +65 -38
  233. package/dist/plugins/one-tap/client.mjs +9 -2
  234. package/dist/plugins/one-tap/index.mjs +19 -36
  235. package/dist/plugins/open-api/generator.mjs +25 -5
  236. package/dist/plugins/organization/access/statement.d.mts +68 -201
  237. package/dist/plugins/organization/adapter.d.mts +1 -1
  238. package/dist/plugins/organization/adapter.mjs +65 -58
  239. package/dist/plugins/organization/client.d.mts +3 -1
  240. package/dist/plugins/organization/client.mjs +1 -1
  241. package/dist/plugins/organization/error-codes.d.mts +2 -0
  242. package/dist/plugins/organization/error-codes.mjs +3 -1
  243. package/dist/plugins/organization/routes/crud-access-control.d.mts +2 -2
  244. package/dist/plugins/organization/routes/crud-invites.d.mts +8 -1
  245. package/dist/plugins/organization/routes/crud-invites.mjs +8 -3
  246. package/dist/plugins/organization/routes/crud-org.d.mts +4 -4
  247. package/dist/plugins/organization/routes/crud-org.mjs +2 -2
  248. package/dist/plugins/organization/routes/crud-team.mjs +7 -2
  249. package/dist/plugins/organization/schema.d.mts +1 -1
  250. package/dist/plugins/organization/types.d.mts +15 -5
  251. package/dist/plugins/phone-number/routes.mjs +1 -1
  252. package/dist/plugins/siwe/client.d.mts +4 -0
  253. package/dist/plugins/siwe/client.mjs +5 -1
  254. package/dist/plugins/siwe/index.d.mts +13 -2
  255. package/dist/plugins/siwe/index.mjs +179 -165
  256. package/dist/plugins/two-factor/backup-codes/index.d.mts +4 -3
  257. package/dist/plugins/two-factor/client.mjs +2 -1
  258. package/dist/plugins/two-factor/index.mjs +3 -2
  259. package/dist/plugins/username/client.d.mts +1 -0
  260. package/dist/plugins/username/error-codes.d.mts +1 -0
  261. package/dist/plugins/username/error-codes.mjs +2 -1
  262. package/dist/plugins/username/index.d.mts +43 -2
  263. package/dist/plugins/username/index.mjs +73 -6
  264. package/dist/state.d.mts +2 -2
  265. package/dist/state.mjs +18 -4
  266. package/dist/test-utils/headers.mjs +2 -7
  267. package/dist/test-utils/http-test-instance.d.mts +397 -0
  268. package/dist/test-utils/http-test-instance.mjs +54 -0
  269. package/dist/test-utils/index.d.mts +2 -1
  270. package/dist/test-utils/index.mjs +2 -1
  271. package/dist/test-utils/test-instance.d.mts +4110 -224
  272. package/dist/test-utils/test-instance.mjs +11 -2
  273. package/dist/types/auth.d.mts +4 -0
  274. package/dist/utils/index.d.mts +1 -1
  275. package/dist/utils/url.d.mts +2 -1
  276. package/dist/utils/url.mjs +9 -3
  277. package/dist/version.mjs +1 -1
  278. package/package.json +14 -14
  279. package/dist/oauth2/error-codes.d.mts +0 -20
  280. package/dist/plugins/generic-oauth/client.d.mts +0 -32
  281. package/dist/plugins/generic-oauth/client.mjs +0 -19
  282. package/dist/plugins/generic-oauth/error-codes.d.mts +0 -10
@@ -101,6 +101,12 @@ const anonymous = (options) => {
101
101
  const session = ctx.context.session;
102
102
  if (options?.disableDeleteAnonymousUser) throw APIError.from("BAD_REQUEST", ANONYMOUS_ERROR_CODES.DELETE_ANONYMOUS_USER_DISABLED);
103
103
  if (!session.user.isAnonymous) throw APIError.from("FORBIDDEN", ANONYMOUS_ERROR_CODES.USER_IS_NOT_ANONYMOUS);
104
+ try {
105
+ await ctx.context.internalAdapter.deleteUserSessions(session.user.id);
106
+ } catch (error) {
107
+ ctx.context.logger.error("Failed to delete anonymous user sessions", error);
108
+ throw APIError.from("INTERNAL_SERVER_ERROR", ANONYMOUS_ERROR_CODES.FAILED_TO_DELETE_ANONYMOUS_USER_SESSIONS);
109
+ }
104
110
  try {
105
111
  await ctx.context.internalAdapter.deleteUser(session.user.id);
106
112
  } catch (error) {
@@ -113,7 +119,7 @@ const anonymous = (options) => {
113
119
  },
114
120
  hooks: { after: [{
115
121
  matcher(ctx) {
116
- return ctx.path?.startsWith("/sign-in") || ctx.path?.startsWith("/sign-up") || ctx.path?.startsWith("/callback") || ctx.path?.startsWith("/magic-link/verify") || ctx.path?.startsWith("/email-otp/verify-email") || ctx.path?.startsWith("/one-tap/callback") || ctx.path?.startsWith("/passkey/verify-authentication") || ctx.path?.startsWith("/phone-number/verify") || false;
122
+ return ctx.path?.startsWith("/sign-in") || ctx.path?.startsWith("/sign-up") || ctx.path?.startsWith("/callback") || ctx.path?.startsWith("/magic-link/verify") || ctx.path?.startsWith("/email-otp/verify-email") || ctx.path?.startsWith("/one-tap/callback") || ctx.path?.startsWith("/passkey/verify-authentication") || ctx.path?.startsWith("/phone-number/verify") || ctx.path?.startsWith("/verify-email") || false;
117
123
  },
118
124
  handler: createAuthMiddleware(async (ctx) => {
119
125
  const setCookie = ctx.context.responseHeaders?.get("set-cookie");
@@ -147,7 +153,15 @@ const anonymous = (options) => {
147
153
  const isSameUser = newSessionUser?.id === session.user.id;
148
154
  const newSessionIsAnonymous = Boolean(newSessionUser?.isAnonymous);
149
155
  if (options?.disableDeleteAnonymousUser || isSameUser || newSessionIsAnonymous) return;
150
- await ctx.context.internalAdapter.deleteUser(session.user.id);
156
+ try {
157
+ await ctx.context.internalAdapter.deleteUserSessions(session.user.id);
158
+ await ctx.context.internalAdapter.deleteUser(session.user.id);
159
+ } catch (error) {
160
+ ctx.context.logger.error("Failed to clean up anonymous user during post-link cleanup", {
161
+ anonymousUserId: session.user.id,
162
+ error
163
+ });
164
+ }
151
165
  })
152
166
  }] },
153
167
  options,
@@ -1,4 +1,4 @@
1
- import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
1
+ import { parseSetCookieHeader, setRequestCookie } from "../../cookies/cookie-utils.mjs";
2
2
  import { PACKAGE_VERSION } from "../../version.mjs";
3
3
  import { serializeSignedCookie } from "better-call";
4
4
  import { createAuthMiddleware } from "@better-auth/core/api";
@@ -30,16 +30,11 @@ const bearer = (options) => {
30
30
  if (authHeader.slice(0, 7).toLowerCase() !== BEARER_SCHEME) return;
31
31
  const token = authHeader.slice(7).trim();
32
32
  if (!token) return;
33
- let signedToken;
34
33
  let decodedToken;
35
- if (token.includes(".")) {
36
- const isEncoded = token.includes("%");
37
- signedToken = isEncoded ? token : encodeURIComponent(token);
38
- decodedToken = isEncoded ? tryDecode(token) : token;
39
- } else {
34
+ if (token.includes(".")) decodedToken = token.includes("%") ? tryDecode(token) : token;
35
+ else {
40
36
  if (options?.requireSignature) return;
41
- signedToken = (await serializeSignedCookie("", token, c.context.secret)).replace("=", "");
42
- decodedToken = tryDecode(signedToken);
37
+ decodedToken = tryDecode((await serializeSignedCookie("", token, c.context.secret)).replace("=", ""));
43
38
  }
44
39
  try {
45
40
  if (!await createHMAC("SHA-256", "base64urlnopad").verify(c.context.secret, decodedToken.split(".")[0], decodedToken.split(".")[1])) return;
@@ -48,9 +43,7 @@ const bearer = (options) => {
48
43
  }
49
44
  const existingHeaders = c.request?.headers || c.headers;
50
45
  const headers = new Headers({ ...Object.fromEntries(existingHeaders?.entries()) });
51
- const existingCookie = headers.get("cookie");
52
- const newCookie = `${c.context.authCookies.sessionToken.name}=${signedToken}`;
53
- headers.set("cookie", existingCookie ? `${existingCookie}; ${newCookie}` : newCookie);
46
+ setRequestCookie(headers, c.context.authCookies.sessionToken.name, decodedToken);
54
47
  return { context: { headers } };
55
48
  })
56
49
  }],
@@ -14,7 +14,20 @@ const captcha = (options) => ({
14
14
  $ERROR_CODES: EXTERNAL_ERROR_CODES,
15
15
  onRequest: async (request, ctx) => {
16
16
  try {
17
- if (!(options.endpoints?.length ? options.endpoints : defaultEndpoints).some((endpoint) => request.url.includes(endpoint))) return void 0;
17
+ const endpoints = options.endpoints?.length ? options.endpoints : defaultEndpoints;
18
+ const url = new URL(request.url);
19
+ const basePath = ctx.options.basePath ?? "/api/auth";
20
+ let pathname = url.pathname.replace(basePath, "");
21
+ if (pathname.endsWith("//")) pathname = pathname.slice(0, -1);
22
+ if (pathname.startsWith("//")) pathname = pathname.slice(1);
23
+ if (!pathname.startsWith("/")) pathname = "/" + pathname;
24
+ const exemptPaths = ["/sign-in/email-otp"].reduce((acc, curr) => {
25
+ if (options.endpoints?.length && options.endpoints.includes(curr)) return acc;
26
+ return [...acc, curr];
27
+ }, []);
28
+ if (!endpoints.some((endpoint) => {
29
+ return pathname.includes(endpoint) && !exemptPaths.some((p) => pathname.includes(p));
30
+ })) return;
18
31
  if (!options.secretKey) throw new Error(INTERNAL_ERROR_CODES.MISSING_SECRET_KEY.message);
19
32
  const captchaResponse = request.headers.get("x-captcha-response");
20
33
  const remoteUserIP = getIp(request, ctx.options) ?? void 0;
@@ -8,6 +8,7 @@ const DEVICE_AUTHORIZATION_ERROR_CODES = defineErrorCodes({
8
8
  ACCESS_DENIED: "Access denied",
9
9
  INVALID_USER_CODE: "Invalid user code",
10
10
  DEVICE_CODE_ALREADY_PROCESSED: "Device code already processed",
11
+ DEVICE_CODE_NOT_CLAIMED: "Device code has not been claimed by a verifying session; call `GET /device` with the `user_code` while signed in before approving or denying",
11
12
  POLLING_TOO_FREQUENTLY: "Polling too frequently",
12
13
  USER_NOT_FOUND: "User not found",
13
14
  FAILED_TO_CREATE_SESSION: "Failed to create session",
@@ -388,6 +388,7 @@ declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions
388
388
  ACCESS_DENIED: _better_auth_core_utils_error_codes0.RawError<"ACCESS_DENIED">;
389
389
  INVALID_USER_CODE: _better_auth_core_utils_error_codes0.RawError<"INVALID_USER_CODE">;
390
390
  DEVICE_CODE_ALREADY_PROCESSED: _better_auth_core_utils_error_codes0.RawError<"DEVICE_CODE_ALREADY_PROCESSED">;
391
+ DEVICE_CODE_NOT_CLAIMED: _better_auth_core_utils_error_codes0.RawError<"DEVICE_CODE_NOT_CLAIMED">;
391
392
  POLLING_TOO_FREQUENTLY: _better_auth_core_utils_error_codes0.RawError<"POLLING_TOO_FREQUENTLY">;
392
393
  INVALID_DEVICE_CODE_STATUS: _better_auth_core_utils_error_codes0.RawError<"INVALID_DEVICE_CODE_STATUS">;
393
394
  AUTHENTICATION_REQUIRED: _better_auth_core_utils_error_codes0.RawError<"AUTHENTICATION_REQUIRED">;
@@ -99,6 +99,7 @@ Follow [rfc8628#section-3.2](https://datatracker.ietf.org/doc/html/rfc8628#secti
99
99
  data: {
100
100
  deviceCode,
101
101
  userCode,
102
+ userId: null,
102
103
  expiresAt,
103
104
  status: "pending",
104
105
  pollingInterval: ms(opts.interval),
@@ -331,6 +332,28 @@ const deviceVerify = createAuthEndpoint("/device", {
331
332
  error: "expired_token",
332
333
  error_description: DEVICE_AUTHORIZATION_ERROR_CODES.EXPIRED_USER_CODE.message
333
334
  });
335
+ const session = await getSessionFromCtx(ctx);
336
+ if (session?.user?.id && !deviceCodeRecord.userId && deviceCodeRecord.status === "pending") {
337
+ if (await ctx.context.adapter.update({
338
+ model: "deviceCode",
339
+ where: [
340
+ {
341
+ field: "id",
342
+ value: deviceCodeRecord.id
343
+ },
344
+ {
345
+ field: "status",
346
+ value: "pending"
347
+ },
348
+ {
349
+ field: "userId",
350
+ operator: "eq",
351
+ value: null
352
+ }
353
+ ],
354
+ update: { userId: session.user.id }
355
+ })) deviceCodeRecord.userId = session.user.id;
356
+ }
334
357
  return ctx.json({
335
358
  user_code,
336
359
  status: deviceCodeRecord.status
@@ -387,7 +410,11 @@ const deviceApprove = createAuthEndpoint("/device/approve", {
387
410
  error: "invalid_request",
388
411
  error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_ALREADY_PROCESSED.message
389
412
  });
390
- if (deviceCodeRecord.userId && deviceCodeRecord.userId !== session.user.id) throw new APIError("FORBIDDEN", {
413
+ if (!deviceCodeRecord.userId) throw new APIError("BAD_REQUEST", {
414
+ error: "invalid_request",
415
+ error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_NOT_CLAIMED.message
416
+ });
417
+ if (deviceCodeRecord.userId !== session.user.id) throw new APIError("FORBIDDEN", {
391
418
  error: "access_denied",
392
419
  error_description: "You are not authorized to approve this device authorization"
393
420
  });
@@ -454,7 +481,11 @@ const deviceDeny = createAuthEndpoint("/device/deny", {
454
481
  error: "invalid_request",
455
482
  error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_ALREADY_PROCESSED.message
456
483
  });
457
- if (deviceCodeRecord.userId && deviceCodeRecord.userId !== session.user.id) throw new APIError("FORBIDDEN", {
484
+ if (!deviceCodeRecord.userId) throw new APIError("BAD_REQUEST", {
485
+ error: "invalid_request",
486
+ error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_NOT_CLAIMED.message
487
+ });
488
+ if (deviceCodeRecord.userId !== session.user.id) throw new APIError("FORBIDDEN", {
458
489
  error: "access_denied",
459
490
  error_description: "You are not authorized to deny this device authorization"
460
491
  });
@@ -466,7 +497,7 @@ const deviceDeny = createAuthEndpoint("/device/deny", {
466
497
  }],
467
498
  update: {
468
499
  status: "denied",
469
- userId: deviceCodeRecord.userId || session.user.id
500
+ userId: session.user.id
470
501
  }
471
502
  });
472
503
  return ctx.json({ success: true });
@@ -465,7 +465,7 @@ const requestPasswordResetEmailOTP = (opts) => createAuthEndpoint("/email-otp/re
465
465
  } }
466
466
  } }
467
467
  }, async (ctx) => {
468
- const email = ctx.body.email;
468
+ const email = ctx.body.email.toLowerCase();
469
469
  const identifier = toOTPIdentifier("forget-password", email);
470
470
  const otp = await resolveOTP(ctx, opts, email, "forget-password");
471
471
  if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
@@ -517,7 +517,7 @@ const forgetPasswordEmailOTP = (opts) => {
517
517
  } }
518
518
  }, async (ctx) => {
519
519
  warnDeprecation();
520
- const email = ctx.body.email;
520
+ const email = ctx.body.email.toLowerCase();
521
521
  const identifier = toOTPIdentifier("forget-password", email);
522
522
  const otp = await resolveOTP(ctx, opts, email, "forget-password");
523
523
  if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
@@ -567,7 +567,7 @@ const resetPasswordEmailOTP = (opts) => createAuthEndpoint("/email-otp/reset-pas
567
567
  } }
568
568
  } }
569
569
  }, async (ctx) => {
570
- const email = ctx.body.email;
570
+ const email = ctx.body.email.toLowerCase();
571
571
  await atomicVerifyOTP(ctx, opts, toOTPIdentifier("forget-password", email), ctx.body.otp);
572
572
  const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
573
573
  if (!user) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
@@ -585,7 +585,7 @@ const resetPasswordEmailOTP = (opts) => createAuthEndpoint("/email-otp/reset-pas
585
585
  else await ctx.context.internalAdapter.updatePassword(user.user.id, passwordHash);
586
586
  if (ctx.context.options.emailAndPassword?.onPasswordReset) await ctx.context.options.emailAndPassword.onPasswordReset({ user: user.user }, ctx.request);
587
587
  if (!user.user.emailVerified) await ctx.context.internalAdapter.updateUser(user.user.id, { emailVerified: true });
588
- if (ctx.context.options.emailAndPassword?.revokeSessionsOnPasswordReset) await ctx.context.internalAdapter.deleteSessions(user.user.id);
588
+ if (ctx.context.options.emailAndPassword?.revokeSessionsOnPasswordReset) await ctx.context.internalAdapter.deleteUserSessions(user.user.id);
589
589
  return ctx.json({ success: true });
590
590
  });
591
591
  const requestEmailChangeEmailOTPBodySchema = z.object({
@@ -1,4 +1,3 @@
1
- import "../../oauth2/error-codes.mjs";
2
1
  import { defineErrorCodes } from "@better-auth/core/utils/error-codes";
3
2
  //#region src/plugins/generic-oauth/error-codes.ts
4
3
  const GENERIC_OAUTH_ERROR_CODES = defineErrorCodes({
@@ -23,11 +23,9 @@ declare module "@better-auth/core" {
23
23
  }
24
24
  /**
25
25
  * Base type for OAuth provider options.
26
- * Extracts common fields from GenericOAuthConfig and makes clientSecret required.
26
+ * Extracts common fields from GenericOAuthConfig for provider helpers.
27
27
  */
28
- type BaseOAuthProviderOptions = Omit<Pick<GenericOAuthConfig, "clientId" | "clientSecret" | "scopes" | "redirectURI" | "pkce" | "disableImplicitSignUp" | "disableSignUp" | "overrideUserInfo">, "clientSecret"> & {
29
- /** OAuth client secret (required for provider options) */clientSecret: string;
30
- };
28
+ type BaseOAuthProviderOptions = Pick<GenericOAuthConfig, "clientId" | "clientSecret" | "tokenEndpointAuth" | "scopes" | "redirectURI" | "pkce" | "disableImplicitSignUp" | "disableSignUp" | "overrideUserInfo">;
31
29
  /**
32
30
  * A generic OAuth plugin that registers any OAuth/OIDC provider
33
31
  * as a first-class social provider.
@@ -10,16 +10,15 @@ import { okta } from "./providers/okta.mjs";
10
10
  import { patreon } from "./providers/patreon.mjs";
11
11
  import { slack } from "./providers/slack.mjs";
12
12
  import { APIError } from "@better-auth/core/error";
13
- import { createAuthorizationURL, refreshAccessToken, validateAuthorizationCode } from "@better-auth/core/oauth2";
13
+ import { applyDefaultAccessTokenExpiry, createAuthorizationURL, refreshAccessToken, validateAuthorizationCode } from "@better-auth/core/oauth2";
14
14
  import { decodeJwt } from "jose";
15
15
  import { betterFetch } from "@better-fetch/fetch";
16
16
  //#region src/plugins/generic-oauth/index.ts
17
- function buildClientAssertion(config, tokenEndpoint) {
18
- if (config.authentication !== "private_key_jwt" || !config.clientAssertion) return;
19
- return {
20
- ...config.clientAssertion,
21
- tokenEndpoint
22
- };
17
+ function isSecretlessTokenEndpointAuth(tokenEndpointAuth) {
18
+ return tokenEndpointAuth?.method === "private_key_jwt" || tokenEndpointAuth?.method === "none";
19
+ }
20
+ function isClientSecretTokenEndpointAuth(tokenEndpointAuth) {
21
+ return tokenEndpointAuth?.method === "client_secret_basic" || tokenEndpointAuth?.method === "client_secret_post";
23
22
  }
24
23
  async function fetchDiscovery(url, headers) {
25
24
  const result = await betterFetch(url, {
@@ -100,11 +99,15 @@ const genericOAuth = (options) => {
100
99
  isOidc = Array.isArray(discovered.id_token_signing_alg_values_supported) && discovered.id_token_signing_alg_values_supported.length > 0;
101
100
  } else if (!authorizationUrl || !tokenUrl) ctx.logger.error(`Provider "${c.providerId}": discovery returned no data and no explicit endpoints configured. OAuth sign-in will fail for this provider.`);
102
101
  }
103
- if (!c.clientSecret && !c.clientAssertion && c.authentication !== "private_key_jwt") ctx.logger.warn(`Provider "${c.providerId}": no clientSecret or clientAssertion configured. Token exchange will fail unless this is a public client.`);
102
+ const tokenEndpointAuth = c.tokenEndpointAuth;
103
+ if (c.clientSecret && isSecretlessTokenEndpointAuth(tokenEndpointAuth)) throw new Error(`Provider "${c.providerId}": tokenEndpointAuth.method "${tokenEndpointAuth?.method}" cannot be combined with clientSecret`);
104
+ if (!c.clientSecret && isClientSecretTokenEndpointAuth(tokenEndpointAuth)) throw new Error(`Provider "${c.providerId}": tokenEndpointAuth.method "${tokenEndpointAuth?.method}" requires clientSecret`);
105
+ if (!c.clientSecret && !tokenEndpointAuth && c.authentication === "basic") throw new Error(`Provider "${c.providerId}": authentication "basic" requires clientSecret`);
104
106
  genericProviders.push({
105
107
  id: c.providerId,
106
108
  name: c.name ?? c.providerId,
107
109
  issuer,
110
+ allowIdpInitiated: c.allowIdpInitiated,
108
111
  createAuthorizationURL(data) {
109
112
  if (!authorizationUrl) throw APIError.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.INVALID_OAUTH_CONFIGURATION);
110
113
  return createAuthorizationURL({
@@ -127,14 +130,17 @@ const genericOAuth = (options) => {
127
130
  accessType: c.accessType,
128
131
  responseType: c.responseType,
129
132
  responseMode: c.responseMode,
130
- additionalParams: c.authorizationUrlParams,
133
+ additionalParams: {
134
+ ...c.authorizationUrlParams ?? {},
135
+ ...data.additionalParams ?? {}
136
+ },
131
137
  loginHint: data.loginHint
132
138
  });
133
139
  },
134
140
  async validateAuthorizationCode(data) {
135
- if (c.getToken) return c.getToken(data);
141
+ if (c.getToken) return applyDefaultAccessTokenExpiry(await c.getToken(data), c.accessTokenExpiresIn);
136
142
  if (!tokenUrl) throw APIError.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.TOKEN_URL_NOT_FOUND);
137
- return validateAuthorizationCode({
143
+ return applyDefaultAccessTokenExpiry(await validateAuthorizationCode({
138
144
  headers: c.authorizationHeaders,
139
145
  code: data.code,
140
146
  codeVerifier: c.pkce ?? true ? data.codeVerifier : void 0,
@@ -146,9 +152,9 @@ const genericOAuth = (options) => {
146
152
  },
147
153
  tokenEndpoint: tokenUrl,
148
154
  authentication: c.authentication,
149
- additionalParams: c.tokenUrlParams,
150
- clientAssertion: buildClientAssertion(c, tokenUrl)
151
- });
155
+ tokenEndpointAuth,
156
+ additionalParams: c.tokenUrlParams
157
+ }), c.accessTokenExpiresIn);
152
158
  },
153
159
  async getUserInfo(tokens) {
154
160
  const raw = c.getUserInfo ? await c.getUserInfo(tokens) : await fetchUserInfo(tokens, userInfoUrl);
@@ -172,16 +178,16 @@ const genericOAuth = (options) => {
172
178
  },
173
179
  async refreshAccessToken(refreshToken) {
174
180
  if (!tokenUrl) throw APIError.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.TOKEN_URL_NOT_FOUND);
175
- return refreshAccessToken({
181
+ return applyDefaultAccessTokenExpiry(await refreshAccessToken({
176
182
  refreshToken,
177
183
  options: {
178
184
  clientId: c.clientId,
179
185
  clientSecret: c.clientSecret
180
186
  },
181
187
  authentication: c.authentication,
182
- clientAssertion: buildClientAssertion(c, tokenUrl),
188
+ tokenEndpointAuth,
183
189
  tokenEndpoint: tokenUrl
184
- });
190
+ }), c.accessTokenExpiresIn);
185
191
  },
186
192
  disableImplicitSignUp: c.disableImplicitSignUp,
187
193
  disableSignUp: c.disableSignUp,
@@ -27,6 +27,7 @@ function auth0(options) {
27
27
  discoveryUrl: `https://${options.domain.replace(/^https?:\/\//, "")}/.well-known/openid-configuration`,
28
28
  clientId: options.clientId,
29
29
  clientSecret: options.clientSecret,
30
+ tokenEndpointAuth: options.tokenEndpointAuth,
30
31
  scopes: options.scopes ?? [
31
32
  "openid",
32
33
  "profile",
@@ -44,6 +44,7 @@ function gumroad(options) {
44
44
  tokenUrl: "https://api.gumroad.com/oauth/token",
45
45
  clientId: options.clientId,
46
46
  clientSecret: options.clientSecret,
47
+ tokenEndpointAuth: options.tokenEndpointAuth,
47
48
  scopes: options.scopes ?? ["view_profile"],
48
49
  redirectURI: options.redirectURI,
49
50
  pkce: options.pkce,
@@ -43,6 +43,7 @@ function hubspot(options) {
43
43
  tokenUrl: "https://api.hubapi.com/oauth/v1/token",
44
44
  clientId: options.clientId,
45
45
  clientSecret: options.clientSecret,
46
+ tokenEndpointAuth: options.tokenEndpointAuth,
46
47
  scopes: options.scopes ?? defaultScopes,
47
48
  redirectURI: options.redirectURI,
48
49
  authentication: "post",
@@ -27,6 +27,7 @@ function keycloak(options) {
27
27
  discoveryUrl: `${options.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`,
28
28
  clientId: options.clientId,
29
29
  clientSecret: options.clientSecret,
30
+ tokenEndpointAuth: options.tokenEndpointAuth,
30
31
  scopes: options.scopes ?? [
31
32
  "openid",
32
33
  "profile",
@@ -75,6 +75,7 @@ function line(options) {
75
75
  userInfoUrl,
76
76
  clientId: options.clientId,
77
77
  clientSecret: options.clientSecret,
78
+ tokenEndpointAuth: options.tokenEndpointAuth,
78
79
  scopes: options.scopes ?? defaultScopes,
79
80
  redirectURI: options.redirectURI,
80
81
  pkce: options.pkce,
@@ -50,6 +50,7 @@ function microsoftEntraId(options) {
50
50
  userInfoUrl,
51
51
  clientId: options.clientId,
52
52
  clientSecret: options.clientSecret,
53
+ tokenEndpointAuth: options.tokenEndpointAuth,
53
54
  scopes: options.scopes ?? defaultScopes,
54
55
  redirectURI: options.redirectURI,
55
56
  pkce: options.pkce,
@@ -27,6 +27,7 @@ function okta(options) {
27
27
  discoveryUrl: `${options.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`,
28
28
  clientId: options.clientId,
29
29
  clientSecret: options.clientSecret,
30
+ tokenEndpointAuth: options.tokenEndpointAuth,
30
31
  scopes: options.scopes ?? [
31
32
  "openid",
32
33
  "profile",
@@ -43,6 +43,7 @@ function patreon(options) {
43
43
  tokenUrl: "https://www.patreon.com/api/oauth2/token",
44
44
  clientId: options.clientId,
45
45
  clientSecret: options.clientSecret,
46
+ tokenEndpointAuth: options.tokenEndpointAuth,
46
47
  scopes: options.scopes ?? defaultScopes,
47
48
  redirectURI: options.redirectURI,
48
49
  pkce: options.pkce,
@@ -45,6 +45,7 @@ function slack(options) {
45
45
  userInfoUrl: "https://slack.com/api/openid.connect.userInfo",
46
46
  clientId: options.clientId,
47
47
  clientSecret: options.clientSecret,
48
+ tokenEndpointAuth: options.tokenEndpointAuth,
48
49
  scopes: options.scopes ?? defaultScopes,
49
50
  redirectURI: options.redirectURI,
50
51
  pkce: options.pkce,
@@ -1,5 +1,5 @@
1
1
  import { User } from "@better-auth/core/db";
2
- import { ClientAssertionConfig, OAuth2Tokens, OAuth2UserInfo } from "@better-auth/core/oauth2";
2
+ import { OAuth2Tokens, OAuth2UserInfo, TokenEndpointAuth } from "@better-auth/core/oauth2";
3
3
 
4
4
  //#region src/plugins/generic-oauth/types.d.ts
5
5
  interface GenericOAuthOptions<ID extends string = string> {
@@ -43,6 +43,14 @@ interface GenericOAuthConfig<ID extends string = string> {
43
43
  clientId: string;
44
44
  /** OAuth client secret */
45
45
  clientSecret?: string | undefined;
46
+ /**
47
+ * Token endpoint client authentication method.
48
+ *
49
+ * Use `private_key_jwt` for IdPs that authenticate clients with RFC 7523
50
+ * client assertions instead of a client secret. Secret-based methods require
51
+ * clientSecret.
52
+ */
53
+ tokenEndpointAuth?: TokenEndpointAuth | undefined;
46
54
  /**
47
55
  * Array of OAuth scopes to request.
48
56
  * @default []
@@ -79,6 +87,13 @@ interface GenericOAuthConfig<ID extends string = string> {
79
87
  * Use "offline" to request a refresh token.
80
88
  */
81
89
  accessType?: string | undefined;
90
+ /**
91
+ * Fallback access-token lifetime, in seconds, used only when the provider's
92
+ * token response omits `expires_in`. Set this so `getAccessToken` can track
93
+ * expiry and refresh the token; leave unset if the provider returns
94
+ * `expires_in`.
95
+ */
96
+ accessTokenExpiresIn?: number | undefined;
82
97
  /**
83
98
  * Custom function to exchange authorization code for tokens.
84
99
  * If provided, this function will be used instead of the default token exchange logic.
@@ -111,7 +126,9 @@ interface GenericOAuthConfig<ID extends string = string> {
111
126
  authorizationUrlParams?: Record<string, string> | undefined;
112
127
  /**
113
128
  * Additional search-params to add to the tokenUrl.
114
- * Warning: Search-params added here overwrite any default params.
129
+ * Parameters already set by Better Auth are preserved. Configure token
130
+ * endpoint client authentication with clientId, clientSecret, and
131
+ * tokenEndpointAuth.
115
132
  */
116
133
  tokenUrlParams?: Record<string, string> | undefined;
117
134
  /**
@@ -125,14 +142,10 @@ interface GenericOAuthConfig<ID extends string = string> {
125
142
  disableSignUp?: boolean | undefined;
126
143
  /**
127
144
  * Authentication method for token requests.
145
+ * "basic" requires clientSecret.
128
146
  * @default "post"
129
147
  */
130
- authentication?: ("basic" | "post" | "private_key_jwt") | undefined;
131
- /**
132
- * Client assertion config for `private_key_jwt` authentication.
133
- * Required when `authentication` is `"private_key_jwt"`.
134
- */
135
- clientAssertion?: ClientAssertionConfig | undefined;
148
+ authentication?: ("basic" | "post") | undefined;
136
149
  /**
137
150
  * Custom headers to include in the discovery request.
138
151
  * Useful for providers like Epic that require specific headers (e.g., Epic-Client-ID).
@@ -151,6 +164,15 @@ interface GenericOAuthConfig<ID extends string = string> {
151
164
  * @default false
152
165
  */
153
166
  overrideUserInfo?: boolean | undefined;
167
+ /**
168
+ * Accept callbacks from providers that initiate the OAuth flow without
169
+ * sending a `state` parameter (e.g. Clever). When enabled, stateless
170
+ * callbacks restart the OAuth flow server-side with a fresh `state` and
171
+ * PKCE verifier. See the generic-oauth docs for details.
172
+ *
173
+ * @default false
174
+ */
175
+ allowIdpInitiated?: boolean | undefined;
154
176
  }
155
177
  //#endregion
156
178
  export { GenericOAuthConfig, GenericOAuthOptions };
@@ -1,6 +1,6 @@
1
1
  import { InferOptionSchema, InferPluginContext, InferPluginErrorCodes, InferPluginIDs } from "../types/plugins.mjs";
2
2
  import { HIDE_METADATA } from "../utils/hide-metadata.mjs";
3
- import { AccessControl, ArrayElement, Role, Statements, SubArray, Subset } from "./access/types.mjs";
3
+ import { AccessControl, ArrayElement, ExactRoleStatements, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, Statements, SubArray, Subset } from "./access/types.mjs";
4
4
  import { AuthorizeResponse, createAccessControl, role } from "./access/access.mjs";
5
5
  import { OrganizationOptions } from "./organization/types.mjs";
6
6
  import { InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferTeam, Invitation, InvitationInput, InvitationStatus, Member, MemberInput, Organization, OrganizationInput, OrganizationRole, OrganizationSchema, Team, TeamInput, TeamMember, TeamMemberInput, defaultRolesSchema, invitationSchema, invitationStatus, memberSchema, organizationRoleSchema, organizationSchema, roleSchema, teamMemberSchema, teamSchema } from "./organization/schema.mjs";
@@ -62,4 +62,4 @@ import { USERNAME_ERROR_CODES } from "./username/error-codes.mjs";
62
62
  import { UsernameOptions, username } from "./username/index.mjs";
63
63
  import { hasPermission } from "./organization/has-permission.mjs";
64
64
  import { DefaultOrganizationPlugin, DynamicAccessControlEndpoints, OrganizationCreator, OrganizationEndpoints, OrganizationPlugin, TeamEndpoints, organization, parseRoles } from "./organization/organization.mjs";
65
- export { AccessControl, AdminOptions, AnonymousOptions, AnonymousSession, ArrayElement, Auth0Options, AuthorizationQuery, AuthorizeResponse, BackupCodeOptions, BaseCaptchaOptions, BaseOAuthProviderOptions, BearerOptions, CaptchaFoxOptions, CaptchaOptions, Client, CloudflareTurnstileOptions, CodeVerificationValue, CustomSessionPluginOptions, DefaultOrganizationPlugin, DeviceAuthorizationOptions, DynamicAccessControlEndpoints, MULTI_SESSION_ERROR_CODES as ERROR_CODES, EmailOTPOptions, FieldSchema, GenericOAuthConfig, GenericOAuthOptions, GoogleRecaptchaOptions, GumroadOptions, HCaptchaOptions, HIDE_METADATA, HaveIBeenPwnedOptions, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOptionSchema, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginContext, InferPluginErrorCodes, InferPluginIDs, InferTeam, Invitation, InvitationInput, InvitationStatus, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodOptions, LineOptions, LoginResult, MagicLinkOptions, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OAuthProxyOptions, OIDCMetadata, OIDCOptions, OTPOptions, OktaOptions, OneTapOptions, OneTimeTokenOptions, OpenAPIModelSchema, OpenAPIOptions, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, Path, PatreonOptions, PhoneNumberOptions, Provider, ResolvedSigningKey, Role, SIWEPluginOptions, SessionWithImpersonatedBy, SlackOptions, Statements, SubArray, Subset, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, TestCookie, TestHelpers, TestUtilsOptions, TimeString, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, UsernameOptions, admin, anonymous, auth0, backupCode2fa, bearer, captcha, createAccessControl, createJwk, customSession, defaultRolesSchema, deviceAuthorization, deviceAuthorizationOptionsSchema, emailOTP, encodeBackupCodes, generateBackupCodes, generateExportedKeyPair, generator, genericOAuth, getBackupCodes, getClient, getJwtToken, getMCPProtectedResourceMetadata, getMCPProviderMetadata, getMetadata, getOrgAdapter, gumroad, hasPermission, haveIBeenPwned, hubspot, invitationSchema, invitationStatus, jwt, keycloak, lastLoginMethod, line, magicLink, mcp, memberSchema, microsoftEntraId, ms, multiSession, oAuthDiscoveryMetadata, oAuthProtectedResourceMetadata, oAuthProxy, oidcProvider, okta, oneTap, oneTimeToken, openAPI, organization, organizationRoleSchema, organizationSchema, otp2fa, parseRoles, patreon, phoneNumber, resolveSigningKey, role, roleSchema, sec, signJWT, siwe, slack, teamMemberSchema, teamSchema, testUtils, toExpJWT, totp2fa, twoFactor, twoFactorClient, username, verifyBackupCode, verifyJWT, withMcpAuth };
65
+ export { AccessControl, AdminOptions, AnonymousOptions, AnonymousSession, ArrayElement, Auth0Options, AuthorizationQuery, AuthorizeResponse, BackupCodeOptions, BaseCaptchaOptions, BaseOAuthProviderOptions, BearerOptions, CaptchaFoxOptions, CaptchaOptions, Client, CloudflareTurnstileOptions, CodeVerificationValue, CustomSessionPluginOptions, DefaultOrganizationPlugin, DeviceAuthorizationOptions, DynamicAccessControlEndpoints, MULTI_SESSION_ERROR_CODES as ERROR_CODES, EmailOTPOptions, ExactRoleStatements, FieldSchema, GenericOAuthConfig, GenericOAuthOptions, GoogleRecaptchaOptions, GumroadOptions, HCaptchaOptions, HIDE_METADATA, HaveIBeenPwnedOptions, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOptionSchema, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginContext, InferPluginErrorCodes, InferPluginIDs, InferTeam, Invitation, InvitationInput, InvitationStatus, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodOptions, LineOptions, LoginResult, MagicLinkOptions, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OAuthProxyOptions, OIDCMetadata, OIDCOptions, OTPOptions, OktaOptions, OneTapOptions, OneTimeTokenOptions, OpenAPIModelSchema, OpenAPIOptions, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, Path, PatreonOptions, PhoneNumberOptions, Provider, ResolvedSigningKey, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, SIWEPluginOptions, SessionWithImpersonatedBy, SlackOptions, Statements, SubArray, Subset, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, TestCookie, TestHelpers, TestUtilsOptions, TimeString, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, UsernameOptions, admin, anonymous, auth0, backupCode2fa, bearer, captcha, createAccessControl, createJwk, customSession, defaultRolesSchema, deviceAuthorization, deviceAuthorizationOptionsSchema, emailOTP, encodeBackupCodes, generateBackupCodes, generateExportedKeyPair, generator, genericOAuth, getBackupCodes, getClient, getJwtToken, getMCPProtectedResourceMetadata, getMCPProviderMetadata, getMetadata, getOrgAdapter, gumroad, hasPermission, haveIBeenPwned, hubspot, invitationSchema, invitationStatus, jwt, keycloak, lastLoginMethod, line, magicLink, mcp, memberSchema, microsoftEntraId, ms, multiSession, oAuthDiscoveryMetadata, oAuthProtectedResourceMetadata, oAuthProxy, oidcProvider, okta, oneTap, oneTimeToken, openAPI, organization, organizationRoleSchema, organizationSchema, otp2fa, parseRoles, patreon, phoneNumber, resolveSigningKey, role, roleSchema, sec, signJWT, siwe, slack, teamMemberSchema, teamSchema, testUtils, toExpJWT, totp2fa, twoFactor, twoFactorClient, username, verifyBackupCode, verifyJWT, withMcpAuth };
@@ -16,7 +16,7 @@ declare function toExpJWT(expirationTime: number | Date | string, iat: number):
16
16
  declare function generateExportedKeyPair(options?: JwtOptions | undefined): Promise<{
17
17
  publicWebKey: jose.JWK;
18
18
  privateWebKey: jose.JWK;
19
- alg: "RS256" | "PS256" | "ES256" | "ES512" | "EdDSA";
19
+ alg: "EdDSA" | "ES256" | "ES512" | "PS256" | "RS256";
20
20
  cfg: {
21
21
  crv?: "Ed25519" | undefined;
22
22
  } | {
@@ -1,9 +1,9 @@
1
+ import { parseCookies } from "../../cookies/cookie-utils.mjs";
1
2
  import { PACKAGE_VERSION } from "../../version.mjs";
2
3
  //#region src/plugins/last-login-method/client.ts
3
4
  function getCookieValue(name) {
4
5
  if (typeof document === "undefined") return null;
5
- const cookie = document.cookie.split("; ").find((row) => row.startsWith(`${name}=`));
6
- return cookie ? cookie.split("=")[1] : null;
6
+ return parseCookies(document.cookie).get(name) ?? null;
7
7
  }
8
8
  /**
9
9
  * Client-side plugin to retrieve the last used login method
@@ -18,7 +18,14 @@ interface MagicLinkOptions {
18
18
  expiresIn?: number | undefined;
19
19
  /**
20
20
  * Allowed attempts for verifying the magic link token.
21
- * Note: Passing Infinity will allow unlimited attempts.
21
+ *
22
+ * @deprecated Multi-attempt verification is no longer supported. Each
23
+ * magic link token is consumed atomically on the first verification call,
24
+ * so a given token mints at most one session regardless of this value
25
+ * (see GHSA-hc7v-rggr-4hvx). The option is kept for source compatibility
26
+ * and may be removed in a future major; any value other than `1` is
27
+ * ignored and emits a `console.warn` at plugin construction.
28
+ *
22
29
  * @default 1
23
30
  */
24
31
  allowedAttempts?: number;
@@ -27,6 +27,7 @@ const magicLink = (options) => {
27
27
  allowedAttempts: 1,
28
28
  ...options
29
29
  };
30
+ if (options.allowedAttempts !== void 0 && options.allowedAttempts !== 1) console.warn("[better-auth/magic-link] `allowedAttempts` is ignored: tokens are consumed atomically on the first verification call (GHSA-hc7v-rggr-4hvx). Any value other than `1` has no effect; remove the option to silence this warning.");
30
31
  async function storeToken(ctx, token) {
31
32
  if (opts.storeToken === "hashed") return await defaultKeyHasher(token);
32
33
  if (typeof opts.storeToken === "object" && "type" in opts.storeToken && opts.storeToken.type === "custom-hasher") return await opts.storeToken.hash(token);
@@ -59,8 +60,7 @@ const magicLink = (options) => {
59
60
  identifier: storedToken,
60
61
  value: JSON.stringify({
61
62
  email,
62
- name: ctx.body.name,
63
- attempt: 0
63
+ name: ctx.body.name
64
64
  }),
65
65
  expiresAt: new Date(Date.now() + (opts.expiresIn || 300) * 1e3)
66
66
  });
@@ -119,22 +119,9 @@ const magicLink = (options) => {
119
119
  }
120
120
  const newUserCallbackURL = new URL(ctx.query.newUserCallbackURL ? decodeURIComponent(ctx.query.newUserCallbackURL) : callbackURL, ctx.context.baseURL).toString();
121
121
  const storedToken = await storeToken(ctx, token);
122
- const tokenValue = await ctx.context.internalAdapter.findVerificationValue(storedToken);
122
+ const tokenValue = await ctx.context.internalAdapter.consumeVerificationValue(storedToken);
123
123
  if (!tokenValue) redirectWithError("INVALID_TOKEN");
124
- if (tokenValue.expiresAt < /* @__PURE__ */ new Date()) {
125
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(storedToken);
126
- redirectWithError("EXPIRED_TOKEN");
127
- }
128
- const { email, name, attempt = 0 } = JSON.parse(tokenValue.value);
129
- if (attempt >= opts.allowedAttempts) {
130
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(storedToken);
131
- redirectWithError("ATTEMPTS_EXCEEDED");
132
- }
133
- await ctx.context.internalAdapter.updateVerificationByIdentifier(storedToken, { value: JSON.stringify({
134
- email,
135
- name,
136
- attempt: attempt + 1
137
- }) });
124
+ const { email, name } = JSON.parse(tokenValue.value);
138
125
  let isNewUser = false;
139
126
  let user = await ctx.context.internalAdapter.findUserByEmail(email).then((res) => res?.user);
140
127
  if (!user) if (!opts.disableSignUp) {
@@ -72,8 +72,14 @@ async function authorizeMCPOAuth(ctx, options) {
72
72
  });
73
73
  if (invalidScopes.length) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_scope", `The following scopes are invalid: ${invalidScopes.join(", ")}`));
74
74
  if ((!query.code_challenge || !query.code_challenge_method) && options.requirePKCE) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_request", "pkce is required"));
75
- if (!query.code_challenge_method) query.code_challenge_method = "plain";
76
- if (!["s256", options.allowPlainCodeChallengeMethod ? "plain" : "s256"].includes(query.code_challenge_method?.toLowerCase() || "")) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method"));
75
+ if (query.code_challenge_method && !query.code_challenge) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_request", "code_challenge_method requires code_challenge"));
76
+ if (query.code_challenge) {
77
+ const allowedCodeChallengeMethods = options.allowPlainCodeChallengeMethod ? ["s256", "plain"] : ["s256"];
78
+ let codeChallengeMethod = query.code_challenge_method?.toLowerCase();
79
+ if (!codeChallengeMethod && options.allowPlainCodeChallengeMethod) codeChallengeMethod = "plain";
80
+ if (!codeChallengeMethod || !allowedCodeChallengeMethods.includes(codeChallengeMethod)) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method"));
81
+ query.code_challenge_method = codeChallengeMethod;
82
+ }
77
83
  const code = generateRandomString(32, "a-z", "A-Z", "0-9");
78
84
  const codeExpiresInMs = opts.codeExpiresIn * 1e3;
79
85
  const expiresAt = new Date(Date.now() + codeExpiresInMs);