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
@@ -1,5 +1,6 @@
1
1
  import { getBaseURL, isDynamicBaseURLConfig, resolveBaseURL } from "../../utils/url.mjs";
2
2
  import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
3
+ import { constantTimeEqual } from "../../crypto/buffer.mjs";
3
4
  import { generateRandomString } from "../../crypto/random.mjs";
4
5
  import { expireCookie } from "../../cookies/index.mjs";
5
6
  import { resolveDynamicTrustedProxyHeaders } from "../../context/helpers.mjs";
@@ -13,6 +14,7 @@ import { oidcProvider } from "../oidc-provider/index.mjs";
13
14
  import { authorizeMCPOAuth } from "./authorize.mjs";
14
15
  import { isProduction, logger } from "@better-auth/core/env";
15
16
  import { safeJSONParse } from "@better-auth/core/utils/json";
17
+ import { isSafeUrlScheme } from "@better-auth/core/utils/url";
16
18
  import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
17
19
  import * as z from "zod";
18
20
  import { base64 } from "@better-auth/utils/base64";
@@ -45,7 +47,7 @@ const getMCPProviderMetadata = (ctx, options) => {
45
47
  grant_types_supported: ["authorization_code", "refresh_token"],
46
48
  acr_values_supported: ["urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:bronze"],
47
49
  subject_types_supported: ["public"],
48
- id_token_signing_alg_values_supported: ["RS256", "none"],
50
+ id_token_signing_alg_values_supported: ["RS256"],
49
51
  token_endpoint_auth_methods_supported: [
50
52
  "client_secret_basic",
51
53
  "client_secret_post",
@@ -81,11 +83,11 @@ const getMCPProtectedResourceMetadata = (ctx, options) => {
81
83
  "offline_access"
82
84
  ],
83
85
  bearer_methods_supported: ["header"],
84
- resource_signing_alg_values_supported: ["RS256", "none"]
86
+ resource_signing_alg_values_supported: ["RS256"]
85
87
  };
86
88
  };
