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.
- package/README.md +14 -15
- package/dist/_virtual/_rolldown/runtime.mjs +10 -1
- package/dist/api/index.d.mts +18 -48
- package/dist/api/routes/account.d.mts +4 -23
- package/dist/api/routes/account.mjs +102 -75
- package/dist/api/routes/callback.mjs +42 -20
- package/dist/api/routes/email-verification.d.mts +1 -0
- package/dist/api/routes/email-verification.mjs +6 -5
- package/dist/api/routes/error.mjs +1 -1
- package/dist/api/routes/password.mjs +1 -1
- package/dist/api/routes/session.mjs +15 -10
- package/dist/api/routes/sign-in.d.mts +3 -1
- package/dist/api/routes/sign-in.mjs +13 -15
- package/dist/api/routes/sign-up.d.mts +1 -0
- package/dist/api/routes/sign-up.mjs +10 -8
- package/dist/api/routes/update-user.mjs +8 -8
- package/dist/api/to-auth-endpoints.mjs +7 -1
- package/dist/auth/base.mjs +30 -27
- package/dist/client/config.mjs +8 -1
- package/dist/client/fetch-plugins.mjs +2 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/lynx/index.d.mts +13 -0
- package/dist/client/lynx/index.mjs +2 -1
- package/dist/client/parser.mjs +0 -1
- package/dist/client/plugins/index.d.mts +4 -17
- package/dist/client/plugins/index.mjs +1 -4
- package/dist/client/proxy.mjs +2 -1
- package/dist/client/react/index.d.mts +13 -0
- package/dist/client/react/index.mjs +2 -1
- package/dist/client/session-atom.mjs +12 -1
- package/dist/client/solid/index.d.mts +13 -0
- package/dist/client/solid/index.mjs +3 -2
- package/dist/client/svelte/index.d.mts +13 -0
- package/dist/client/svelte/index.mjs +2 -1
- package/dist/client/vanilla.d.mts +13 -0
- package/dist/client/vanilla.mjs +2 -1
- package/dist/client/vue/index.d.mts +13 -0
- package/dist/client/vue/index.mjs +2 -1
- package/dist/context/create-context.mjs +10 -14
- package/dist/context/helpers.mjs +3 -2
- package/dist/cookies/cookie-utils.d.mts +33 -1
- package/dist/cookies/cookie-utils.mjs +95 -14
- package/dist/cookies/index.d.mts +2 -3
- package/dist/cookies/index.mjs +39 -11
- package/dist/cookies/session-store.mjs +4 -23
- package/dist/db/get-migration.mjs +4 -4
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +3 -2
- package/dist/db/internal-adapter.mjs +129 -27
- package/dist/db/schema.d.mts +14 -1
- package/dist/db/schema.mjs +26 -1
- package/dist/db/with-hooks.d.mts +1 -0
- package/dist/db/with-hooks.mjs +58 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/integrations/cookie-plugin-guard.mjs +18 -0
- package/dist/integrations/next-js.mjs +6 -0
- package/dist/integrations/svelte-kit.mjs +6 -0
- package/dist/integrations/tanstack-start-solid.mjs +6 -0
- package/dist/integrations/tanstack-start.mjs +6 -0
- package/dist/node_modules/.pnpm/clipboardy@4.0.0/node_modules/clipboardy/index.mjs +32 -0
- package/dist/node_modules/.pnpm/clipboardy@4.0.0/node_modules/clipboardy/lib/linux.mjs +55 -0
- package/dist/node_modules/.pnpm/clipboardy@4.0.0/node_modules/clipboardy/lib/macos.mjs +26 -0
- package/dist/node_modules/.pnpm/clipboardy@4.0.0/node_modules/clipboardy/lib/termux.mjs +39 -0
- package/dist/node_modules/.pnpm/clipboardy@4.0.0/node_modules/clipboardy/lib/windows.mjs +19 -0
- package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/chunks/prompt.mjs +845 -0
- package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/core.mjs +386 -0
- package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/index.mjs +320 -0
- package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/shared/consola.DRwqZj3T.mjs +62 -0
- package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/shared/consola.DXBYu-KD.mjs +190 -0
- package/dist/node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/utils.mjs +2 -0
- package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.mjs +29 -0
- package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.mjs +42 -0
- package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.mjs +67 -0
- package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.mjs +23 -0
- package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.mjs +22 -0
- package/dist/node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.mjs +36 -0
- package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/adapters/node.d.mts +292 -0
- package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/adapters/node.mjs +127 -0
- package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/index.d.mts +145 -0
- package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/shared/crossws.BQXMA5bH.d.mts +298 -0
- package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/shared/crossws.By9qWDAI.mjs +9 -0
- package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/shared/crossws.CipVM6lf.mjs +3549 -0
- package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/shared/crossws.D9ehKjSh.mjs +66 -0
- package/dist/node_modules/.pnpm/crossws@0.3.5/node_modules/crossws/dist/shared/crossws.DfCzGthR.mjs +227 -0
- package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/index.mjs +224 -0
- package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/command.mjs +52 -0
- package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/error.mjs +54 -0
- package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/kill.mjs +62 -0
- package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/pipe.mjs +26 -0
- package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/promise.mjs +32 -0
- package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/stdio.mjs +19 -0
- package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/stream.mjs +98 -0
- package/dist/node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/verbose.mjs +15 -0
- package/dist/node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.d.mts +15 -0
- package/dist/node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.mjs +220 -0
- package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/array-buffer.mjs +54 -0
- package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/array.mjs +2 -0
- package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/buffer.mjs +14 -0
- package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/contents.mjs +76 -0
- package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/index.mjs +6 -0
- package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/string.mjs +35 -0
- package/dist/node_modules/.pnpm/get-stream@8.0.1/node_modules/get-stream/source/utils.mjs +10 -0
- package/dist/node_modules/.pnpm/http-shutdown@1.2.2/node_modules/http-shutdown/index.mjs +76 -0
- package/dist/node_modules/.pnpm/human-signals@5.0.0/node_modules/human-signals/build/src/core.mjs +274 -0
- package/dist/node_modules/.pnpm/human-signals@5.0.0/node_modules/human-signals/build/src/main.mjs +44 -0
- package/dist/node_modules/.pnpm/human-signals@5.0.0/node_modules/human-signals/build/src/realtime.mjs +15 -0
- package/dist/node_modules/.pnpm/human-signals@5.0.0/node_modules/human-signals/build/src/signals.mjs +23 -0
- package/dist/node_modules/.pnpm/is-docker@3.0.0/node_modules/is-docker/index.mjs +24 -0
- package/dist/node_modules/.pnpm/is-inside-container@1.0.0/node_modules/is-inside-container/index.mjs +18 -0
- package/dist/node_modules/.pnpm/is-stream@3.0.0/node_modules/is-stream/index.mjs +9 -0
- package/dist/node_modules/.pnpm/is-wsl@3.1.1/node_modules/is-wsl/index.mjs +20 -0
- package/dist/node_modules/.pnpm/is64bit@2.0.0/node_modules/is64bit/index.mjs +13 -0
- package/dist/node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.mjs +47 -0
- package/dist/node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.mjs +33 -0
- package/dist/node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.mjs +33 -0
- package/dist/node_modules/.pnpm/listhen@1.9.0/node_modules/listhen/dist/chunks/xdg-open.mjs +1070 -0
- package/dist/node_modules/.pnpm/listhen@1.9.0/node_modules/listhen/dist/index.mjs +619 -0
- package/dist/node_modules/.pnpm/listhen@1.9.0/node_modules/listhen/dist/shared/listhen.1c46e31d.d.mts +83 -0
- package/dist/node_modules/.pnpm/merge-stream@2.0.0/node_modules/merge-stream/index.mjs +38 -0
- package/dist/node_modules/.pnpm/mimic-fn@4.0.0/node_modules/mimic-fn/index.mjs +38 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/aes.mjs +597 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/aesCipherSuites.mjs +195 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/asn1-validator.mjs +76 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/asn1.mjs +967 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/baseN.mjs +137 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/cipher.mjs +186 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/cipherModes.mjs +597 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/des.mjs +1187 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/ed25519.mjs +1029 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/forge.mjs +15 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/hmac.mjs +107 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/index.mjs +66 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/jsbn.mjs +1334 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/kem.mjs +146 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/log.mjs +241 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/md.all.mjs +24 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/md.mjs +18 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/md5.mjs +324 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/mgf.mjs +20 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/mgf1.mjs +44 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/oids.mjs +154 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pbe.mjs +815 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pbkdf2.mjs +125 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pem.mjs +175 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pkcs1.mjs +200 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pkcs12.mjs +724 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pkcs7.mjs +642 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pkcs7asn1.mjs +405 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pki.mjs +101 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/prime.mjs +193 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/prng.mjs +290 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/pss.mjs +141 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/random.mjs +141 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/rc2.mjs +538 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/rsa.mjs +1309 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/sha1.mjs +230 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/sha256.mjs +267 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/sha512.mjs +413 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/ssh.mjs +194 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/tls.mjs +3655 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/util.mjs +2117 -0
- package/dist/node_modules/.pnpm/node-forge@1.4.0/node_modules/node-forge/lib/x509.mjs +2168 -0
- package/dist/node_modules/.pnpm/npm-run-path@5.3.0/node_modules/npm-run-path/index.mjs +34 -0
- package/dist/node_modules/.pnpm/onetime@6.0.0/node_modules/onetime/index.mjs +26 -0
- package/dist/node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.mjs +14 -0
- package/dist/node_modules/.pnpm/path-key@4.0.0/node_modules/path-key/index.mjs +8 -0
- package/dist/node_modules/.pnpm/pathe@1.1.2/node_modules/pathe/dist/shared/pathe.ff20891b.mjs +176 -0
- package/dist/node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.mjs +17 -0
- package/dist/node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.mjs +8 -0
- package/dist/node_modules/.pnpm/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/index.mjs +169 -0
- package/dist/node_modules/.pnpm/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/signals.mjs +33 -0
- package/dist/node_modules/.pnpm/std-env@3.10.0/node_modules/std-env/dist/index.mjs +171 -0
- package/dist/node_modules/.pnpm/strip-final-newline@3.0.0/node_modules/strip-final-newline/index.mjs +10 -0
- package/dist/node_modules/.pnpm/system-architecture@0.1.0/node_modules/system-architecture/index.mjs +16 -0
- package/dist/node_modules/.pnpm/uncrypto@0.1.3/node_modules/uncrypto/dist/crypto.node.mjs +7 -0
- package/dist/node_modules/.pnpm/untun@0.1.3/node_modules/untun/dist/chunks/index.mjs +154 -0
- package/dist/node_modules/.pnpm/untun@0.1.3/node_modules/untun/dist/index.mjs +34 -0
- package/dist/node_modules/.pnpm/uqr@0.1.2/node_modules/uqr/dist/index.mjs +896 -0
- package/dist/node_modules/.pnpm/which@2.0.2/node_modules/which/which.mjs +76 -0
- package/dist/oauth2/errors.mjs +27 -0
- package/dist/oauth2/index.d.mts +2 -2
- package/dist/oauth2/index.mjs +3 -3
- package/dist/oauth2/link-account.d.mts +27 -1
- package/dist/oauth2/link-account.mjs +30 -5
- package/dist/oauth2/state.mjs +8 -2
- package/dist/{package.mjs → packages/better-auth/package.mjs} +1 -1
- package/dist/plugins/access/access.d.mts +3 -15
- package/dist/plugins/access/access.mjs +11 -6
- package/dist/plugins/access/index.d.mts +2 -2
- package/dist/plugins/access/types.d.mts +11 -4
- package/dist/plugins/admin/access/statement.d.mts +29 -93
- package/dist/plugins/admin/admin.mjs +0 -4
- package/dist/plugins/admin/client.d.mts +6 -1
- package/dist/plugins/admin/client.mjs +5 -0
- package/dist/plugins/admin/routes.mjs +3 -2
- package/dist/plugins/anonymous/client.d.mts +1 -0
- package/dist/plugins/anonymous/error-codes.d.mts +1 -0
- package/dist/plugins/anonymous/error-codes.mjs +1 -0
- package/dist/plugins/anonymous/index.d.mts +1 -0
- package/dist/plugins/anonymous/index.mjs +16 -2
- package/dist/plugins/bearer/index.mjs +5 -12
- package/dist/plugins/captcha/index.mjs +14 -1
- package/dist/plugins/device-authorization/error-codes.mjs +1 -0
- package/dist/plugins/device-authorization/index.d.mts +1 -0
- package/dist/plugins/device-authorization/routes.mjs +34 -3
- package/dist/plugins/email-otp/routes.mjs +4 -4
- package/dist/plugins/generic-oauth/error-codes.mjs +0 -1
- package/dist/plugins/generic-oauth/index.d.mts +2 -4
- package/dist/plugins/generic-oauth/index.mjs +23 -17
- package/dist/plugins/generic-oauth/providers/auth0.mjs +1 -0
- package/dist/plugins/generic-oauth/providers/gumroad.mjs +1 -0
- package/dist/plugins/generic-oauth/providers/hubspot.mjs +1 -0
- package/dist/plugins/generic-oauth/providers/keycloak.mjs +1 -0
- package/dist/plugins/generic-oauth/providers/line.mjs +1 -0
- package/dist/plugins/generic-oauth/providers/microsoft-entra-id.mjs +1 -0
- package/dist/plugins/generic-oauth/providers/okta.mjs +1 -0
- package/dist/plugins/generic-oauth/providers/patreon.mjs +1 -0
- package/dist/plugins/generic-oauth/providers/slack.mjs +1 -0
- package/dist/plugins/generic-oauth/types.d.mts +30 -8
- package/dist/plugins/index.d.mts +2 -2
- package/dist/plugins/jwt/utils.d.mts +1 -1
- package/dist/plugins/last-login-method/client.mjs +2 -2
- package/dist/plugins/magic-link/index.d.mts +8 -1
- package/dist/plugins/magic-link/index.mjs +4 -17
- package/dist/plugins/mcp/authorize.mjs +8 -2
- package/dist/plugins/mcp/index.mjs +75 -35
- package/dist/plugins/multi-session/index.mjs +2 -2
- package/dist/plugins/oauth-proxy/index.mjs +45 -32
- package/dist/plugins/oauth-proxy/utils.mjs +3 -10
- package/dist/plugins/oidc-provider/authorize.mjs +8 -2
- package/dist/plugins/oidc-provider/index.mjs +65 -38
- package/dist/plugins/one-tap/client.mjs +9 -2
- package/dist/plugins/one-tap/index.mjs +19 -36
- package/dist/plugins/open-api/generator.mjs +25 -5
- package/dist/plugins/organization/access/statement.d.mts +68 -201
- package/dist/plugins/organization/adapter.d.mts +1 -1
- package/dist/plugins/organization/adapter.mjs +65 -58
- package/dist/plugins/organization/client.d.mts +3 -1
- package/dist/plugins/organization/client.mjs +1 -1
- package/dist/plugins/organization/error-codes.d.mts +2 -0
- package/dist/plugins/organization/error-codes.mjs +3 -1
- package/dist/plugins/organization/routes/crud-access-control.d.mts +2 -2
- package/dist/plugins/organization/routes/crud-invites.d.mts +8 -1
- package/dist/plugins/organization/routes/crud-invites.mjs +8 -3
- package/dist/plugins/organization/routes/crud-org.d.mts +4 -4
- package/dist/plugins/organization/routes/crud-org.mjs +2 -2
- package/dist/plugins/organization/routes/crud-team.mjs +7 -2
- package/dist/plugins/organization/schema.d.mts +1 -1
- package/dist/plugins/organization/types.d.mts +15 -5
- package/dist/plugins/phone-number/routes.mjs +1 -1
- package/dist/plugins/siwe/client.d.mts +4 -0
- package/dist/plugins/siwe/client.mjs +5 -1
- package/dist/plugins/siwe/index.d.mts +13 -2
- package/dist/plugins/siwe/index.mjs +179 -165
- package/dist/plugins/two-factor/backup-codes/index.d.mts +4 -3
- package/dist/plugins/two-factor/client.mjs +2 -1
- package/dist/plugins/two-factor/index.mjs +3 -2
- package/dist/plugins/username/client.d.mts +1 -0
- package/dist/plugins/username/error-codes.d.mts +1 -0
- package/dist/plugins/username/error-codes.mjs +2 -1
- package/dist/plugins/username/index.d.mts +43 -2
- package/dist/plugins/username/index.mjs +73 -6
- package/dist/state.d.mts +2 -2
- package/dist/state.mjs +18 -4
- package/dist/test-utils/headers.mjs +2 -7
- package/dist/test-utils/http-test-instance.d.mts +397 -0
- package/dist/test-utils/http-test-instance.mjs +54 -0
- package/dist/test-utils/index.d.mts +2 -1
- package/dist/test-utils/index.mjs +2 -1
- package/dist/test-utils/test-instance.d.mts +4110 -224
- package/dist/test-utils/test-instance.mjs +11 -2
- package/dist/types/auth.d.mts +4 -0
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/url.d.mts +2 -1
- package/dist/utils/url.mjs +9 -3
- package/dist/version.mjs +1 -1
- package/package.json +14 -14
- package/dist/oauth2/error-codes.d.mts +0 -20
- package/dist/plugins/generic-oauth/client.d.mts +0 -32
- package/dist/plugins/generic-oauth/client.mjs +0 -19
- 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"
|
|
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"
|
|
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:
|
|
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 && !
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
|
257
|
-
|
|
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
|
|
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 (
|
|
404
|
-
|
|
405
|
-
|
|
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,
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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:
|
|
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
|
|
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
|
|
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,
|
|
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.
|
|
97
|
-
if (
|
|
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:
|
|
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
|
-
})
|
|
197
|
-
if (opts.storeClientSecret === "hashed") return await defaultClientSecretHasher(clientSecret)
|
|
198
|
-
if (typeof opts.storeClientSecret === "object" && "hash" in opts.storeClientSecret) return await opts.storeClientSecret.hash(clientSecret)
|
|
199
|
-
if (typeof opts.storeClientSecret === "object" && "decrypt" in opts.storeClientSecret) return await opts.storeClientSecret.decrypt(storedClientSecret)
|
|
200
|
-
return clientSecret
|
|
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 && !
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
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
|
|
394
|
-
|
|
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 (
|
|
531
|
-
|
|
532
|
-
|
|
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)
|
|
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)
|
|
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";
|