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
|
@@ -101,6 +101,12 @@ const anonymous = (options) => {
|
|
|
101
101
|
const session = ctx.context.session;
|
|
102
102
|
if (options?.disableDeleteAnonymousUser) throw APIError.from("BAD_REQUEST", ANONYMOUS_ERROR_CODES.DELETE_ANONYMOUS_USER_DISABLED);
|
|
103
103
|
if (!session.user.isAnonymous) throw APIError.from("FORBIDDEN", ANONYMOUS_ERROR_CODES.USER_IS_NOT_ANONYMOUS);
|
|
104
|
+
try {
|
|
105
|
+
await ctx.context.internalAdapter.deleteUserSessions(session.user.id);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
ctx.context.logger.error("Failed to delete anonymous user sessions", error);
|
|
108
|
+
throw APIError.from("INTERNAL_SERVER_ERROR", ANONYMOUS_ERROR_CODES.FAILED_TO_DELETE_ANONYMOUS_USER_SESSIONS);
|
|
109
|
+
}
|
|
104
110
|
try {
|
|
105
111
|
await ctx.context.internalAdapter.deleteUser(session.user.id);
|
|
106
112
|
} catch (error) {
|
|
@@ -113,7 +119,7 @@ const anonymous = (options) => {
|
|
|
113
119
|
},
|
|
114
120
|
hooks: { after: [{
|
|
115
121
|
matcher(ctx) {
|
|
116
|
-
return ctx.path?.startsWith("/sign-in") || ctx.path?.startsWith("/sign-up") || ctx.path?.startsWith("/callback") || ctx.path?.startsWith("/magic-link/verify") || ctx.path?.startsWith("/email-otp/verify-email") || ctx.path?.startsWith("/one-tap/callback") || ctx.path?.startsWith("/passkey/verify-authentication") || ctx.path?.startsWith("/phone-number/verify") || false;
|
|
122
|
+
return ctx.path?.startsWith("/sign-in") || ctx.path?.startsWith("/sign-up") || ctx.path?.startsWith("/callback") || ctx.path?.startsWith("/magic-link/verify") || ctx.path?.startsWith("/email-otp/verify-email") || ctx.path?.startsWith("/one-tap/callback") || ctx.path?.startsWith("/passkey/verify-authentication") || ctx.path?.startsWith("/phone-number/verify") || ctx.path?.startsWith("/verify-email") || false;
|
|
117
123
|
},
|
|
118
124
|
handler: createAuthMiddleware(async (ctx) => {
|
|
119
125
|
const setCookie = ctx.context.responseHeaders?.get("set-cookie");
|
|
@@ -147,7 +153,15 @@ const anonymous = (options) => {
|
|
|
147
153
|
const isSameUser = newSessionUser?.id === session.user.id;
|
|
148
154
|
const newSessionIsAnonymous = Boolean(newSessionUser?.isAnonymous);
|
|
149
155
|
if (options?.disableDeleteAnonymousUser || isSameUser || newSessionIsAnonymous) return;
|
|
150
|
-
|
|
156
|
+
try {
|
|
157
|
+
await ctx.context.internalAdapter.deleteUserSessions(session.user.id);
|
|
158
|
+
await ctx.context.internalAdapter.deleteUser(session.user.id);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
ctx.context.logger.error("Failed to clean up anonymous user during post-link cleanup", {
|
|
161
|
+
anonymousUserId: session.user.id,
|
|
162
|
+
error
|
|
163
|
+
});
|
|
164
|
+
}
|
|
151
165
|
})
|
|
152
166
|
}] },
|
|
153
167
|
options,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
|
|
1
|
+
import { parseSetCookieHeader, setRequestCookie } from "../../cookies/cookie-utils.mjs";
|
|
2
2
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
3
3
|
import { serializeSignedCookie } from "better-call";
|
|
4
4
|
import { createAuthMiddleware } from "@better-auth/core/api";
|
|
@@ -30,16 +30,11 @@ const bearer = (options) => {
|
|
|
30
30
|
if (authHeader.slice(0, 7).toLowerCase() !== BEARER_SCHEME) return;
|
|
31
31
|
const token = authHeader.slice(7).trim();
|
|
32
32
|
if (!token) return;
|
|
33
|
-
let signedToken;
|
|
34
33
|
let decodedToken;
|
|
35
|
-
if (token.includes("."))
|
|
36
|
-
|
|
37
|
-
signedToken = isEncoded ? token : encodeURIComponent(token);
|
|
38
|
-
decodedToken = isEncoded ? tryDecode(token) : token;
|
|
39
|
-
} else {
|
|
34
|
+
if (token.includes(".")) decodedToken = token.includes("%") ? tryDecode(token) : token;
|
|
35
|
+
else {
|
|
40
36
|
if (options?.requireSignature) return;
|
|
41
|
-
|
|
42
|
-
decodedToken = tryDecode(signedToken);
|
|
37
|
+
decodedToken = tryDecode((await serializeSignedCookie("", token, c.context.secret)).replace("=", ""));
|
|
43
38
|
}
|
|
44
39
|
try {
|
|
45
40
|
if (!await createHMAC("SHA-256", "base64urlnopad").verify(c.context.secret, decodedToken.split(".")[0], decodedToken.split(".")[1])) return;
|
|
@@ -48,9 +43,7 @@ const bearer = (options) => {
|
|
|
48
43
|
}
|
|
49
44
|
const existingHeaders = c.request?.headers || c.headers;
|
|
50
45
|
const headers = new Headers({ ...Object.fromEntries(existingHeaders?.entries()) });
|
|
51
|
-
|
|
52
|
-
const newCookie = `${c.context.authCookies.sessionToken.name}=${signedToken}`;
|
|
53
|
-
headers.set("cookie", existingCookie ? `${existingCookie}; ${newCookie}` : newCookie);
|
|
46
|
+
setRequestCookie(headers, c.context.authCookies.sessionToken.name, decodedToken);
|
|
54
47
|
return { context: { headers } };
|
|
55
48
|
})
|
|
56
49
|
}],
|
|
@@ -14,7 +14,20 @@ const captcha = (options) => ({
|
|
|
14
14
|
$ERROR_CODES: EXTERNAL_ERROR_CODES,
|
|
15
15
|
onRequest: async (request, ctx) => {
|
|
16
16
|
try {
|
|
17
|
-
|
|
17
|
+
const endpoints = options.endpoints?.length ? options.endpoints : defaultEndpoints;
|
|
18
|
+
const url = new URL(request.url);
|
|
19
|
+
const basePath = ctx.options.basePath ?? "/api/auth";
|
|
20
|
+
let pathname = url.pathname.replace(basePath, "");
|
|
21
|
+
if (pathname.endsWith("//")) pathname = pathname.slice(0, -1);
|
|
22
|
+
if (pathname.startsWith("//")) pathname = pathname.slice(1);
|
|
23
|
+
if (!pathname.startsWith("/")) pathname = "/" + pathname;
|
|
24
|
+
const exemptPaths = ["/sign-in/email-otp"].reduce((acc, curr) => {
|
|
25
|
+
if (options.endpoints?.length && options.endpoints.includes(curr)) return acc;
|
|
26
|
+
return [...acc, curr];
|
|
27
|
+
}, []);
|
|
28
|
+
if (!endpoints.some((endpoint) => {
|
|
29
|
+
return pathname.includes(endpoint) && !exemptPaths.some((p) => pathname.includes(p));
|
|
30
|
+
})) return;
|
|
18
31
|
if (!options.secretKey) throw new Error(INTERNAL_ERROR_CODES.MISSING_SECRET_KEY.message);
|
|
19
32
|
const captchaResponse = request.headers.get("x-captcha-response");
|
|
20
33
|
const remoteUserIP = getIp(request, ctx.options) ?? void 0;
|
|
@@ -8,6 +8,7 @@ const DEVICE_AUTHORIZATION_ERROR_CODES = defineErrorCodes({
|
|
|
8
8
|
ACCESS_DENIED: "Access denied",
|
|
9
9
|
INVALID_USER_CODE: "Invalid user code",
|
|
10
10
|
DEVICE_CODE_ALREADY_PROCESSED: "Device code already processed",
|
|
11
|
+
DEVICE_CODE_NOT_CLAIMED: "Device code has not been claimed by a verifying session; call `GET /device` with the `user_code` while signed in before approving or denying",
|
|
11
12
|
POLLING_TOO_FREQUENTLY: "Polling too frequently",
|
|
12
13
|
USER_NOT_FOUND: "User not found",
|
|
13
14
|
FAILED_TO_CREATE_SESSION: "Failed to create session",
|
|
@@ -388,6 +388,7 @@ declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions
|
|
|
388
388
|
ACCESS_DENIED: _better_auth_core_utils_error_codes0.RawError<"ACCESS_DENIED">;
|
|
389
389
|
INVALID_USER_CODE: _better_auth_core_utils_error_codes0.RawError<"INVALID_USER_CODE">;
|
|
390
390
|
DEVICE_CODE_ALREADY_PROCESSED: _better_auth_core_utils_error_codes0.RawError<"DEVICE_CODE_ALREADY_PROCESSED">;
|
|
391
|
+
DEVICE_CODE_NOT_CLAIMED: _better_auth_core_utils_error_codes0.RawError<"DEVICE_CODE_NOT_CLAIMED">;
|
|
391
392
|
POLLING_TOO_FREQUENTLY: _better_auth_core_utils_error_codes0.RawError<"POLLING_TOO_FREQUENTLY">;
|
|
392
393
|
INVALID_DEVICE_CODE_STATUS: _better_auth_core_utils_error_codes0.RawError<"INVALID_DEVICE_CODE_STATUS">;
|
|
393
394
|
AUTHENTICATION_REQUIRED: _better_auth_core_utils_error_codes0.RawError<"AUTHENTICATION_REQUIRED">;
|
|
@@ -99,6 +99,7 @@ Follow [rfc8628#section-3.2](https://datatracker.ietf.org/doc/html/rfc8628#secti
|
|
|
99
99
|
data: {
|
|
100
100
|
deviceCode,
|
|
101
101
|
userCode,
|
|
102
|
+
userId: null,
|
|
102
103
|
expiresAt,
|
|
103
104
|
status: "pending",
|
|
104
105
|
pollingInterval: ms(opts.interval),
|
|
@@ -331,6 +332,28 @@ const deviceVerify = createAuthEndpoint("/device", {
|
|
|
331
332
|
error: "expired_token",
|
|
332
333
|
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.EXPIRED_USER_CODE.message
|
|
333
334
|
});
|
|
335
|
+
const session = await getSessionFromCtx(ctx);
|
|
336
|
+
if (session?.user?.id && !deviceCodeRecord.userId && deviceCodeRecord.status === "pending") {
|
|
337
|
+
if (await ctx.context.adapter.update({
|
|
338
|
+
model: "deviceCode",
|
|
339
|
+
where: [
|
|
340
|
+
{
|
|
341
|
+
field: "id",
|
|
342
|
+
value: deviceCodeRecord.id
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
field: "status",
|
|
346
|
+
value: "pending"
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
field: "userId",
|
|
350
|
+
operator: "eq",
|
|
351
|
+
value: null
|
|
352
|
+
}
|
|
353
|
+
],
|
|
354
|
+
update: { userId: session.user.id }
|
|
355
|
+
})) deviceCodeRecord.userId = session.user.id;
|
|
356
|
+
}
|
|
334
357
|
return ctx.json({
|
|
335
358
|
user_code,
|
|
336
359
|
status: deviceCodeRecord.status
|
|
@@ -387,7 +410,11 @@ const deviceApprove = createAuthEndpoint("/device/approve", {
|
|
|
387
410
|
error: "invalid_request",
|
|
388
411
|
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_ALREADY_PROCESSED.message
|
|
389
412
|
});
|
|
390
|
-
if (deviceCodeRecord.userId
|
|
413
|
+
if (!deviceCodeRecord.userId) throw new APIError("BAD_REQUEST", {
|
|
414
|
+
error: "invalid_request",
|
|
415
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_NOT_CLAIMED.message
|
|
416
|
+
});
|
|
417
|
+
if (deviceCodeRecord.userId !== session.user.id) throw new APIError("FORBIDDEN", {
|
|
391
418
|
error: "access_denied",
|
|
392
419
|
error_description: "You are not authorized to approve this device authorization"
|
|
393
420
|
});
|
|
@@ -454,7 +481,11 @@ const deviceDeny = createAuthEndpoint("/device/deny", {
|
|
|
454
481
|
error: "invalid_request",
|
|
455
482
|
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_ALREADY_PROCESSED.message
|
|
456
483
|
});
|
|
457
|
-
if (deviceCodeRecord.userId
|
|
484
|
+
if (!deviceCodeRecord.userId) throw new APIError("BAD_REQUEST", {
|
|
485
|
+
error: "invalid_request",
|
|
486
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_NOT_CLAIMED.message
|
|
487
|
+
});
|
|
488
|
+
if (deviceCodeRecord.userId !== session.user.id) throw new APIError("FORBIDDEN", {
|
|
458
489
|
error: "access_denied",
|
|
459
490
|
error_description: "You are not authorized to deny this device authorization"
|
|
460
491
|
});
|
|
@@ -466,7 +497,7 @@ const deviceDeny = createAuthEndpoint("/device/deny", {
|
|
|
466
497
|
}],
|
|
467
498
|
update: {
|
|
468
499
|
status: "denied",
|
|
469
|
-
userId:
|
|
500
|
+
userId: session.user.id
|
|
470
501
|
}
|
|
471
502
|
});
|
|
472
503
|
return ctx.json({ success: true });
|
|
@@ -465,7 +465,7 @@ const requestPasswordResetEmailOTP = (opts) => createAuthEndpoint("/email-otp/re
|
|
|
465
465
|
} }
|
|
466
466
|
} }
|
|
467
467
|
}, async (ctx) => {
|
|
468
|
-
const email = ctx.body.email;
|
|
468
|
+
const email = ctx.body.email.toLowerCase();
|
|
469
469
|
const identifier = toOTPIdentifier("forget-password", email);
|
|
470
470
|
const otp = await resolveOTP(ctx, opts, email, "forget-password");
|
|
471
471
|
if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
|
|
@@ -517,7 +517,7 @@ const forgetPasswordEmailOTP = (opts) => {
|
|
|
517
517
|
} }
|
|
518
518
|
}, async (ctx) => {
|
|
519
519
|
warnDeprecation();
|
|
520
|
-
const email = ctx.body.email;
|
|
520
|
+
const email = ctx.body.email.toLowerCase();
|
|
521
521
|
const identifier = toOTPIdentifier("forget-password", email);
|
|
522
522
|
const otp = await resolveOTP(ctx, opts, email, "forget-password");
|
|
523
523
|
if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
|
|
@@ -567,7 +567,7 @@ const resetPasswordEmailOTP = (opts) => createAuthEndpoint("/email-otp/reset-pas
|
|
|
567
567
|
} }
|
|
568
568
|
} }
|
|
569
569
|
}, async (ctx) => {
|
|
570
|
-
const email = ctx.body.email;
|
|
570
|
+
const email = ctx.body.email.toLowerCase();
|
|
571
571
|
await atomicVerifyOTP(ctx, opts, toOTPIdentifier("forget-password", email), ctx.body.otp);
|
|
572
572
|
const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
|
|
573
573
|
if (!user) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
|
|
@@ -585,7 +585,7 @@ const resetPasswordEmailOTP = (opts) => createAuthEndpoint("/email-otp/reset-pas
|
|
|
585
585
|
else await ctx.context.internalAdapter.updatePassword(user.user.id, passwordHash);
|
|
586
586
|
if (ctx.context.options.emailAndPassword?.onPasswordReset) await ctx.context.options.emailAndPassword.onPasswordReset({ user: user.user }, ctx.request);
|
|
587
587
|
if (!user.user.emailVerified) await ctx.context.internalAdapter.updateUser(user.user.id, { emailVerified: true });
|
|
588
|
-
if (ctx.context.options.emailAndPassword?.revokeSessionsOnPasswordReset) await ctx.context.internalAdapter.
|
|
588
|
+
if (ctx.context.options.emailAndPassword?.revokeSessionsOnPasswordReset) await ctx.context.internalAdapter.deleteUserSessions(user.user.id);
|
|
589
589
|
return ctx.json({ success: true });
|
|
590
590
|
});
|
|
591
591
|
const requestEmailChangeEmailOTPBodySchema = z.object({
|
|
@@ -23,11 +23,9 @@ declare module "@better-auth/core" {
|
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Base type for OAuth provider options.
|
|
26
|
-
* Extracts common fields from GenericOAuthConfig
|
|
26
|
+
* Extracts common fields from GenericOAuthConfig for provider helpers.
|
|
27
27
|
*/
|
|
28
|
-
type BaseOAuthProviderOptions =
|
|
29
|
-
/** OAuth client secret (required for provider options) */clientSecret: string;
|
|
30
|
-
};
|
|
28
|
+
type BaseOAuthProviderOptions = Pick<GenericOAuthConfig, "clientId" | "clientSecret" | "tokenEndpointAuth" | "scopes" | "redirectURI" | "pkce" | "disableImplicitSignUp" | "disableSignUp" | "overrideUserInfo">;
|
|
31
29
|
/**
|
|
32
30
|
* A generic OAuth plugin that registers any OAuth/OIDC provider
|
|
33
31
|
* as a first-class social provider.
|
|
@@ -10,16 +10,15 @@ import { okta } from "./providers/okta.mjs";
|
|
|
10
10
|
import { patreon } from "./providers/patreon.mjs";
|
|
11
11
|
import { slack } from "./providers/slack.mjs";
|
|
12
12
|
import { APIError } from "@better-auth/core/error";
|
|
13
|
-
import { createAuthorizationURL, refreshAccessToken, validateAuthorizationCode } from "@better-auth/core/oauth2";
|
|
13
|
+
import { applyDefaultAccessTokenExpiry, createAuthorizationURL, refreshAccessToken, validateAuthorizationCode } from "@better-auth/core/oauth2";
|
|
14
14
|
import { decodeJwt } from "jose";
|
|
15
15
|
import { betterFetch } from "@better-fetch/fetch";
|
|
16
16
|
//#region src/plugins/generic-oauth/index.ts
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
17
|
+
function isSecretlessTokenEndpointAuth(tokenEndpointAuth) {
|
|
18
|
+
return tokenEndpointAuth?.method === "private_key_jwt" || tokenEndpointAuth?.method === "none";
|
|
19
|
+
}
|
|
20
|
+
function isClientSecretTokenEndpointAuth(tokenEndpointAuth) {
|
|
21
|
+
return tokenEndpointAuth?.method === "client_secret_basic" || tokenEndpointAuth?.method === "client_secret_post";
|
|
23
22
|
}
|
|
24
23
|
async function fetchDiscovery(url, headers) {
|
|
25
24
|
const result = await betterFetch(url, {
|
|
@@ -100,11 +99,15 @@ const genericOAuth = (options) => {
|
|
|
100
99
|
isOidc = Array.isArray(discovered.id_token_signing_alg_values_supported) && discovered.id_token_signing_alg_values_supported.length > 0;
|
|
101
100
|
} else if (!authorizationUrl || !tokenUrl) ctx.logger.error(`Provider "${c.providerId}": discovery returned no data and no explicit endpoints configured. OAuth sign-in will fail for this provider.`);
|
|
102
101
|
}
|
|
103
|
-
|
|
102
|
+
const tokenEndpointAuth = c.tokenEndpointAuth;
|
|
103
|
+
if (c.clientSecret && isSecretlessTokenEndpointAuth(tokenEndpointAuth)) throw new Error(`Provider "${c.providerId}": tokenEndpointAuth.method "${tokenEndpointAuth?.method}" cannot be combined with clientSecret`);
|
|
104
|
+
if (!c.clientSecret && isClientSecretTokenEndpointAuth(tokenEndpointAuth)) throw new Error(`Provider "${c.providerId}": tokenEndpointAuth.method "${tokenEndpointAuth?.method}" requires clientSecret`);
|
|
105
|
+
if (!c.clientSecret && !tokenEndpointAuth && c.authentication === "basic") throw new Error(`Provider "${c.providerId}": authentication "basic" requires clientSecret`);
|
|
104
106
|
genericProviders.push({
|
|
105
107
|
id: c.providerId,
|
|
106
108
|
name: c.name ?? c.providerId,
|
|
107
109
|
issuer,
|
|
110
|
+
allowIdpInitiated: c.allowIdpInitiated,
|
|
108
111
|
createAuthorizationURL(data) {
|
|
109
112
|
if (!authorizationUrl) throw APIError.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.INVALID_OAUTH_CONFIGURATION);
|
|
110
113
|
return createAuthorizationURL({
|
|
@@ -127,14 +130,17 @@ const genericOAuth = (options) => {
|
|
|
127
130
|
accessType: c.accessType,
|
|
128
131
|
responseType: c.responseType,
|
|
129
132
|
responseMode: c.responseMode,
|
|
130
|
-
additionalParams:
|
|
133
|
+
additionalParams: {
|
|
134
|
+
...c.authorizationUrlParams ?? {},
|
|
135
|
+
...data.additionalParams ?? {}
|
|
136
|
+
},
|
|
131
137
|
loginHint: data.loginHint
|
|
132
138
|
});
|
|
133
139
|
},
|
|
134
140
|
async validateAuthorizationCode(data) {
|
|
135
|
-
if (c.getToken) return c.getToken(data);
|
|
141
|
+
if (c.getToken) return applyDefaultAccessTokenExpiry(await c.getToken(data), c.accessTokenExpiresIn);
|
|
136
142
|
if (!tokenUrl) throw APIError.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.TOKEN_URL_NOT_FOUND);
|
|
137
|
-
return validateAuthorizationCode({
|
|
143
|
+
return applyDefaultAccessTokenExpiry(await validateAuthorizationCode({
|
|
138
144
|
headers: c.authorizationHeaders,
|
|
139
145
|
code: data.code,
|
|
140
146
|
codeVerifier: c.pkce ?? true ? data.codeVerifier : void 0,
|
|
@@ -146,9 +152,9 @@ const genericOAuth = (options) => {
|
|
|
146
152
|
},
|
|
147
153
|
tokenEndpoint: tokenUrl,
|
|
148
154
|
authentication: c.authentication,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
});
|
|
155
|
+
tokenEndpointAuth,
|
|
156
|
+
additionalParams: c.tokenUrlParams
|
|
157
|
+
}), c.accessTokenExpiresIn);
|
|
152
158
|
},
|
|
153
159
|
async getUserInfo(tokens) {
|
|
154
160
|
const raw = c.getUserInfo ? await c.getUserInfo(tokens) : await fetchUserInfo(tokens, userInfoUrl);
|
|
@@ -172,16 +178,16 @@ const genericOAuth = (options) => {
|
|
|
172
178
|
},
|
|
173
179
|
async refreshAccessToken(refreshToken) {
|
|
174
180
|
if (!tokenUrl) throw APIError.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.TOKEN_URL_NOT_FOUND);
|
|
175
|
-
return refreshAccessToken({
|
|
181
|
+
return applyDefaultAccessTokenExpiry(await refreshAccessToken({
|
|
176
182
|
refreshToken,
|
|
177
183
|
options: {
|
|
178
184
|
clientId: c.clientId,
|
|
179
185
|
clientSecret: c.clientSecret
|
|
180
186
|
},
|
|
181
187
|
authentication: c.authentication,
|
|
182
|
-
|
|
188
|
+
tokenEndpointAuth,
|
|
183
189
|
tokenEndpoint: tokenUrl
|
|
184
|
-
});
|
|
190
|
+
}), c.accessTokenExpiresIn);
|
|
185
191
|
},
|
|
186
192
|
disableImplicitSignUp: c.disableImplicitSignUp,
|
|
187
193
|
disableSignUp: c.disableSignUp,
|
|
@@ -27,6 +27,7 @@ function auth0(options) {
|
|
|
27
27
|
discoveryUrl: `https://${options.domain.replace(/^https?:\/\//, "")}/.well-known/openid-configuration`,
|
|
28
28
|
clientId: options.clientId,
|
|
29
29
|
clientSecret: options.clientSecret,
|
|
30
|
+
tokenEndpointAuth: options.tokenEndpointAuth,
|
|
30
31
|
scopes: options.scopes ?? [
|
|
31
32
|
"openid",
|
|
32
33
|
"profile",
|
|
@@ -44,6 +44,7 @@ function gumroad(options) {
|
|
|
44
44
|
tokenUrl: "https://api.gumroad.com/oauth/token",
|
|
45
45
|
clientId: options.clientId,
|
|
46
46
|
clientSecret: options.clientSecret,
|
|
47
|
+
tokenEndpointAuth: options.tokenEndpointAuth,
|
|
47
48
|
scopes: options.scopes ?? ["view_profile"],
|
|
48
49
|
redirectURI: options.redirectURI,
|
|
49
50
|
pkce: options.pkce,
|
|
@@ -43,6 +43,7 @@ function hubspot(options) {
|
|
|
43
43
|
tokenUrl: "https://api.hubapi.com/oauth/v1/token",
|
|
44
44
|
clientId: options.clientId,
|
|
45
45
|
clientSecret: options.clientSecret,
|
|
46
|
+
tokenEndpointAuth: options.tokenEndpointAuth,
|
|
46
47
|
scopes: options.scopes ?? defaultScopes,
|
|
47
48
|
redirectURI: options.redirectURI,
|
|
48
49
|
authentication: "post",
|
|
@@ -27,6 +27,7 @@ function keycloak(options) {
|
|
|
27
27
|
discoveryUrl: `${options.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`,
|
|
28
28
|
clientId: options.clientId,
|
|
29
29
|
clientSecret: options.clientSecret,
|
|
30
|
+
tokenEndpointAuth: options.tokenEndpointAuth,
|
|
30
31
|
scopes: options.scopes ?? [
|
|
31
32
|
"openid",
|
|
32
33
|
"profile",
|
|
@@ -75,6 +75,7 @@ function line(options) {
|
|
|
75
75
|
userInfoUrl,
|
|
76
76
|
clientId: options.clientId,
|
|
77
77
|
clientSecret: options.clientSecret,
|
|
78
|
+
tokenEndpointAuth: options.tokenEndpointAuth,
|
|
78
79
|
scopes: options.scopes ?? defaultScopes,
|
|
79
80
|
redirectURI: options.redirectURI,
|
|
80
81
|
pkce: options.pkce,
|
|
@@ -50,6 +50,7 @@ function microsoftEntraId(options) {
|
|
|
50
50
|
userInfoUrl,
|
|
51
51
|
clientId: options.clientId,
|
|
52
52
|
clientSecret: options.clientSecret,
|
|
53
|
+
tokenEndpointAuth: options.tokenEndpointAuth,
|
|
53
54
|
scopes: options.scopes ?? defaultScopes,
|
|
54
55
|
redirectURI: options.redirectURI,
|
|
55
56
|
pkce: options.pkce,
|
|
@@ -27,6 +27,7 @@ function okta(options) {
|
|
|
27
27
|
discoveryUrl: `${options.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`,
|
|
28
28
|
clientId: options.clientId,
|
|
29
29
|
clientSecret: options.clientSecret,
|
|
30
|
+
tokenEndpointAuth: options.tokenEndpointAuth,
|
|
30
31
|
scopes: options.scopes ?? [
|
|
31
32
|
"openid",
|
|
32
33
|
"profile",
|
|
@@ -43,6 +43,7 @@ function patreon(options) {
|
|
|
43
43
|
tokenUrl: "https://www.patreon.com/api/oauth2/token",
|
|
44
44
|
clientId: options.clientId,
|
|
45
45
|
clientSecret: options.clientSecret,
|
|
46
|
+
tokenEndpointAuth: options.tokenEndpointAuth,
|
|
46
47
|
scopes: options.scopes ?? defaultScopes,
|
|
47
48
|
redirectURI: options.redirectURI,
|
|
48
49
|
pkce: options.pkce,
|
|
@@ -45,6 +45,7 @@ function slack(options) {
|
|
|
45
45
|
userInfoUrl: "https://slack.com/api/openid.connect.userInfo",
|
|
46
46
|
clientId: options.clientId,
|
|
47
47
|
clientSecret: options.clientSecret,
|
|
48
|
+
tokenEndpointAuth: options.tokenEndpointAuth,
|
|
48
49
|
scopes: options.scopes ?? defaultScopes,
|
|
49
50
|
redirectURI: options.redirectURI,
|
|
50
51
|
pkce: options.pkce,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { User } from "@better-auth/core/db";
|
|
2
|
-
import {
|
|
2
|
+
import { OAuth2Tokens, OAuth2UserInfo, TokenEndpointAuth } from "@better-auth/core/oauth2";
|
|
3
3
|
|
|
4
4
|
//#region src/plugins/generic-oauth/types.d.ts
|
|
5
5
|
interface GenericOAuthOptions<ID extends string = string> {
|
|
@@ -43,6 +43,14 @@ interface GenericOAuthConfig<ID extends string = string> {
|
|
|
43
43
|
clientId: string;
|
|
44
44
|
/** OAuth client secret */
|
|
45
45
|
clientSecret?: string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Token endpoint client authentication method.
|
|
48
|
+
*
|
|
49
|
+
* Use `private_key_jwt` for IdPs that authenticate clients with RFC 7523
|
|
50
|
+
* client assertions instead of a client secret. Secret-based methods require
|
|
51
|
+
* clientSecret.
|
|
52
|
+
*/
|
|
53
|
+
tokenEndpointAuth?: TokenEndpointAuth | undefined;
|
|
46
54
|
/**
|
|
47
55
|
* Array of OAuth scopes to request.
|
|
48
56
|
* @default []
|
|
@@ -79,6 +87,13 @@ interface GenericOAuthConfig<ID extends string = string> {
|
|
|
79
87
|
* Use "offline" to request a refresh token.
|
|
80
88
|
*/
|
|
81
89
|
accessType?: string | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Fallback access-token lifetime, in seconds, used only when the provider's
|
|
92
|
+
* token response omits `expires_in`. Set this so `getAccessToken` can track
|
|
93
|
+
* expiry and refresh the token; leave unset if the provider returns
|
|
94
|
+
* `expires_in`.
|
|
95
|
+
*/
|
|
96
|
+
accessTokenExpiresIn?: number | undefined;
|
|
82
97
|
/**
|
|
83
98
|
* Custom function to exchange authorization code for tokens.
|
|
84
99
|
* If provided, this function will be used instead of the default token exchange logic.
|
|
@@ -111,7 +126,9 @@ interface GenericOAuthConfig<ID extends string = string> {
|
|
|
111
126
|
authorizationUrlParams?: Record<string, string> | undefined;
|
|
112
127
|
/**
|
|
113
128
|
* Additional search-params to add to the tokenUrl.
|
|
114
|
-
*
|
|
129
|
+
* Parameters already set by Better Auth are preserved. Configure token
|
|
130
|
+
* endpoint client authentication with clientId, clientSecret, and
|
|
131
|
+
* tokenEndpointAuth.
|
|
115
132
|
*/
|
|
116
133
|
tokenUrlParams?: Record<string, string> | undefined;
|
|
117
134
|
/**
|
|
@@ -125,14 +142,10 @@ interface GenericOAuthConfig<ID extends string = string> {
|
|
|
125
142
|
disableSignUp?: boolean | undefined;
|
|
126
143
|
/**
|
|
127
144
|
* Authentication method for token requests.
|
|
145
|
+
* "basic" requires clientSecret.
|
|
128
146
|
* @default "post"
|
|
129
147
|
*/
|
|
130
|
-
authentication?: ("basic" | "post"
|
|
131
|
-
/**
|
|
132
|
-
* Client assertion config for `private_key_jwt` authentication.
|
|
133
|
-
* Required when `authentication` is `"private_key_jwt"`.
|
|
134
|
-
*/
|
|
135
|
-
clientAssertion?: ClientAssertionConfig | undefined;
|
|
148
|
+
authentication?: ("basic" | "post") | undefined;
|
|
136
149
|
/**
|
|
137
150
|
* Custom headers to include in the discovery request.
|
|
138
151
|
* Useful for providers like Epic that require specific headers (e.g., Epic-Client-ID).
|
|
@@ -151,6 +164,15 @@ interface GenericOAuthConfig<ID extends string = string> {
|
|
|
151
164
|
* @default false
|
|
152
165
|
*/
|
|
153
166
|
overrideUserInfo?: boolean | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Accept callbacks from providers that initiate the OAuth flow without
|
|
169
|
+
* sending a `state` parameter (e.g. Clever). When enabled, stateless
|
|
170
|
+
* callbacks restart the OAuth flow server-side with a fresh `state` and
|
|
171
|
+
* PKCE verifier. See the generic-oauth docs for details.
|
|
172
|
+
*
|
|
173
|
+
* @default false
|
|
174
|
+
*/
|
|
175
|
+
allowIdpInitiated?: boolean | undefined;
|
|
154
176
|
}
|
|
155
177
|
//#endregion
|
|
156
178
|
export { GenericOAuthConfig, GenericOAuthOptions };
|
package/dist/plugins/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InferOptionSchema, InferPluginContext, InferPluginErrorCodes, InferPluginIDs } from "../types/plugins.mjs";
|
|
2
2
|
import { HIDE_METADATA } from "../utils/hide-metadata.mjs";
|
|
3
|
-
import { AccessControl, ArrayElement, Role, Statements, SubArray, Subset } from "./access/types.mjs";
|
|
3
|
+
import { AccessControl, ArrayElement, ExactRoleStatements, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, Statements, SubArray, Subset } from "./access/types.mjs";
|
|
4
4
|
import { AuthorizeResponse, createAccessControl, role } from "./access/access.mjs";
|
|
5
5
|
import { OrganizationOptions } from "./organization/types.mjs";
|
|
6
6
|
import { InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferTeam, Invitation, InvitationInput, InvitationStatus, Member, MemberInput, Organization, OrganizationInput, OrganizationRole, OrganizationSchema, Team, TeamInput, TeamMember, TeamMemberInput, defaultRolesSchema, invitationSchema, invitationStatus, memberSchema, organizationRoleSchema, organizationSchema, roleSchema, teamMemberSchema, teamSchema } from "./organization/schema.mjs";
|
|
@@ -62,4 +62,4 @@ import { USERNAME_ERROR_CODES } from "./username/error-codes.mjs";
|
|
|
62
62
|
import { UsernameOptions, username } from "./username/index.mjs";
|
|
63
63
|
import { hasPermission } from "./organization/has-permission.mjs";
|
|
64
64
|
import { DefaultOrganizationPlugin, DynamicAccessControlEndpoints, OrganizationCreator, OrganizationEndpoints, OrganizationPlugin, TeamEndpoints, organization, parseRoles } from "./organization/organization.mjs";
|
|
65
|
-
export { AccessControl, AdminOptions, AnonymousOptions, AnonymousSession, ArrayElement, Auth0Options, AuthorizationQuery, AuthorizeResponse, BackupCodeOptions, BaseCaptchaOptions, BaseOAuthProviderOptions, BearerOptions, CaptchaFoxOptions, CaptchaOptions, Client, CloudflareTurnstileOptions, CodeVerificationValue, CustomSessionPluginOptions, DefaultOrganizationPlugin, DeviceAuthorizationOptions, DynamicAccessControlEndpoints, MULTI_SESSION_ERROR_CODES as ERROR_CODES, EmailOTPOptions, FieldSchema, GenericOAuthConfig, GenericOAuthOptions, GoogleRecaptchaOptions, GumroadOptions, HCaptchaOptions, HIDE_METADATA, HaveIBeenPwnedOptions, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOptionSchema, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginContext, InferPluginErrorCodes, InferPluginIDs, InferTeam, Invitation, InvitationInput, InvitationStatus, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodOptions, LineOptions, LoginResult, MagicLinkOptions, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OAuthProxyOptions, OIDCMetadata, OIDCOptions, OTPOptions, OktaOptions, OneTapOptions, OneTimeTokenOptions, OpenAPIModelSchema, OpenAPIOptions, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, Path, PatreonOptions, PhoneNumberOptions, Provider, ResolvedSigningKey, Role, SIWEPluginOptions, SessionWithImpersonatedBy, SlackOptions, Statements, SubArray, Subset, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, TestCookie, TestHelpers, TestUtilsOptions, TimeString, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, UsernameOptions, admin, anonymous, auth0, backupCode2fa, bearer, captcha, createAccessControl, createJwk, customSession, defaultRolesSchema, deviceAuthorization, deviceAuthorizationOptionsSchema, emailOTP, encodeBackupCodes, generateBackupCodes, generateExportedKeyPair, generator, genericOAuth, getBackupCodes, getClient, getJwtToken, getMCPProtectedResourceMetadata, getMCPProviderMetadata, getMetadata, getOrgAdapter, gumroad, hasPermission, haveIBeenPwned, hubspot, invitationSchema, invitationStatus, jwt, keycloak, lastLoginMethod, line, magicLink, mcp, memberSchema, microsoftEntraId, ms, multiSession, oAuthDiscoveryMetadata, oAuthProtectedResourceMetadata, oAuthProxy, oidcProvider, okta, oneTap, oneTimeToken, openAPI, organization, organizationRoleSchema, organizationSchema, otp2fa, parseRoles, patreon, phoneNumber, resolveSigningKey, role, roleSchema, sec, signJWT, siwe, slack, teamMemberSchema, teamSchema, testUtils, toExpJWT, totp2fa, twoFactor, twoFactorClient, username, verifyBackupCode, verifyJWT, withMcpAuth };
|
|
65
|
+
export { AccessControl, AdminOptions, AnonymousOptions, AnonymousSession, ArrayElement, Auth0Options, AuthorizationQuery, AuthorizeResponse, BackupCodeOptions, BaseCaptchaOptions, BaseOAuthProviderOptions, BearerOptions, CaptchaFoxOptions, CaptchaOptions, Client, CloudflareTurnstileOptions, CodeVerificationValue, CustomSessionPluginOptions, DefaultOrganizationPlugin, DeviceAuthorizationOptions, DynamicAccessControlEndpoints, MULTI_SESSION_ERROR_CODES as ERROR_CODES, EmailOTPOptions, ExactRoleStatements, FieldSchema, GenericOAuthConfig, GenericOAuthOptions, GoogleRecaptchaOptions, GumroadOptions, HCaptchaOptions, HIDE_METADATA, HaveIBeenPwnedOptions, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOptionSchema, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginContext, InferPluginErrorCodes, InferPluginIDs, InferTeam, Invitation, InvitationInput, InvitationStatus, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodOptions, LineOptions, LoginResult, MagicLinkOptions, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OAuthProxyOptions, OIDCMetadata, OIDCOptions, OTPOptions, OktaOptions, OneTapOptions, OneTimeTokenOptions, OpenAPIModelSchema, OpenAPIOptions, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, Path, PatreonOptions, PhoneNumberOptions, Provider, ResolvedSigningKey, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, SIWEPluginOptions, SessionWithImpersonatedBy, SlackOptions, Statements, SubArray, Subset, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, TestCookie, TestHelpers, TestUtilsOptions, TimeString, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, UsernameOptions, admin, anonymous, auth0, backupCode2fa, bearer, captcha, createAccessControl, createJwk, customSession, defaultRolesSchema, deviceAuthorization, deviceAuthorizationOptionsSchema, emailOTP, encodeBackupCodes, generateBackupCodes, generateExportedKeyPair, generator, genericOAuth, getBackupCodes, getClient, getJwtToken, getMCPProtectedResourceMetadata, getMCPProviderMetadata, getMetadata, getOrgAdapter, gumroad, hasPermission, haveIBeenPwned, hubspot, invitationSchema, invitationStatus, jwt, keycloak, lastLoginMethod, line, magicLink, mcp, memberSchema, microsoftEntraId, ms, multiSession, oAuthDiscoveryMetadata, oAuthProtectedResourceMetadata, oAuthProxy, oidcProvider, okta, oneTap, oneTimeToken, openAPI, organization, organizationRoleSchema, organizationSchema, otp2fa, parseRoles, patreon, phoneNumber, resolveSigningKey, role, roleSchema, sec, signJWT, siwe, slack, teamMemberSchema, teamSchema, testUtils, toExpJWT, totp2fa, twoFactor, twoFactorClient, username, verifyBackupCode, verifyJWT, withMcpAuth };
|
|
@@ -16,7 +16,7 @@ declare function toExpJWT(expirationTime: number | Date | string, iat: number):
|
|
|
16
16
|
declare function generateExportedKeyPair(options?: JwtOptions | undefined): Promise<{
|
|
17
17
|
publicWebKey: jose.JWK;
|
|
18
18
|
privateWebKey: jose.JWK;
|
|
19
|
-
alg: "
|
|
19
|
+
alg: "EdDSA" | "ES256" | "ES512" | "PS256" | "RS256";
|
|
20
20
|
cfg: {
|
|
21
21
|
crv?: "Ed25519" | undefined;
|
|
22
22
|
} | {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { parseCookies } from "../../cookies/cookie-utils.mjs";
|
|
1
2
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
2
3
|
//#region src/plugins/last-login-method/client.ts
|
|
3
4
|
function getCookieValue(name) {
|
|
4
5
|
if (typeof document === "undefined") return null;
|
|
5
|
-
|
|
6
|
-
return cookie ? cookie.split("=")[1] : null;
|
|
6
|
+
return parseCookies(document.cookie).get(name) ?? null;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Client-side plugin to retrieve the last used login method
|
|
@@ -18,7 +18,14 @@ interface MagicLinkOptions {
|
|
|
18
18
|
expiresIn?: number | undefined;
|
|
19
19
|
/**
|
|
20
20
|
* Allowed attempts for verifying the magic link token.
|
|
21
|
-
*
|
|
21
|
+
*
|
|
22
|
+
* @deprecated Multi-attempt verification is no longer supported. Each
|
|
23
|
+
* magic link token is consumed atomically on the first verification call,
|
|
24
|
+
* so a given token mints at most one session regardless of this value
|
|
25
|
+
* (see GHSA-hc7v-rggr-4hvx). The option is kept for source compatibility
|
|
26
|
+
* and may be removed in a future major; any value other than `1` is
|
|
27
|
+
* ignored and emits a `console.warn` at plugin construction.
|
|
28
|
+
*
|
|
22
29
|
* @default 1
|
|
23
30
|
*/
|
|
24
31
|
allowedAttempts?: number;
|
|
@@ -27,6 +27,7 @@ const magicLink = (options) => {
|
|
|
27
27
|
allowedAttempts: 1,
|
|
28
28
|
...options
|
|
29
29
|
};
|
|
30
|
+
if (options.allowedAttempts !== void 0 && options.allowedAttempts !== 1) console.warn("[better-auth/magic-link] `allowedAttempts` is ignored: tokens are consumed atomically on the first verification call (GHSA-hc7v-rggr-4hvx). Any value other than `1` has no effect; remove the option to silence this warning.");
|
|
30
31
|
async function storeToken(ctx, token) {
|
|
31
32
|
if (opts.storeToken === "hashed") return await defaultKeyHasher(token);
|
|
32
33
|
if (typeof opts.storeToken === "object" && "type" in opts.storeToken && opts.storeToken.type === "custom-hasher") return await opts.storeToken.hash(token);
|
|
@@ -59,8 +60,7 @@ const magicLink = (options) => {
|
|
|
59
60
|
identifier: storedToken,
|
|
60
61
|
value: JSON.stringify({
|
|
61
62
|
email,
|
|
62
|
-
name: ctx.body.name
|
|
63
|
-
attempt: 0
|
|
63
|
+
name: ctx.body.name
|
|
64
64
|
}),
|
|
65
65
|
expiresAt: new Date(Date.now() + (opts.expiresIn || 300) * 1e3)
|
|
66
66
|
});
|
|
@@ -119,22 +119,9 @@ const magicLink = (options) => {
|
|
|
119
119
|
}
|
|
120
120
|
const newUserCallbackURL = new URL(ctx.query.newUserCallbackURL ? decodeURIComponent(ctx.query.newUserCallbackURL) : callbackURL, ctx.context.baseURL).toString();
|
|
121
121
|
const storedToken = await storeToken(ctx, token);
|
|
122
|
-
const tokenValue = await ctx.context.internalAdapter.
|
|
122
|
+
const tokenValue = await ctx.context.internalAdapter.consumeVerificationValue(storedToken);
|
|
123
123
|
if (!tokenValue) redirectWithError("INVALID_TOKEN");
|
|
124
|
-
|
|
125
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(storedToken);
|
|
126
|
-
redirectWithError("EXPIRED_TOKEN");
|
|
127
|
-
}
|
|
128
|
-
const { email, name, attempt = 0 } = JSON.parse(tokenValue.value);
|
|
129
|
-
if (attempt >= opts.allowedAttempts) {
|
|
130
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(storedToken);
|
|
131
|
-
redirectWithError("ATTEMPTS_EXCEEDED");
|
|
132
|
-
}
|
|
133
|
-
await ctx.context.internalAdapter.updateVerificationByIdentifier(storedToken, { value: JSON.stringify({
|
|
134
|
-
email,
|
|
135
|
-
name,
|
|
136
|
-
attempt: attempt + 1
|
|
137
|
-
}) });
|
|
124
|
+
const { email, name } = JSON.parse(tokenValue.value);
|
|
138
125
|
let isNewUser = false;
|
|
139
126
|
let user = await ctx.context.internalAdapter.findUserByEmail(email).then((res) => res?.user);
|
|
140
127
|
if (!user) if (!opts.disableSignUp) {
|
|
@@ -72,8 +72,14 @@ async function authorizeMCPOAuth(ctx, options) {
|
|
|
72
72
|
});
|
|
73
73
|
if (invalidScopes.length) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_scope", `The following scopes are invalid: ${invalidScopes.join(", ")}`));
|
|
74
74
|
if ((!query.code_challenge || !query.code_challenge_method) && options.requirePKCE) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_request", "pkce is required"));
|
|
75
|
-
if (!query.
|
|
76
|
-
if (
|
|
75
|
+
if (query.code_challenge_method && !query.code_challenge) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_request", "code_challenge_method requires code_challenge"));
|
|
76
|
+
if (query.code_challenge) {
|
|
77
|
+
const allowedCodeChallengeMethods = options.allowPlainCodeChallengeMethod ? ["s256", "plain"] : ["s256"];
|
|
78
|
+
let codeChallengeMethod = query.code_challenge_method?.toLowerCase();
|
|
79
|
+
if (!codeChallengeMethod && options.allowPlainCodeChallengeMethod) codeChallengeMethod = "plain";
|
|
80
|
+
if (!codeChallengeMethod || !allowedCodeChallengeMethods.includes(codeChallengeMethod)) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method"));
|
|
81
|
+
query.code_challenge_method = codeChallengeMethod;
|
|
82
|
+
}
|
|
77
83
|
const code = generateRandomString(32, "a-z", "A-Z", "0-9");
|
|
78
84
|
const codeExpiresInMs = opts.codeExpiresIn * 1e3;
|
|
79
85
|
const expiresAt = new Date(Date.now() + codeExpiresInMs);
|