87
89
  const registerMcpClientBodySchema = z.object({
88
- redirect_uris: z.array(z.string()),
90
+ redirect_uris: z.array(z.string().refine(isSafeUrlScheme, { message: "redirect_uri cannot use a javascript:, data:, or vbscript: scheme" })),
89
91
  token_endpoint_auth_method: z.enum([
90
92
  "none",
91
93
  "client_secret_basic",
@@ -122,7 +124,7 @@ const mcp = (options) => {
122
124
  defaultScope: "openid",
123
125
  accessTokenExpiresIn: 3600,
124
126
  refreshTokenExpiresIn: 604800,
125
- allowPlainCodeChallengeMethod: true,
127
+ allowPlainCodeChallengeMethod: false,
126
128
  ...options.oidcConfig,
127
129
  loginPage: options.loginPage,
128
130
  scopes: [
@@ -225,10 +227,6 @@ const mcp = (options) => {
225
227
  allowedMediaTypes: ["application/x-www-form-urlencoded", "application/json"]
226
228
  }
227
229
  }, async (ctx) => {
228
- ctx.setHeader("Access-Control-Allow-Origin", "*");
229
- ctx.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
230
- ctx.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
231
- ctx.setHeader("Access-Control-Max-Age", "86400");
232
230
  let { body } = ctx;
233
231
  if (!body) throw ctx.error("BAD_REQUEST", {
234
232
  error_description: "request body not found",
@@ -241,25 +239,43 @@ const mcp = (options) => {
241
239
  });
242
240
  let { client_id, client_secret } = body;
243
241
  const authorization = ctx.request?.headers.get("authorization") || null;
244
- if (authorization && !client_id && !client_secret && authorization.startsWith("Basic ")) try {
245
- const encoded = authorization.replace("Basic ", "");
246
- const decoded = new TextDecoder().decode(base64.decode(encoded));
247
- if (!decoded.includes(":")) throw new APIError("UNAUTHORIZED", {
242
+ if (authorization && !client_secret && authorization.startsWith("Basic ")) {
243
+ let decoded;
244
+ try {
245
+ const encoded = authorization.replace("Basic ", "");
246
+ decoded = new TextDecoder().decode(base64.decode(encoded));
247
+ } catch {
248
+ throw new APIError("UNAUTHORIZED", {
249
+ error_description: "invalid authorization header format",
250
+ error: "invalid_client"
251
+ });
252
+ }
253
+ const colonIndex = decoded.indexOf(":");
254
+ if (colonIndex === -1) throw new APIError("UNAUTHORIZED", {
248
255
  error_description: "invalid authorization header format",
249
256
  error: "invalid_client"
250
257
  });
251
- const [id, secret] = decoded.split(":");
258
+ let id;
259
+ let secret;
260
+ try {
261
+ id = decodeURIComponent(decoded.slice(0, colonIndex));
262
+ secret = decodeURIComponent(decoded.slice(colonIndex + 1));
263
+ } catch {
264
+ throw new APIError("UNAUTHORIZED", {
265
+ error_description: "invalid authorization header format",
266
+ error: "invalid_client"
267
+ });
268
+ }
252
269
  if (!id || !secret) throw new APIError("UNAUTHORIZED", {
253
270
  error_description: "invalid authorization header format",
254
271
  error: "invalid_client"
255
272
  });
256
- client_id = id;
257
- client_secret = secret;
258
- } catch {
259
- throw new APIError("UNAUTHORIZED", {
260
- error_description: "invalid authorization header format",
273
+ if (client_id && client_id.toString() !== id) throw new APIError("UNAUTHORIZED", {
274
+ error_description: "client_id in body does not match Authorization header",
261
275
  error: "invalid_client"
262
276
  });
277
+ client_id = id;
278
+ client_secret = secret;
263
279
  }
264
280
  const { grant_type, code, redirect_uri, refresh_token, code_verifier } = body;
265
281
  if (grant_type === "refresh_token") {
@@ -286,6 +302,38 @@ const mcp = (options) => {
286
302
  error_description: "refresh token expired",
287
303
  error: "invalid_grant"
288
304
  });
305
+ const refreshClient = await ctx.context.adapter.findOne({
306
+ model: modelName.oauthClient,
307
+ where: [{
308
+ field: "clientId",
309
+ value: client_id.toString()
310
+ }]
311
+ }).then((res) => {
312
+ if (!res) return null;
313
+ return {
314
+ ...res,
315
+ redirectUrls: res.redirectUrls.split(","),
316
+ metadata: res.metadata ? JSON.parse(res.metadata) : {}
317
+ };
318
+ });
319
+ if (!refreshClient) throw new APIError("UNAUTHORIZED", {
320
+ error_description: "invalid client_id",
321
+ error: "invalid_client"
322
+ });
323
+ if (refreshClient.disabled) throw new APIError("UNAUTHORIZED", {
324
+ error_description: "client is disabled",
325
+ error: "invalid_client"
326
+ });
327
+ if (refreshClient.type !== "public") {
328
+ if (!refreshClient.clientSecret || !client_secret) throw new APIError("UNAUTHORIZED", {
329
+ error_description: "client_secret is required for confidential clients",
330
+ error: "invalid_client"
331
+ });
332
+ if (!constantTimeEqual(refreshClient.clientSecret, client_secret.toString())) throw new APIError("UNAUTHORIZED", {
333
+ error_description: "invalid client_secret",
334
+ error: "invalid_client"
335
+ });
336
+ }
289
337
  const accessToken = generateRandomString(32, "a-z", "A-Z");
290
338
  const newRefreshToken = generateRandomString(32, "a-z", "A-Z");
291
339
  const accessTokenExpiresAt = new Date(Date.now() + opts.accessTokenExpiresIn * 1e3);
@@ -320,20 +368,11 @@ const mcp = (options) => {
320
368
  error_description: "code verifier is missing",
321
369
  error: "invalid_request"
322
370
  });
323
- /**
324
- * We need to check if the code is valid before we can proceed
325
- * with the rest of the request.
326
- */
327
- const verificationValue = await ctx.context.internalAdapter.findVerificationValue(code.toString());
371
+ const verificationValue = await ctx.context.internalAdapter.consumeVerificationValue(code.toString());
328
372
  if (!verificationValue) throw new APIError("UNAUTHORIZED", {
329
373
  error_description: "invalid code",
330
374
  error: "invalid_grant"
331
375
  });
332
- if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("UNAUTHORIZED", {
333
- error_description: "code expired",
334
- error: "invalid_grant"
335
- });
336
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(code.toString());
337
376
  if (!client_id) throw new APIError("UNAUTHORIZED", {
338
377
  error_description: "client_id is required",
339
378
  error: "invalid_client"
@@ -378,11 +417,11 @@ const mcp = (options) => {
378
417
  error: "invalid_request"
379
418
  });
380
419
  } else {
381
- if (!client_secret) throw new APIError("UNAUTHORIZED", {
420
+ if (!client.clientSecret || !client_secret) throw new APIError("UNAUTHORIZED", {
382
421
  error_description: "client_secret is required for confidential clients",
383
422
  error: "invalid_client"
384
423
  });
385
- if (!(client.clientSecret === client_secret.toString())) throw new APIError("UNAUTHORIZED", {
424
+ if (!constantTimeEqual(client.clientSecret, client_secret.toString())) throw new APIError("UNAUTHORIZED", {
386
425
  error_description: "invalid client_secret",
387
426
  error: "invalid_client"
388
427
  });
@@ -400,12 +439,13 @@ const mcp = (options) => {
400
439
  error_description: "code verifier is missing",
401
440
  error: "invalid_request"
402
441
  });
403
- if ((value.codeChallengeMethod === "plain" ? code_verifier : await createHash("SHA-256", "base64urlnopad").digest(code_verifier)) !== value.codeChallenge) throw new APIError("UNAUTHORIZED", {
404
- error_description: "code verification failed",
405
- error: "invalid_request"
406
- });
442
+ if (value.codeChallenge) {
443
+ if ((value.codeChallengeMethod === "plain" ? code_verifier : await createHash("SHA-256", "base64urlnopad").digest(code_verifier)) !== value.codeChallenge) throw new APIError("UNAUTHORIZED", {
444
+ error_description: "code verification failed",
445
+ error: "invalid_request"
446
+ });
447
+ }
407
448
  const requestedScopes = value.scope;
408
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(code.toString());
409
449
  const accessToken = generateRandomString(32, "a-z", "A-Z");
410
450
  const refreshToken = generateRandomString(32, "A-Z", "a-z");
411
451
  const accessTokenExpiresAt = new Date(Date.now() + opts.accessTokenExpiresIn * 1e3);
@@ -1,6 +1,6 @@
1
1
  import { parseSessionOutput, parseUserOutput } from "../../db/schema.mjs";
2
- import { SECURE_COOKIE_PREFIX, parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
3
- import { deleteSessionCookie, expireCookie, parseCookies, setSessionCookie } from "../../cookies/index.mjs";
2
+ import { SECURE_COOKIE_PREFIX, parseCookies, parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
3
+ import { deleteSessionCookie, expireCookie, setSessionCookie } from "../../cookies/index.mjs";
4
4
  import { sessionMiddleware } from "../../api/routes/session.mjs";
5
5
  import { APIError } from "../../api/index.mjs";
6
6
  import { PACKAGE_VERSION } from "../../version.mjs";
@@ -1,13 +1,15 @@
1
+ import { isAPIError } from "../../utils/is-api-error.mjs";
1
2
  import { getOrigin } from "../../utils/url.mjs";
2
3
  import { originCheck } from "../../api/middlewares/origin-check.mjs";
3
4
  import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
4
5
  import { symmetricDecrypt, symmetricEncrypt } from "../../crypto/index.mjs";
5
6
  import { setSessionCookie } from "../../cookies/index.mjs";
6
- import { parseGenericState } from "../../state.mjs";
7
+ import { redirectOnError } from "../../oauth2/errors.mjs";
7
8
  import { handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
9
+ import { parseGenericState } from "../../state.mjs";
8
10
  import { PACKAGE_VERSION } from "../../version.mjs";
9
11
  import { parseJSON } from "../../client/parser.mjs";
10
- import { checkSkipProxy, redirectOnError, resolveCurrentURL, stripTrailingSlash } from "./utils.mjs";
12
+ import { checkSkipProxy, resolveCurrentURL, stripTrailingSlash } from "./utils.mjs";
11
13
  import { defu } from "defu";
12
14
  import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
13
15
  import * as z from "zod";
@@ -90,18 +92,28 @@ const oAuthProxy = (opts) => {
90
92
  throw redirectOnError(ctx, errorURL, "payload_expired");
91
93
  }
92
94
  try {
93
- await parseGenericState(ctx, payload.state);
95
+ await parseGenericState(ctx, payload.state, { skipStateCookieCheck: true });
94
96
  } catch (e) {
95
97
  ctx.context.logger.warn("Failed to clean up OAuth state", e);
96
98
  }
97
- const result = await handleOAuthUserInfo(ctx, {
98
- userInfo: payload.userInfo,
99
- account: payload.account,
100
- callbackURL: payload.callbackURL,
101
- disableSignUp: payload.disableSignUp
102
- });
103
- if (result.error || !result.data) {
104
- ctx.context.logger.error("Failed to create user or session", result.error);
99
+ let result;
100
+ try {
101
+ result = await handleOAuthUserInfo(ctx, {
102
+ userInfo: payload.userInfo,
103
+ account: payload.account,
104
+ callbackURL: payload.callbackURL,
105
+ disableSignUp: payload.disableSignUp
106
+ });
107
+ } catch (e) {
108
+ if (isAPIError(e) && e.body?.code) throw redirectOnError(ctx, errorURL, e.body.code, e.body.message);
109
+ throw e;
110
+ }
111
+ if (result.error) {
112
+ ctx.context.logger.error("OAuth proxy callback error", result.error);
113
+ throw redirectOnError(ctx, errorURL, result.error.split(" ").join("_"));
114
+ }
115
+ if (!result.data) {
116
+ ctx.context.logger.error("OAuth proxy callback missing session data");
105
117
  throw redirectOnError(ctx, errorURL, "user_creation_failed");
106
118
  }
107
119
  await setSessionCookie(ctx, result.data);
@@ -141,6 +153,7 @@ const oAuthProxy = (opts) => {
141
153
  data: state
142
154
  }));
143
155
  } catch {
156
+ ctx.context.logger.debug("OAuth proxy: could not decrypt state package, falling back to regular callback");
144
157
  return;
145
158
  }
146
159
  if (!statePackage.isOAuthProxy || !statePackage.state || !statePackage.stateCookie) {
@@ -248,29 +261,29 @@ const oAuthProxy = (opts) => {
248
261
  const oauthURL = new URL(providerURL);
249
262
  const originalState = oauthURL.searchParams.get("state");
250
263
  if (!originalState) return;
251
- let stateCookieValue;
252
- if (ctx.context.oauthConfig.storeStateStrategy === "cookie") {
253
- const setCookieHeader = ctx.context.responseHeaders?.get("set-cookie");
254
- if (setCookieHeader) {
255
- const parsedCookies = parseSetCookieHeader(setCookieHeader);
256
- const stateCookie = ctx.context.createAuthCookie("oauth_state");
257
- stateCookieValue = parsedCookies.get(stateCookie.name)?.value;
258
- }
259
- } else {
260
- const verification = await ctx.context.internalAdapter.findVerificationValue(originalState);
261
- if (verification) stateCookieValue = await symmetricEncrypt({
262
- key: getEncryptionKey(ctx),
263
- data: verification.value
264
- });
265
- }
266
- if (!stateCookieValue) {
267
- ctx.context.logger.warn("No OAuth state cookie value found");
268
- return;
269
- }
270
264
  try {
265
+ let plaintextState;
266
+ if (ctx.context.oauthConfig.storeStateStrategy === "cookie") {
267
+ const setCookieHeader = ctx.context.responseHeaders?.get("set-cookie");
268
+ if (setCookieHeader) {
269
+ const oauthStateCookie = ctx.context.createAuthCookie("oauth_state");
270
+ const encryptedCookieValue = parseSetCookieHeader(setCookieHeader).get(oauthStateCookie.name)?.value;
271
+ if (encryptedCookieValue) plaintextState = await symmetricDecrypt({
272
+ key: ctx.context.secretConfig,
273
+ data: encryptedCookieValue
274
+ });
275
+ }
276
+ } else plaintextState = (await ctx.context.internalAdapter.findVerificationValue(originalState))?.value;
277
+ if (!plaintextState) {
278
+ ctx.context.logger.warn("No OAuth state found for proxy");
279
+ return;
280
+ }
271
281
  const statePackage = {
272
282
  state: originalState,
273
- stateCookie: stateCookieValue,
283
+ stateCookie: await symmetricEncrypt({
284
+ key: getEncryptionKey(ctx),
285
+ data: plaintextState
286
+ }),
274
287
  isOAuthProxy: true
275
288
  };
276
289
  const encryptedPackage = await symmetricEncrypt({
@@ -283,7 +296,7 @@ const oAuthProxy = (opts) => {
283
296
  url: oauthURL.toString()
284
297
  };
285
298
  } catch (e) {
286
- ctx.context.logger.error("Failed to encrypt OAuth proxy state package:", e);
299
+ ctx.context.logger.error("Failed to prepare OAuth proxy state:", e);
287
300
  }
288
301
  })
289
302
  }, {
@@ -1,4 +1,4 @@
1
- import { getOrigin } from "../../utils/url.mjs";
1
+ import { getOrigin, trimTrailingSlashes } from "../../utils/url.mjs";
2
2
  import { env } from "@better-auth/core/env";
3
3
  //#region src/plugins/oauth-proxy/utils.ts
4
4
  /**
@@ -6,7 +6,7 @@ import { env } from "@better-auth/core/env";
6
6
  */
7
7
  function stripTrailingSlash(url) {
8
8
  if (!url) return "";
9
- return url.replace(/\/+$/, "");
9
+ return trimTrailingSlashes(url);
10
10
  }
11
11
  /**
12
12
  * Get base URL from vendor-specific environment variables
@@ -37,12 +37,5 @@ function checkSkipProxy(ctx, opts) {
37
37
  if (!currentURL) return false;
38
38
  return getOrigin(productionURL) === getOrigin(currentURL);
39
39
  }
40
- /**
41
- * Redirect to error URL with error code
42
- */
43
- function redirectOnError(ctx, errorURL, error) {
44
- const sep = errorURL.includes("?") ? "&" : "?";
45
- throw ctx.redirect(`${errorURL}${sep}error=${error}`);
46
- }
47
40
  //#endregion
48
- export { checkSkipProxy, redirectOnError, resolveCurrentURL, stripTrailingSlash };
41
+ export { checkSkipProxy, resolveCurrentURL, stripTrailingSlash };
@@ -93,8 +93,14 @@ async function authorize(ctx, options) {
93
93
  });
94
94
  if (invalidScopes.length) return handleRedirect(formatErrorURL(query.redirect_uri, "invalid_scope", `The following scopes are invalid: ${invalidScopes.join(", ")}`));
95
95
  if ((!query.code_challenge || !query.code_challenge_method) && options.requirePKCE) return handleRedirect(formatErrorURL(query.redirect_uri, "invalid_request", "pkce is required"));
96
- if (!query.code_challenge_method) query.code_challenge_method = "plain";
97
- if (!["s256", options.allowPlainCodeChallengeMethod ? "plain" : "s256"].includes(query.code_challenge_method?.toLowerCase() || "")) return handleRedirect(formatErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method"));
96
+ if (query.code_challenge_method && !query.code_challenge) return handleRedirect(formatErrorURL(query.redirect_uri, "invalid_request", "code_challenge_method requires code_challenge"));
97
+ if (query.code_challenge) {
98
+ const allowedCodeChallengeMethods = options.allowPlainCodeChallengeMethod ? ["s256", "plain"] : ["s256"];
99
+ let codeChallengeMethod = query.code_challenge_method?.toLowerCase();
100
+ if (!codeChallengeMethod && options.allowPlainCodeChallengeMethod) codeChallengeMethod = "plain";
101
+ if (!codeChallengeMethod || !allowedCodeChallengeMethods.includes(codeChallengeMethod)) return handleRedirect(formatErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method"));
102
+ query.code_challenge_method = codeChallengeMethod;
103
+ }
98
104
  const code = generateRandomString(32, "a-z", "A-Z", "0-9");
99
105
  const codeExpiresInMs = opts.codeExpiresIn * 1e3;
100
106
  const expiresAt = new Date(Date.now() + codeExpiresInMs);
@@ -1,5 +1,6 @@
1
1
  import { mergeSchema } from "../../db/schema.mjs";
2
2
  import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
3
+ import { constantTimeEqual } from "../../crypto/buffer.mjs";
3
4
  import { generateRandomString } from "../../crypto/random.mjs";
4
5
  import { symmetricDecrypt, symmetricEncrypt } from "../../crypto/index.mjs";
5
6
  import { expireCookie } from "../../cookies/index.mjs";
@@ -14,6 +15,7 @@ import { authorize } from "./authorize.mjs";
14
15
  import { schema } from "./schema.mjs";
15
16
  import { defaultClientSecretHasher } from "./utils.mjs";
16
17
  import { getCurrentAuthContext } from "@better-auth/core/context";
18
+ import { isSafeUrlScheme } from "@better-auth/core/utils/url";
17
19
  import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
18
20
  import { deprecate } from "@better-auth/core/utils/deprecate";
19
21
  import * as z from "zod";
@@ -52,11 +54,7 @@ const getMetadata = (ctx, options) => {
52
54
  const jwtPlugin = ctx.context.getPlugin("jwt");
53
55
  const issuer = jwtPlugin && jwtPlugin.options?.jwt && jwtPlugin.options.jwt.issuer ? jwtPlugin.options.jwt.issuer : ctx.context.options.baseURL;
54
56
  const baseURL = ctx.context.baseURL;
55
- const supportedAlgs = options?.useJWTPlugin ? [
56
- "RS256",
57
- "EdDSA",
58
- "none"
59
- ] : ["HS256", "none"];
57
+ const supportedAlgs = options?.useJWTPlugin ? ["RS256", "EdDSA"] : ["HS256"];
60
58
  return {
61
59
  issuer,
62
60
  authorization_endpoint: `${baseURL}/oauth2/authorize`,
@@ -104,7 +102,7 @@ const oAuthConsentBodySchema = z.object({
104
102
  });
105
103
  const oAuth2TokenBodySchema = z.record(z.any(), z.any());
106
104
  const registerOAuthApplicationBodySchema = z.object({
107
- redirect_uris: z.array(z.string()).meta({ description: "A list of redirect URIs. Eg: [\"https://client.example.com/callback\"]" }),
105
+ redirect_uris: z.array(z.string().refine(isSafeUrlScheme, { message: "redirect_uri cannot use a javascript:, data:, or vbscript: scheme" })).meta({ description: "A list of redirect URIs. Eg: [\"https://client.example.com/callback\"]" }),
108
106
  token_endpoint_auth_method: z.enum([
109
107
  "none",
110
108
  "client_secret_basic",
@@ -161,7 +159,7 @@ const oidcProvider = (options) => {
161
159
  defaultScope: "openid",
162
160
  accessTokenExpiresIn: DEFAULT_ACCESS_TOKEN_EXPIRES_IN,
163
161
  refreshTokenExpiresIn: DEFAULT_REFRESH_TOKEN_EXPIRES_IN,
164
- allowPlainCodeChallengeMethod: true,
162
+ allowPlainCodeChallengeMethod: false,
165
163
  storeClientSecret: "plain",
166
164
  ...options,
167
165
  scopes: [
@@ -190,14 +188,14 @@ const oidcProvider = (options) => {
190
188
  * Verify stored client secret against provided client secret
191
189
  */
192
190
  async function verifyStoredClientSecret(ctx, storedClientSecret, clientSecret) {
193
- if (opts.storeClientSecret === "encrypted") return await symmetricDecrypt({
191
+ if (opts.storeClientSecret === "encrypted") return constantTimeEqual(await symmetricDecrypt({
194
192
  key: ctx.context.secretConfig,
195
193
  data: storedClientSecret
196
- }) === clientSecret;
197
- if (opts.storeClientSecret === "hashed") return await defaultClientSecretHasher(clientSecret) === storedClientSecret;
198
- if (typeof opts.storeClientSecret === "object" && "hash" in opts.storeClientSecret) return await opts.storeClientSecret.hash(clientSecret) === storedClientSecret;
199
- if (typeof opts.storeClientSecret === "object" && "decrypt" in opts.storeClientSecret) return await opts.storeClientSecret.decrypt(storedClientSecret) === clientSecret;
200
- return clientSecret === storedClientSecret;
194
+ }), clientSecret);
195
+ if (opts.storeClientSecret === "hashed") return constantTimeEqual(await defaultClientSecretHasher(clientSecret), storedClientSecret);
196
+ if (typeof opts.storeClientSecret === "object" && "hash" in opts.storeClientSecret) return constantTimeEqual(await opts.storeClientSecret.hash(clientSecret), storedClientSecret);
197
+ if (typeof opts.storeClientSecret === "object" && "decrypt" in opts.storeClientSecret) return constantTimeEqual(await opts.storeClientSecret.decrypt(storedClientSecret), clientSecret);
198
+ return constantTimeEqual(clientSecret, storedClientSecret);
201
199
  }
202
200
  return {
203
201
  id: "oidc-provider",
@@ -378,25 +376,43 @@ const oidcProvider = (options) => {
378
376
  });
379
377
  let { client_id, client_secret } = body;
380
378
  const authorization = ctx.request?.headers.get("authorization") || null;
381
- if (authorization && !client_id && !client_secret && authorization.startsWith("Basic ")) try {
382
- const encoded = authorization.replace("Basic ", "");
383
- const decoded = new TextDecoder().decode(base64.decode(encoded));
384
- if (!decoded.includes(":")) throw new APIError("UNAUTHORIZED", {
379
+ if (authorization && !client_secret && authorization.startsWith("Basic ")) {
380
+ let decoded;
381
+ try {
382
+ const encoded = authorization.replace("Basic ", "");
383
+ decoded = new TextDecoder().decode(base64.decode(encoded));
384
+ } catch {
385
+ throw new APIError("UNAUTHORIZED", {
386
+ error_description: "invalid authorization header format",
387
+ error: "invalid_client"
388
+ });
389
+ }
390
+ const colonIndex = decoded.indexOf(":");
391
+ if (colonIndex === -1) throw new APIError("UNAUTHORIZED", {
385
392
  error_description: "invalid authorization header format",
386
393
  error: "invalid_client"
387
394
  });
388
- const [id, secret] = decoded.split(":");
395
+ let id;
396
+ let secret;
397
+ try {
398
+ id = decodeURIComponent(decoded.slice(0, colonIndex));
399
+ secret = decodeURIComponent(decoded.slice(colonIndex + 1));
400
+ } catch {
401
+ throw new APIError("UNAUTHORIZED", {
402
+ error_description: "invalid authorization header format",
403
+ error: "invalid_client"
404
+ });
405
+ }
389
406
  if (!id || !secret) throw new APIError("UNAUTHORIZED", {
390
407
  error_description: "invalid authorization header format",
391
408
  error: "invalid_client"
392
409
  });
393
- client_id = id;
394
- client_secret = secret;
395
- } catch {
396
- throw new APIError("UNAUTHORIZED", {
397
- error_description: "invalid authorization header format",
410
+ if (client_id && client_id.toString() !== id) throw new APIError("UNAUTHORIZED", {
411
+ error_description: "client_id in body does not match Authorization header",
398
412
  error: "invalid_client"
399
413
  });
414
+ client_id = id;
415
+ client_secret = secret;
400
416
  }
401
417
  const now = Date.now();
402
418
  const iat = Math.floor(now / 1e3);
@@ -428,6 +444,25 @@ const oidcProvider = (options) => {
428
444
  error_description: "refresh token expired",
429
445
  error: "invalid_grant"
430
446
  });
447
+ const refreshClient = await getClient(client_id.toString(), trustedClients);
448
+ if (!refreshClient) throw new APIError("UNAUTHORIZED", {
449
+ error_description: "invalid client_id",
450
+ error: "invalid_client"
451
+ });
452
+ if (refreshClient.disabled) throw new APIError("UNAUTHORIZED", {
453
+ error_description: "client is disabled",
454
+ error: "invalid_client"
455
+ });
456
+ if (refreshClient.type !== "public") {
457
+ if (!refreshClient.clientSecret || !client_secret) throw new APIError("UNAUTHORIZED", {
458
+ error_description: "client_secret is required for confidential clients",
459
+ error: "invalid_client"
460
+ });
461
+ if (!await verifyStoredClientSecret(ctx, refreshClient.clientSecret, client_secret.toString())) throw new APIError("UNAUTHORIZED", {
462
+ error_description: "invalid client_secret",
463
+ error: "invalid_client"
464
+ });
465
+ }
431
466
  const accessToken = generateRandomString(32, "a-z", "A-Z");
432
467
  const newRefreshToken = generateRandomString(32, "a-z", "A-Z");
433
468
  await ctx.context.adapter.create({
@@ -460,20 +495,11 @@ const oidcProvider = (options) => {
460
495
  error_description: "code verifier is missing",
461
496
  error: "invalid_request"
462
497
  });
463
- /**
464
- * We need to check if the code is valid before we can proceed
465
- * with the rest of the request.
466
- */
467
- const verificationValue = await ctx.context.internalAdapter.findVerificationValue(code.toString());
498
+ const verificationValue = await ctx.context.internalAdapter.consumeVerificationValue(code.toString());
468
499
  if (!verificationValue) throw new APIError("UNAUTHORIZED", {
469
500
  error_description: "invalid code",
470
501
  error: "invalid_grant"
471
502
  });
472
- if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("UNAUTHORIZED", {
473
- error_description: "code expired",
474
- error: "invalid_grant"
475
- });
476
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(code.toString());
477
503
  if (!client_id) throw new APIError("UNAUTHORIZED", {
478
504
  error_description: "client_id is required",
479
505
  error: "invalid_client"
@@ -527,12 +553,13 @@ const oidcProvider = (options) => {
527
553
  error: "invalid_client"
528
554
  });
529
555
  }
530
- if ((value.codeChallengeMethod === "plain" ? code_verifier : await createHash("SHA-256", "base64urlnopad").digest(code_verifier)) !== value.codeChallenge) throw new APIError("UNAUTHORIZED", {
531
- error_description: "code verification failed",
532
- error: "invalid_request"
533
- });
556
+ if (value.codeChallenge) {
557
+ if ((value.codeChallengeMethod === "plain" ? code_verifier : await createHash("SHA-256", "base64urlnopad").digest(code_verifier)) !== value.codeChallenge) throw new APIError("UNAUTHORIZED", {
558
+ error_description: "code verification failed",
559
+ error: "invalid_request"
560
+ });
561
+ }
534
562
  const requestedScopes = value.scope;
535
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(code.toString());
536
563
  const accessToken = generateRandomString(32, "a-z", "A-Z");
537
564
  const refreshToken = generateRandomString(32, "A-Z", "a-z");
538
565
  await ctx.context.adapter.create({
@@ -1,4 +1,5 @@
1
1
  import { PACKAGE_VERSION } from "../../version.mjs";
2
+ import { isSafeUrlScheme } from "@better-auth/core/utils/url";
2
3
  //#region src/plugins/one-tap/client.ts
3
4
  let isRequestInProgress = false;
4
5
  function isFedCMSupported() {
@@ -49,7 +50,10 @@ const oneTapClient = (options) => {
49
50
  ...opts?.fetchOptions,
50
51
  ...fetchOptions
51
52
  });
52
- if (!opts?.fetchOptions && !fetchOptions || opts?.callbackURL) window.location.href = opts?.callbackURL ?? "/";
53
+ if (!opts?.fetchOptions && !fetchOptions || opts?.callbackURL) {
54
+ const target = opts?.callbackURL ?? "/";
55
+ if (isSafeUrlScheme(target)) window.location.href = target;
56
+ }
53
57
  }
54
58
  const { autoSelect, cancelOnTapOutside, context } = opts ?? {};
55
59
  const contextValue = context ?? options.context ?? "signin";
@@ -82,7 +86,10 @@ const oneTapClient = (options) => {
82
86
  ...opts?.fetchOptions,
83
87
  ...fetchOptions
84
88
  });
85
- if (!opts?.fetchOptions && !fetchOptions || opts?.callbackURL) window.location.href = opts?.callbackURL ?? "/";
89
+ if (!opts?.fetchOptions && !fetchOptions || opts?.callbackURL) {
90
+ const target = opts?.callbackURL ?? "/";
91
+ if (isSafeUrlScheme(target)) window.location.href = target;
92
+ }
86
93
  }
87
94
  const { autoSelect, cancelOnTapOutside, context } = opts ?? {};
88
95
  const contextValue = context ?? options.context ?? "signin";