@masheev/better-auth 0.0.0-stage → 1.7.5
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/LICENSE.md +20 -0
- package/README.md +31 -2
- package/TYPE-PATCH-NOTICE.md +9 -0
- package/TYPE-PATCH-PROVENANCE.json +267 -0
- package/dist/_virtual/_rolldown/runtime.mjs +27 -0
- package/dist/adapters/drizzle-adapter/index.d.mts +1 -0
- package/dist/adapters/drizzle-adapter/index.mjs +2 -0
- package/dist/adapters/index.d.mts +26 -0
- package/dist/adapters/index.mjs +9 -0
- package/dist/adapters/kysely-adapter/index.d.mts +1 -0
- package/dist/adapters/kysely-adapter/index.mjs +2 -0
- package/dist/adapters/memory-adapter/index.d.mts +1 -0
- package/dist/adapters/memory-adapter/index.mjs +2 -0
- package/dist/adapters/mongodb-adapter/index.d.mts +1 -0
- package/dist/adapters/mongodb-adapter/index.mjs +2 -0
- package/dist/adapters/prisma-adapter/index.d.mts +1 -0
- package/dist/adapters/prisma-adapter/index.mjs +2 -0
- package/dist/api/dispatch.d.mts +34 -0
- package/dist/api/dispatch.mjs +275 -0
- package/dist/api/index.d.mts +4053 -0
- package/dist/api/index.mjs +220 -0
- package/dist/api/middlewares/authorization.d.mts +69 -0
- package/dist/api/middlewares/authorization.mjs +78 -0
- package/dist/api/middlewares/index.d.mts +2 -0
- package/dist/api/middlewares/origin-check.d.mts +17 -0
- package/dist/api/middlewares/origin-check.mjs +155 -0
- package/dist/api/rate-limiter/index.mjs +318 -0
- package/dist/api/routes/account.d.mts +413 -0
- package/dist/api/routes/account.mjs +619 -0
- package/dist/api/routes/callback.d.mts +32 -0
- package/dist/api/routes/callback.mjs +261 -0
- package/dist/api/routes/email-verification.d.mts +160 -0
- package/dist/api/routes/email-verification.mjs +324 -0
- package/dist/api/routes/error.d.mts +27 -0
- package/dist/api/routes/error.mjs +387 -0
- package/dist/api/routes/index.d.mts +12 -0
- package/dist/api/routes/ok.d.mts +35 -0
- package/dist/api/routes/ok.mjs +27 -0
- package/dist/api/routes/password.d.mts +182 -0
- package/dist/api/routes/password.mjs +206 -0
- package/dist/api/routes/session.d.mts +450 -0
- package/dist/api/routes/session.mjs +481 -0
- package/dist/api/routes/sign-in.d.mts +186 -0
- package/dist/api/routes/sign-in.mjs +368 -0
- package/dist/api/routes/sign-out.d.mts +51 -0
- package/dist/api/routes/sign-out.mjs +96 -0
- package/dist/api/routes/sign-up.d.mts +159 -0
- package/dist/api/routes/sign-up.mjs +275 -0
- package/dist/api/routes/update-session.d.mts +74 -0
- package/dist/api/routes/update-session.mjs +57 -0
- package/dist/api/routes/update-user.d.mts +428 -0
- package/dist/api/routes/update-user.mjs +522 -0
- package/dist/api/state/oauth.d.mts +53 -0
- package/dist/api/state/oauth.mjs +38 -0
- package/dist/api/state/should-session-refresh.d.mts +12 -0
- package/dist/api/state/should-session-refresh.mjs +13 -0
- package/dist/api/to-auth-endpoints.mjs +60 -0
- package/dist/auth/base.mjs +66 -0
- package/dist/auth/full.d.mts +28 -0
- package/dist/auth/full.mjs +29 -0
- package/dist/auth/minimal.d.mts +10 -0
- package/dist/auth/minimal.mjs +11 -0
- package/dist/auth/trusted-origins.mjs +106 -0
- package/dist/client/broadcast-channel.d.mts +19 -0
- package/dist/client/broadcast-channel.mjs +44 -0
- package/dist/client/config.d.mts +90 -0
- package/dist/client/config.mjs +113 -0
- package/dist/client/equality.d.mts +19 -0
- package/dist/client/equality.mjs +42 -0
- package/dist/client/fetch-plugins.mjs +17 -0
- package/dist/client/focus-manager.d.mts +10 -0
- package/dist/client/focus-manager.mjs +30 -0
- package/dist/client/index.d.mts +35 -0
- package/dist/client/index.mjs +22 -0
- package/dist/client/lynx/index.d.mts +43 -0
- package/dist/client/lynx/index.mjs +22 -0
- package/dist/client/lynx/lynx-store.d.mts +46 -0
- package/dist/client/lynx/lynx-store.mjs +44 -0
- package/dist/client/online-manager.d.mts +11 -0
- package/dist/client/online-manager.mjs +33 -0
- package/dist/client/parser.d.mts +10 -0
- package/dist/client/parser.mjs +70 -0
- package/dist/client/path-to-object.d.mts +113 -0
- package/dist/client/plugins/index.d.mts +44 -0
- package/dist/client/plugins/index.mjs +30 -0
- package/dist/client/plugins/infer-plugin.d.mts +16 -0
- package/dist/client/plugins/infer-plugin.mjs +11 -0
- package/dist/client/proxy.mjs +78 -0
- package/dist/client/query.d.mts +23 -0
- package/dist/client/query.mjs +124 -0
- package/dist/client/react/index.d.mts +44 -0
- package/dist/client/react/index.mjs +22 -0
- package/dist/client/react/react-store.d.mts +46 -0
- package/dist/client/react/react-store.mjs +44 -0
- package/dist/client/session-atom.d.mts +11 -0
- package/dist/client/session-atom.mjs +178 -0
- package/dist/client/session-refresh.d.mts +20 -0
- package/dist/client/session-refresh.mjs +131 -0
- package/dist/client/solid/index.d.mts +44 -0
- package/dist/client/solid/index.mjs +22 -0
- package/dist/client/solid/solid-store.mjs +21 -0
- package/dist/client/svelte/index.d.mts +44 -0
- package/dist/client/svelte/index.mjs +18 -0
- package/dist/client/types.d.mts +51 -0
- package/dist/client/vanilla.d.mts +42 -0
- package/dist/client/vanilla.mjs +18 -0
- package/dist/client/vue/index.d.mts +67 -0
- package/dist/client/vue/index.mjs +54 -0
- package/dist/client/vue/vue-store.mjs +23 -0
- package/dist/context/create-context.mjs +235 -0
- package/dist/context/helpers.mjs +158 -0
- package/dist/context/init-minimal.mjs +17 -0
- package/dist/context/init.mjs +19 -0
- package/dist/context/secret-utils.mjs +53 -0
- package/dist/context/store-capabilities.mjs +12 -0
- package/dist/cookies/cache.mjs +33 -0
- package/dist/cookies/cookie-utils.d.mts +72 -0
- package/dist/cookies/cookie-utils.mjs +203 -0
- package/dist/cookies/index.d.mts +124 -0
- package/dist/cookies/index.mjs +350 -0
- package/dist/cookies/jwt.mjs +44 -0
- package/dist/cookies/session-store.d.mts +32 -0
- package/dist/cookies/session-store.mjs +183 -0
- package/dist/crypto/buffer.d.mts +7 -0
- package/dist/crypto/buffer.mjs +16 -0
- package/dist/crypto/index.d.mts +32 -0
- package/dist/crypto/index.mjs +69 -0
- package/dist/crypto/jwt.d.mts +9 -0
- package/dist/crypto/jwt.mjs +141 -0
- package/dist/crypto/password.d.mts +13 -0
- package/dist/crypto/password.mjs +14 -0
- package/dist/crypto/random.d.mts +4 -0
- package/dist/crypto/random.mjs +5 -0
- package/dist/db/adapter-base.d.mts +7 -0
- package/dist/db/adapter-base.mjs +25 -0
- package/dist/db/adapter-kysely.d.mts +7 -0
- package/dist/db/adapter-kysely.mjs +17 -0
- package/dist/db/field-converter.d.mts +7 -0
- package/dist/db/field-converter.mjs +19 -0
- package/dist/db/field.d.mts +41 -0
- package/dist/db/get-migration.d.mts +56 -0
- package/dist/db/get-migration.mjs +685 -0
- package/dist/db/get-schema.d.mts +13 -0
- package/dist/db/get-schema.mjs +39 -0
- package/dist/db/index.d.mts +10 -0
- package/dist/db/index.mjs +35 -0
- package/dist/db/internal-adapter.d.mts +14 -0
- package/dist/db/internal-adapter.mjs +951 -0
- package/dist/db/revoke-unproven-account-access.d.mts +21 -0
- package/dist/db/revoke-unproven-account-access.mjs +58 -0
- package/dist/db/schema.d.mts +62 -0
- package/dist/db/schema.mjs +161 -0
- package/dist/db/to-zod.d.mts +35 -0
- package/dist/db/to-zod.mjs +23 -0
- package/dist/db/verification-token-storage.mjs +25 -0
- package/dist/db/with-hooks.d.mts +37 -0
- package/dist/db/with-hooks.mjs +255 -0
- package/dist/index.d.mts +30 -0
- package/dist/index.mjs +17 -0
- package/dist/integrations/cookie-plugin-guard.mjs +18 -0
- package/dist/integrations/next-js.d.mts +29 -0
- package/dist/integrations/next-js.mjs +105 -0
- package/dist/integrations/node.d.mts +11 -0
- package/dist/integrations/node.mjs +13 -0
- package/dist/integrations/solid-start.d.mts +22 -0
- package/dist/integrations/solid-start.mjs +15 -0
- package/dist/integrations/svelte-kit.d.mts +38 -0
- package/dist/integrations/svelte-kit.mjs +57 -0
- package/dist/integrations/tanstack-start-solid.d.mts +33 -0
- package/dist/integrations/tanstack-start-solid.mjs +57 -0
- package/dist/integrations/tanstack-start.d.mts +33 -0
- package/dist/integrations/tanstack-start.mjs +57 -0
- package/dist/oauth2/account-key.mjs +43 -0
- package/dist/oauth2/errors.mjs +49 -0
- package/dist/oauth2/index.d.mts +5 -0
- package/dist/oauth2/index.mjs +5 -0
- package/dist/oauth2/link-account.d.mts +96 -0
- package/dist/oauth2/link-account.mjs +329 -0
- package/dist/oauth2/state.d.mts +54 -0
- package/dist/oauth2/state.mjs +67 -0
- package/dist/oauth2/utils.d.mts +11 -0
- package/dist/oauth2/utils.mjs +33 -0
- package/dist/package.mjs +4 -0
- package/dist/plugins/access/access.d.mts +17 -0
- package/dist/plugins/access/access.mjs +78 -0
- package/dist/plugins/access/index.d.mts +3 -0
- package/dist/plugins/access/index.mjs +2 -0
- package/dist/plugins/access/types.d.mts +24 -0
- package/dist/plugins/additional-fields/client.d.mts +100 -0
- package/dist/plugins/additional-fields/client.mjs +11 -0
- package/dist/plugins/admin/access/index.d.mts +2 -0
- package/dist/plugins/admin/access/index.mjs +2 -0
- package/dist/plugins/admin/access/statement.d.mts +51 -0
- package/dist/plugins/admin/access/statement.mjs +52 -0
- package/dist/plugins/admin/admin.d.mts +911 -0
- package/dist/plugins/admin/admin.mjs +89 -0
- package/dist/plugins/admin/client.d.mts +81 -0
- package/dist/plugins/admin/client.mjs +39 -0
- package/dist/plugins/admin/error-codes.d.mts +30 -0
- package/dist/plugins/admin/error-codes.mjs +29 -0
- package/dist/plugins/admin/has-permission.mjs +12 -0
- package/dist/plugins/admin/index.d.mts +3 -0
- package/dist/plugins/admin/index.mjs +2 -0
- package/dist/plugins/admin/routes.mjs +919 -0
- package/dist/plugins/admin/schema.d.mts +40 -0
- package/dist/plugins/admin/schema.mjs +33 -0
- package/dist/plugins/admin/types.d.mts +88 -0
- package/dist/plugins/anonymous/client.d.mts +32 -0
- package/dist/plugins/anonymous/client.mjs +21 -0
- package/dist/plugins/anonymous/error-codes.d.mts +15 -0
- package/dist/plugins/anonymous/error-codes.mjs +14 -0
- package/dist/plugins/anonymous/index.d.mts +175 -0
- package/dist/plugins/anonymous/index.mjs +219 -0
- package/dist/plugins/anonymous/schema.d.mts +15 -0
- package/dist/plugins/anonymous/schema.mjs +9 -0
- package/dist/plugins/anonymous/types.d.mts +65 -0
- package/dist/plugins/bearer/index.d.mts +45 -0
- package/dist/plugins/bearer/index.mjs +74 -0
- package/dist/plugins/captcha/constants.d.mts +9 -0
- package/dist/plugins/captcha/constants.mjs +27 -0
- package/dist/plugins/captcha/error-codes.mjs +13 -0
- package/dist/plugins/captcha/index.d.mts +27 -0
- package/dist/plugins/captcha/index.mjs +79 -0
- package/dist/plugins/captcha/types.d.mts +52 -0
- package/dist/plugins/captcha/utils.mjs +9 -0
- package/dist/plugins/captcha/verify-handlers/captchafox.mjs +27 -0
- package/dist/plugins/captcha/verify-handlers/cloudflare-turnstile.mjs +47 -0
- package/dist/plugins/captcha/verify-handlers/google-recaptcha.mjs +32 -0
- package/dist/plugins/captcha/verify-handlers/h-captcha.mjs +27 -0
- package/dist/plugins/custom-session/client.d.mts +17 -0
- package/dist/plugins/custom-session/client.mjs +7 -0
- package/dist/plugins/custom-session/index.d.mts +73 -0
- package/dist/plugins/custom-session/index.mjs +65 -0
- package/dist/plugins/device-authorization/client.d.mts +17 -0
- package/dist/plugins/device-authorization/client.mjs +18 -0
- package/dist/plugins/device-authorization/error-codes.mjs +19 -0
- package/dist/plugins/device-authorization/index.d.mts +558 -0
- package/dist/plugins/device-authorization/index.mjs +87 -0
- package/dist/plugins/device-authorization/routes.d.mts +32 -0
- package/dist/plugins/device-authorization/routes.mjs +725 -0
- package/dist/plugins/device-authorization/schema.d.mts +18 -0
- package/dist/plugins/device-authorization/schema.mjs +63 -0
- package/dist/plugins/email-otp/client.d.mts +21 -0
- package/dist/plugins/email-otp/client.mjs +17 -0
- package/dist/plugins/email-otp/error-codes.d.mts +10 -0
- package/dist/plugins/email-otp/error-codes.mjs +9 -0
- package/dist/plugins/email-otp/index.d.mts +551 -0
- package/dist/plugins/email-otp/index.mjs +145 -0
- package/dist/plugins/email-otp/otp-token.mjs +55 -0
- package/dist/plugins/email-otp/routes.mjs +784 -0
- package/dist/plugins/email-otp/types.d.mts +109 -0
- package/dist/plugins/email-otp/utils.mjs +17 -0
- package/dist/plugins/generic-oauth/error-codes.mjs +8 -0
- package/dist/plugins/generic-oauth/index.d.mts +52 -0
- package/dist/plugins/generic-oauth/index.mjs +279 -0
- package/dist/plugins/generic-oauth/providers/auth0.d.mts +36 -0
- package/dist/plugins/generic-oauth/providers/auth0.mjs +49 -0
- package/dist/plugins/generic-oauth/providers/gumroad.d.mts +31 -0
- package/dist/plugins/generic-oauth/providers/gumroad.mjs +62 -0
- package/dist/plugins/generic-oauth/providers/hubspot.d.mts +36 -0
- package/dist/plugins/generic-oauth/providers/hubspot.mjs +62 -0
- package/dist/plugins/generic-oauth/providers/index.d.mts +10 -0
- package/dist/plugins/generic-oauth/providers/keycloak.d.mts +36 -0
- package/dist/plugins/generic-oauth/providers/keycloak.mjs +47 -0
- package/dist/plugins/generic-oauth/providers/line.d.mts +54 -0
- package/dist/plugins/generic-oauth/providers/line.mjs +93 -0
- package/dist/plugins/generic-oauth/providers/microsoft-entra-id.d.mts +39 -0
- package/dist/plugins/generic-oauth/providers/microsoft-entra-id.mjs +104 -0
- package/dist/plugins/generic-oauth/providers/okta.d.mts +36 -0
- package/dist/plugins/generic-oauth/providers/okta.mjs +47 -0
- package/dist/plugins/generic-oauth/providers/patreon.d.mts +29 -0
- package/dist/plugins/generic-oauth/providers/patreon.mjs +61 -0
- package/dist/plugins/generic-oauth/providers/slack.d.mts +29 -0
- package/dist/plugins/generic-oauth/providers/slack.mjs +63 -0
- package/dist/plugins/generic-oauth/providers/yandex.d.mts +29 -0
- package/dist/plugins/generic-oauth/providers/yandex.mjs +67 -0
- package/dist/plugins/generic-oauth/types.d.mts +268 -0
- package/dist/plugins/haveibeenpwned/index.d.mts +63 -0
- package/dist/plugins/haveibeenpwned/index.mjs +81 -0
- package/dist/plugins/index.d.mts +69 -0
- package/dist/plugins/index.mjs +49 -0
- package/dist/plugins/jwt/adapter.mjs +68 -0
- package/dist/plugins/jwt/client.d.mts +41 -0
- package/dist/plugins/jwt/client.mjs +19 -0
- package/dist/plugins/jwt/cookie-cache.mjs +77 -0
- package/dist/plugins/jwt/index.d.mts +246 -0
- package/dist/plugins/jwt/index.mjs +214 -0
- package/dist/plugins/jwt/schema.d.mts +33 -0
- package/dist/plugins/jwt/schema.mjs +29 -0
- package/dist/plugins/jwt/sign.d.mts +128 -0
- package/dist/plugins/jwt/sign.mjs +130 -0
- package/dist/plugins/jwt/types.d.mts +256 -0
- package/dist/plugins/jwt/utils.d.mts +41 -0
- package/dist/plugins/jwt/utils.mjs +64 -0
- package/dist/plugins/jwt/verify.d.mts +11 -0
- package/dist/plugins/jwt/verify.mjs +45 -0
- package/dist/plugins/last-login-method/client.d.mts +48 -0
- package/dist/plugins/last-login-method/client.mjs +48 -0
- package/dist/plugins/last-login-method/index.d.mts +127 -0
- package/dist/plugins/last-login-method/index.mjs +98 -0
- package/dist/plugins/magic-link/client.d.mts +10 -0
- package/dist/plugins/magic-link/client.mjs +11 -0
- package/dist/plugins/magic-link/index.d.mts +213 -0
- package/dist/plugins/magic-link/index.mjs +208 -0
- package/dist/plugins/magic-link/utils.mjs +9 -0
- package/dist/plugins/multi-session/client.d.mts +19 -0
- package/dist/plugins/multi-session/client.mjs +19 -0
- package/dist/plugins/multi-session/error-codes.d.mts +8 -0
- package/dist/plugins/multi-session/error-codes.mjs +5 -0
- package/dist/plugins/multi-session/index.d.mts +212 -0
- package/dist/plugins/multi-session/index.mjs +217 -0
- package/dist/plugins/oauth-popup/client.d.mts +82 -0
- package/dist/plugins/oauth-popup/client.mjs +203 -0
- package/dist/plugins/oauth-popup/constants.d.mts +11 -0
- package/dist/plugins/oauth-popup/constants.mjs +11 -0
- package/dist/plugins/oauth-popup/error-codes.d.mts +11 -0
- package/dist/plugins/oauth-popup/error-codes.mjs +10 -0
- package/dist/plugins/oauth-popup/index.d.mts +67 -0
- package/dist/plugins/oauth-popup/index.mjs +232 -0
- package/dist/plugins/oauth-popup/types.d.mts +30 -0
- package/dist/plugins/oauth-proxy/index.d.mts +130 -0
- package/dist/plugins/oauth-proxy/index.mjs +407 -0
- package/dist/plugins/oauth-proxy/utils.mjs +55 -0
- package/dist/plugins/one-tap/client.d.mts +174 -0
- package/dist/plugins/one-tap/client.mjs +208 -0
- package/dist/plugins/one-tap/index.d.mts +82 -0
- package/dist/plugins/one-tap/index.mjs +100 -0
- package/dist/plugins/one-time-token/client.d.mts +10 -0
- package/dist/plugins/one-time-token/client.mjs +11 -0
- package/dist/plugins/one-time-token/index.d.mts +132 -0
- package/dist/plugins/one-time-token/index.mjs +107 -0
- package/dist/plugins/one-time-token/utils.mjs +9 -0
- package/dist/plugins/open-api/generator.d.mts +126 -0
- package/dist/plugins/open-api/generator.mjs +557 -0
- package/dist/plugins/open-api/index.d.mts +97 -0
- package/dist/plugins/open-api/index.mjs +65 -0
- package/dist/plugins/open-api/logo.mjs +13 -0
- package/dist/plugins/organization/access/index.d.mts +2 -0
- package/dist/plugins/organization/access/index.mjs +2 -0
- package/dist/plugins/organization/access/statement.d.mts +113 -0
- package/dist/plugins/organization/access/statement.mjs +77 -0
- package/dist/plugins/organization/adapter.d.mts +815 -0
- package/dist/plugins/organization/adapter.mjs +813 -0
- package/dist/plugins/organization/call.mjs +15 -0
- package/dist/plugins/organization/client.d.mts +375 -0
- package/dist/plugins/organization/client.mjs +104 -0
- package/dist/plugins/organization/error-codes.d.mts +66 -0
- package/dist/plugins/organization/error-codes.mjs +65 -0
- package/dist/plugins/organization/has-permission.d.mts +17 -0
- package/dist/plugins/organization/has-permission.mjs +34 -0
- package/dist/plugins/organization/index.d.mts +6 -0
- package/dist/plugins/organization/index.mjs +4 -0
- package/dist/plugins/organization/organization.d.mts +420 -0
- package/dist/plugins/organization/organization.mjs +886 -0
- package/dist/plugins/organization/permission.d.mts +15 -0
- package/dist/plugins/organization/permission.mjs +14 -0
- package/dist/plugins/organization/routes/crud-access-control.d.mts +511 -0
- package/dist/plugins/organization/routes/crud-access-control.mjs +674 -0
- package/dist/plugins/organization/routes/crud-invites.d.mts +1154 -0
- package/dist/plugins/organization/routes/crud-invites.mjs +602 -0
- package/dist/plugins/organization/routes/crud-members.d.mts +1057 -0
- package/dist/plugins/organization/routes/crud-members.mjs +518 -0
- package/dist/plugins/organization/routes/crud-org.d.mts +918 -0
- package/dist/plugins/organization/routes/crud-org.mjs +455 -0
- package/dist/plugins/organization/routes/crud-team.d.mts +1238 -0
- package/dist/plugins/organization/routes/crud-team.mjs +766 -0
- package/dist/plugins/organization/schema.d.mts +390 -0
- package/dist/plugins/organization/schema.mjs +65 -0
- package/dist/plugins/organization/types.d.mts +691 -0
- package/dist/plugins/phone-number/client.d.mts +31 -0
- package/dist/plugins/phone-number/client.mjs +19 -0
- package/dist/plugins/phone-number/error-codes.d.mts +19 -0
- package/dist/plugins/phone-number/error-codes.mjs +18 -0
- package/dist/plugins/phone-number/index.d.mts +360 -0
- package/dist/plugins/phone-number/index.mjs +57 -0
- package/dist/plugins/phone-number/routes.mjs +529 -0
- package/dist/plugins/phone-number/schema.d.mts +22 -0
- package/dist/plugins/phone-number/schema.mjs +18 -0
- package/dist/plugins/phone-number/types.d.mts +116 -0
- package/dist/plugins/siwe/client.d.mts +14 -0
- package/dist/plugins/siwe/client.mjs +15 -0
- package/dist/plugins/siwe/index.d.mts +62 -0
- package/dist/plugins/siwe/index.mjs +272 -0
- package/dist/plugins/siwe/parse-message.mjs +60 -0
- package/dist/plugins/siwe/schema.d.mts +35 -0
- package/dist/plugins/siwe/schema.mjs +30 -0
- package/dist/plugins/siwe/types.d.mts +43 -0
- package/dist/plugins/test-utils/auth-helpers.mjs +33 -0
- package/dist/plugins/test-utils/cookie-builder.mjs +55 -0
- package/dist/plugins/test-utils/db-helpers.mjs +65 -0
- package/dist/plugins/test-utils/factories.mjs +38 -0
- package/dist/plugins/test-utils/index.d.mts +73 -0
- package/dist/plugins/test-utils/index.mjs +97 -0
- package/dist/plugins/test-utils/otp-sink.mjs +21 -0
- package/dist/plugins/test-utils/types.d.mts +51 -0
- package/dist/plugins/two-factor/backup-codes/index.d.mts +291 -0
- package/dist/plugins/two-factor/backup-codes/index.mjs +341 -0
- package/dist/plugins/two-factor/client.d.mts +76 -0
- package/dist/plugins/two-factor/client.mjs +41 -0
- package/dist/plugins/two-factor/constant.mjs +6 -0
- package/dist/plugins/two-factor/error-code.d.mts +20 -0
- package/dist/plugins/two-factor/error-code.mjs +19 -0
- package/dist/plugins/two-factor/index.d.mts +720 -0
- package/dist/plugins/two-factor/index.mjs +349 -0
- package/dist/plugins/two-factor/otp/index.d.mts +216 -0
- package/dist/plugins/two-factor/otp/index.mjs +252 -0
- package/dist/plugins/two-factor/schema.d.mts +59 -0
- package/dist/plugins/two-factor/schema.mjs +53 -0
- package/dist/plugins/two-factor/totp/index.d.mts +219 -0
- package/dist/plugins/two-factor/totp/index.mjs +230 -0
- package/dist/plugins/two-factor/types.d.mts +111 -0
- package/dist/plugins/two-factor/utils.mjs +9 -0
- package/dist/plugins/two-factor/verify-two-factor.mjs +205 -0
- package/dist/plugins/username/client.d.mts +38 -0
- package/dist/plugins/username/client.mjs +17 -0
- package/dist/plugins/username/error-codes.d.mts +16 -0
- package/dist/plugins/username/error-codes.mjs +15 -0
- package/dist/plugins/username/index.d.mts +326 -0
- package/dist/plugins/username/index.mjs +312 -0
- package/dist/plugins/username/schema.d.mts +50 -0
- package/dist/plugins/username/schema.mjs +25 -0
- package/dist/social-providers/index.d.mts +1 -0
- package/dist/social-providers/index.mjs +2 -0
- package/dist/state.d.mts +48 -0
- package/dist/state.mjs +149 -0
- package/dist/test-utils/headers.d.mts +8 -0
- package/dist/test-utils/headers.mjs +17 -0
- package/dist/test-utils/http-test-instance.d.mts +413 -0
- package/dist/test-utils/http-test-instance.mjs +107 -0
- package/dist/test-utils/index.d.mts +4 -0
- package/dist/test-utils/index.mjs +4 -0
- package/dist/test-utils/test-instance.d.mts +56 -0
- package/dist/test-utils/test-instance.mjs +242 -0
- package/dist/types/adapter.d.mts +2 -0
- package/dist/types/api.d.mts +28 -0
- package/dist/types/auth.d.mts +32 -0
- package/dist/types/helper.d.mts +26 -0
- package/dist/types/index.d.mts +10 -0
- package/dist/types/index.mjs +1 -0
- package/dist/types/models.d.mts +10 -0
- package/dist/types/plugins.d.mts +19 -0
- package/dist/utils/boolean.mjs +6 -0
- package/dist/utils/constants.mjs +4 -0
- package/dist/utils/date.mjs +6 -0
- package/dist/utils/hashing.mjs +18 -0
- package/dist/utils/hide-metadata.d.mts +6 -0
- package/dist/utils/hide-metadata.mjs +4 -0
- package/dist/utils/index.d.mts +4 -0
- package/dist/utils/is-api-error.d.mts +2 -0
- package/dist/utils/is-api-error.mjs +2 -0
- package/dist/utils/is-atom.mjs +6 -0
- package/dist/utils/is-promise.mjs +6 -0
- package/dist/utils/middleware-response.mjs +7 -0
- package/dist/utils/password.mjs +32 -0
- package/dist/utils/plugin-helper.mjs +14 -0
- package/dist/utils/request.mjs +23 -0
- package/dist/utils/shim.mjs +22 -0
- package/dist/utils/time.d.mts +48 -0
- package/dist/utils/time.mjs +98 -0
- package/dist/utils/url.d.mts +78 -0
- package/dist/utils/url.mjs +238 -0
- package/dist/utils/validate-user-info.mjs +44 -0
- package/dist/utils/wildcard.mjs +106 -0
- package/dist/version.mjs +5 -0
- package/package.json +595 -3
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
import { ms } from "../../utils/time.mjs";
|
|
2
|
+
import { generateRandomString } from "../../crypto/random.mjs";
|
|
3
|
+
import { getSessionFromCtx } from "../../api/routes/session.mjs";
|
|
4
|
+
import { DEVICE_AUTHORIZATION_ERROR_CODES } from "./error-codes.mjs";
|
|
5
|
+
import { APIError } from "@better-auth/core/error";
|
|
6
|
+
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
7
|
+
import * as z from "zod";
|
|
8
|
+
//#region src/plugins/device-authorization/routes.ts
|
|
9
|
+
const defaultCharset = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
10
|
+
const MAX_DEVICE_CODE_GENERATION_ATTEMPTS = 3;
|
|
11
|
+
const UNIQUE_CONSTRAINT_ERROR_IDENTIFIERS = new Set([
|
|
12
|
+
"11000",
|
|
13
|
+
"1062",
|
|
14
|
+
"2067",
|
|
15
|
+
"23505",
|
|
16
|
+
"2601",
|
|
17
|
+
"2627",
|
|
18
|
+
"ER_DUP_ENTRY",
|
|
19
|
+
"P2002",
|
|
20
|
+
"SQLITE_CONSTRAINT_UNIQUE"
|
|
21
|
+
]);
|
|
22
|
+
function isUniqueConstraintError(error) {
|
|
23
|
+
if (typeof error !== "object" || error === null) return false;
|
|
24
|
+
const details = error;
|
|
25
|
+
if ([
|
|
26
|
+
details.code,
|
|
27
|
+
details.errcode,
|
|
28
|
+
details.errno,
|
|
29
|
+
details.number
|
|
30
|
+
].some((identifier) => UNIQUE_CONSTRAINT_ERROR_IDENTIFIERS.has(String(identifier)))) return true;
|
|
31
|
+
const message = details.message;
|
|
32
|
+
return typeof message === "string" && /unique(?: key)? constraint|duplicate (?:entry|key)|e11000/i.test(message);
|
|
33
|
+
}
|
|
34
|
+
const defaultUserCodePattern = new RegExp(`^[${defaultCharset}]+$`, "i");
|
|
35
|
+
function validateGeneratedCode(code, label) {
|
|
36
|
+
if (typeof code !== "string") throw new APIError("BAD_REQUEST", {
|
|
37
|
+
error: "invalid_request",
|
|
38
|
+
error_description: `Generated ${label} code must be a string`
|
|
39
|
+
});
|
|
40
|
+
if (Array.from(code).length > 191) throw new APIError("BAD_REQUEST", {
|
|
41
|
+
error: "invalid_request",
|
|
42
|
+
error_description: `Generated ${label} code must be at most 191 characters`
|
|
43
|
+
});
|
|
44
|
+
return code;
|
|
45
|
+
}
|
|
46
|
+
function normalizeUserCode(userCode) {
|
|
47
|
+
return userCode.replace(/[^a-zA-Z0-9]/g, "").toUpperCase();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Preserve exact custom user codes, and normalize only default-alphabet codes.
|
|
51
|
+
*/
|
|
52
|
+
async function findDeviceCodeByUserCode(ctx, userCode) {
|
|
53
|
+
const findByUserCode = async (value) => {
|
|
54
|
+
const deviceCode = await ctx.context.adapter.findOne({
|
|
55
|
+
model: "deviceCode",
|
|
56
|
+
where: [{
|
|
57
|
+
field: "userCode",
|
|
58
|
+
value
|
|
59
|
+
}]
|
|
60
|
+
});
|
|
61
|
+
return deviceCode?.userCode === value ? deviceCode : null;
|
|
62
|
+
};
|
|
63
|
+
const exactDeviceCode = await findByUserCode(userCode);
|
|
64
|
+
if (exactDeviceCode) return exactDeviceCode;
|
|
65
|
+
const normalizedUserCode = normalizeUserCode(userCode);
|
|
66
|
+
if (normalizedUserCode === userCode || !defaultUserCodePattern.test(normalizedUserCode)) return exactDeviceCode;
|
|
67
|
+
return findByUserCode(normalizedUserCode);
|
|
68
|
+
}
|
|
69
|
+
const deviceCodeBodySchema = z.object({
|
|
70
|
+
client_id: z.string().meta({ description: "The client ID of the application" }),
|
|
71
|
+
user_id: z.string().meta({ description: "The user ID to which the device code should be pre-bound." }).optional(),
|
|
72
|
+
scope: z.string().meta({ description: "Space-separated list of scopes" }).optional()
|
|
73
|
+
});
|
|
74
|
+
const deviceCodeBaseErrorCodes = [
|
|
75
|
+
"invalid_request",
|
|
76
|
+
"invalid_client",
|
|
77
|
+
"unauthorized_client",
|
|
78
|
+
"invalid_scope"
|
|
79
|
+
];
|
|
80
|
+
const deviceAuthorizationBaseRequestFields = [
|
|
81
|
+
"client_id",
|
|
82
|
+
"user_id",
|
|
83
|
+
"scope"
|
|
84
|
+
];
|
|
85
|
+
async function normalizeDeviceAuthorizationBaseRequestParameters(request, body) {
|
|
86
|
+
for (const field of deviceAuthorizationBaseRequestFields) if (body[field] === "") body[field] = void 0;
|
|
87
|
+
const contentType = request?.headers.get("content-type")?.toLowerCase() ?? "";
|
|
88
|
+
if (!request || !contentType.includes("application/x-www-form-urlencoded")) return;
|
|
89
|
+
const params = new URLSearchParams(await request.clone().text());
|
|
90
|
+
for (const field of deviceAuthorizationBaseRequestFields) {
|
|
91
|
+
const effectiveValues = params.getAll(field).filter((value) => value.length > 0);
|
|
92
|
+
if (effectiveValues.length > 1) throw new APIError("BAD_REQUEST", {
|
|
93
|
+
error: "invalid_request",
|
|
94
|
+
error_description: `${field} must not be repeated`
|
|
95
|
+
});
|
|
96
|
+
if (effectiveValues.length === 0) body[field] = void 0;
|
|
97
|
+
else body[field] = effectiveValues[0];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const deviceCode = (opts, grant) => {
|
|
101
|
+
const generateDeviceCode = async () => {
|
|
102
|
+
return validateGeneratedCode(opts.generateDeviceCode ? await opts.generateDeviceCode() : defaultGenerateDeviceCode(opts.deviceCodeLength), "device");
|
|
103
|
+
};
|
|
104
|
+
const generateUserCode = async () => {
|
|
105
|
+
return validateGeneratedCode(opts.generateUserCode ? await opts.generateUserCode() : defaultGenerateUserCode(opts.userCodeLength), "user");
|
|
106
|
+
};
|
|
107
|
+
const requestFields = grant?.requestSchemaFields ?? {};
|
|
108
|
+
const requestSchema = (grant ? deviceCodeBodySchema.extend({ client_id: z.string().optional() }) : deviceCodeBodySchema).extend(requestFields);
|
|
109
|
+
const requestErrorCodes = [...deviceCodeBaseErrorCodes, ...grant?.requestErrorCodes ?? []];
|
|
110
|
+
const endpointErrorCodes = [...requestErrorCodes, "server_error"];
|
|
111
|
+
return createAuthEndpoint("/device/code", {
|
|
112
|
+
method: "POST",
|
|
113
|
+
cloneRequest: true,
|
|
114
|
+
body: requestSchema,
|
|
115
|
+
error: z.object({
|
|
116
|
+
error: z.enum(endpointErrorCodes).meta({ description: "Error code" }),
|
|
117
|
+
error_description: z.string().meta({ description: "Detailed error description" })
|
|
118
|
+
}),
|
|
119
|
+
onValidationError: ({ issues, message }) => {
|
|
120
|
+
grant?.onRequestValidationError?.(issues);
|
|
121
|
+
throw new APIError("BAD_REQUEST", {
|
|
122
|
+
error: "invalid_request",
|
|
123
|
+
error_description: message
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
metadata: {
|
|
127
|
+
noStore: true,
|
|
128
|
+
allowedMediaTypes: ["application/json", "application/x-www-form-urlencoded"],
|
|
129
|
+
openapi: {
|
|
130
|
+
description: `Request a device and user code
|
|
131
|
+
|
|
132
|
+
Follow [rfc8628#section-3.2](https://datatracker.ietf.org/doc/html/rfc8628#section-3.2)`,
|
|
133
|
+
responses: {
|
|
134
|
+
...grant?.requestOpenAPIResponses,
|
|
135
|
+
200: {
|
|
136
|
+
description: "Success",
|
|
137
|
+
content: { "application/json": { schema: {
|
|
138
|
+
type: "object",
|
|
139
|
+
properties: {
|
|
140
|
+
device_code: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "The device verification code"
|
|
143
|
+
},
|
|
144
|
+
user_code: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: "The user code to display"
|
|
147
|
+
},
|
|
148
|
+
verification_uri: {
|
|
149
|
+
type: "string",
|
|
150
|
+
format: "uri",
|
|
151
|
+
description: "The URL for user verification. Defaults to /device if not configured."
|
|
152
|
+
},
|
|
153
|
+
verification_uri_complete: {
|
|
154
|
+
type: "string",
|
|
155
|
+
format: "uri",
|
|
156
|
+
description: "The complete URL with user code as query parameter."
|
|
157
|
+
},
|
|
158
|
+
expires_in: {
|
|
159
|
+
type: "number",
|
|
160
|
+
description: "Lifetime in seconds of the device code"
|
|
161
|
+
},
|
|
162
|
+
interval: {
|
|
163
|
+
type: "number",
|
|
164
|
+
description: "Minimum polling interval in seconds"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} } }
|
|
168
|
+
},
|
|
169
|
+
400: {
|
|
170
|
+
description: "Error response",
|
|
171
|
+
content: { "application/json": { schema: {
|
|
172
|
+
type: "object",
|
|
173
|
+
properties: {
|
|
174
|
+
error: {
|
|
175
|
+
type: "string",
|
|
176
|
+
enum: requestErrorCodes
|
|
177
|
+
},
|
|
178
|
+
error_description: { type: "string" }
|
|
179
|
+
}
|
|
180
|
+
} } }
|
|
181
|
+
},
|
|
182
|
+
500: {
|
|
183
|
+
description: "Server error",
|
|
184
|
+
content: { "application/json": { schema: {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
error: {
|
|
188
|
+
type: "string",
|
|
189
|
+
enum: ["server_error"]
|
|
190
|
+
},
|
|
191
|
+
error_description: { type: "string" }
|
|
192
|
+
}
|
|
193
|
+
} } }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}, async (ctx) => {
|
|
199
|
+
const request = ctx.body;
|
|
200
|
+
await normalizeDeviceAuthorizationBaseRequestParameters(ctx.request, request);
|
|
201
|
+
const grantAuthorization = grant ? await grant.authorizeRequest({
|
|
202
|
+
ctx,
|
|
203
|
+
request
|
|
204
|
+
}) : void 0;
|
|
205
|
+
const clientId = grantAuthorization?.clientId ?? request.client_id;
|
|
206
|
+
if (!grantAuthorization) {
|
|
207
|
+
if (!request.client_id) throw new APIError("BAD_REQUEST", {
|
|
208
|
+
error: "invalid_request",
|
|
209
|
+
error_description: "client_id is required"
|
|
210
|
+
});
|
|
211
|
+
if (!opts.validateClient) {
|
|
212
|
+
if (grant) throw new APIError("BAD_REQUEST", {
|
|
213
|
+
error: "invalid_client",
|
|
214
|
+
error_description: "Invalid client ID"
|
|
215
|
+
});
|
|
216
|
+
} else if (!await opts.validateClient(request.client_id)) throw new APIError("BAD_REQUEST", {
|
|
217
|
+
error: "invalid_client",
|
|
218
|
+
error_description: "Invalid client ID"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (!clientId) throw new APIError("BAD_REQUEST", {
|
|
222
|
+
error: "invalid_request",
|
|
223
|
+
error_description: "client_id is required"
|
|
224
|
+
});
|
|
225
|
+
if (opts.onDeviceAuthRequest) await opts.onDeviceAuthRequest(clientId, request.scope);
|
|
226
|
+
const expiresIn = ms(opts.expiresIn);
|
|
227
|
+
const expiresAt = new Date(Date.now() + expiresIn);
|
|
228
|
+
for (let attempt = 0; attempt < MAX_DEVICE_CODE_GENERATION_ATTEMPTS; attempt++) {
|
|
229
|
+
const deviceCode = await generateDeviceCode();
|
|
230
|
+
const userCode = await generateUserCode();
|
|
231
|
+
try {
|
|
232
|
+
await ctx.context.adapter.create({
|
|
233
|
+
model: "deviceCode",
|
|
234
|
+
data: {
|
|
235
|
+
...grantAuthorization?.deviceCodeFields,
|
|
236
|
+
deviceCode,
|
|
237
|
+
userCode,
|
|
238
|
+
userId: request.user_id || null,
|
|
239
|
+
expiresAt,
|
|
240
|
+
status: "pending",
|
|
241
|
+
pollingInterval: ms(opts.interval),
|
|
242
|
+
clientId,
|
|
243
|
+
scope: request.scope
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (!isUniqueConstraintError(error)) throw error;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const { verificationUri, verificationUriComplete } = buildVerificationUris(opts.verificationUri, ctx.context.baseURL, userCode);
|
|
251
|
+
ctx.setHeader("Cache-Control", "no-store");
|
|
252
|
+
ctx.setHeader("Pragma", "no-cache");
|
|
253
|
+
return ctx.json({
|
|
254
|
+
device_code: deviceCode,
|
|
255
|
+
user_code: userCode,
|
|
256
|
+
verification_uri: verificationUri,
|
|
257
|
+
verification_uri_complete: verificationUriComplete,
|
|
258
|
+
expires_in: Math.floor(expiresIn / 1e3),
|
|
259
|
+
interval: Math.floor(ms(opts.interval) / 1e3)
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
throw new APIError("INTERNAL_SERVER_ERROR", {
|
|
263
|
+
error: "server_error",
|
|
264
|
+
error_description: "Failed to generate a unique device code"
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
const deviceTokenBodySchema = z.object({
|
|
269
|
+
grant_type: z.literal("urn:ietf:params:oauth:grant-type:device_code").meta({ description: "The grant type for device flow" }),
|
|
270
|
+
device_code: z.string().meta({ description: "The device verification code" }),
|
|
271
|
+
client_id: z.string().meta({ description: "The client ID of the application" })
|
|
272
|
+
});
|
|
273
|
+
const deviceTokenErrorSchema = z.object({
|
|
274
|
+
error: z.enum([
|
|
275
|
+
"authorization_pending",
|
|
276
|
+
"slow_down",
|
|
277
|
+
"expired_token",
|
|
278
|
+
"access_denied",
|
|
279
|
+
"invalid_request",
|
|
280
|
+
"invalid_grant"
|
|
281
|
+
]).meta({ description: "Error code" }),
|
|
282
|
+
error_description: z.string().meta({ description: "Detailed error description" })
|
|
283
|
+
});
|
|
284
|
+
/**
|
|
285
|
+
* Runs the RFC 8628 polling and one-time claim state machine shared by every
|
|
286
|
+
* token issuer. Issuers supply ownership checks and grant-specific validation,
|
|
287
|
+
* but cannot bypass expiry, polling, denial, or atomic consumption.
|
|
288
|
+
*/
|
|
289
|
+
async function redeemDeviceCode(input) {
|
|
290
|
+
const deviceCodeRecord = await input.ctx.context.adapter.findOne({
|
|
291
|
+
model: "deviceCode",
|
|
292
|
+
where: [{
|
|
293
|
+
field: "deviceCode",
|
|
294
|
+
value: input.deviceCode
|
|
295
|
+
}]
|
|
296
|
+
});
|
|
297
|
+
if (!deviceCodeRecord) throw new APIError("BAD_REQUEST", {
|
|
298
|
+
error: "invalid_grant",
|
|
299
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.INVALID_DEVICE_CODE.message
|
|
300
|
+
});
|
|
301
|
+
const authorization = await input.authorizeRedemption(deviceCodeRecord);
|
|
302
|
+
if (deviceCodeRecord.lastPolledAt && deviceCodeRecord.pollingInterval) {
|
|
303
|
+
if (Date.now() - new Date(deviceCodeRecord.lastPolledAt).getTime() < deviceCodeRecord.pollingInterval) throw new APIError("BAD_REQUEST", {
|
|
304
|
+
error: "slow_down",
|
|
305
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.POLLING_TOO_FREQUENTLY.message
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
await input.ctx.context.adapter.update({
|
|
309
|
+
model: "deviceCode",
|
|
310
|
+
where: [{
|
|
311
|
+
field: "id",
|
|
312
|
+
value: deviceCodeRecord.id
|
|
313
|
+
}],
|
|
314
|
+
update: { lastPolledAt: /* @__PURE__ */ new Date() }
|
|
315
|
+
});
|
|
316
|
+
if (deviceCodeRecord.expiresAt < /* @__PURE__ */ new Date()) {
|
|
317
|
+
await input.ctx.context.adapter.delete({
|
|
318
|
+
model: "deviceCode",
|
|
319
|
+
where: [{
|
|
320
|
+
field: "id",
|
|
321
|
+
value: deviceCodeRecord.id
|
|
322
|
+
}]
|
|
323
|
+
});
|
|
324
|
+
throw new APIError("BAD_REQUEST", {
|
|
325
|
+
error: "expired_token",
|
|
326
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.EXPIRED_DEVICE_CODE.message
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
if (deviceCodeRecord.status === "pending") throw new APIError("BAD_REQUEST", {
|
|
330
|
+
error: "authorization_pending",
|
|
331
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.AUTHORIZATION_PENDING.message
|
|
332
|
+
});
|
|
333
|
+
if (deviceCodeRecord.status === "denied") {
|
|
334
|
+
await input.ctx.context.adapter.delete({
|
|
335
|
+
model: "deviceCode",
|
|
336
|
+
where: [{
|
|
337
|
+
field: "id",
|
|
338
|
+
value: deviceCodeRecord.id
|
|
339
|
+
}]
|
|
340
|
+
});
|
|
341
|
+
throw new APIError("BAD_REQUEST", {
|
|
342
|
+
error: "access_denied",
|
|
343
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.ACCESS_DENIED.message
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (deviceCodeRecord.status !== "approved" || !deviceCodeRecord.userId) throw new APIError("INTERNAL_SERVER_ERROR", {
|
|
347
|
+
error: "server_error",
|
|
348
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.INVALID_DEVICE_CODE_STATUS.message
|
|
349
|
+
});
|
|
350
|
+
const redemptionContext = await input.prepareRedemption(deviceCodeRecord, authorization.context);
|
|
351
|
+
const user = await input.ctx.context.internalAdapter.findUserById(deviceCodeRecord.userId);
|
|
352
|
+
if (!user) throw new APIError("INTERNAL_SERVER_ERROR", {
|
|
353
|
+
error: "server_error",
|
|
354
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.USER_NOT_FOUND.message
|
|
355
|
+
});
|
|
356
|
+
const claimedDeviceCode = await input.ctx.context.adapter.consumeOne({
|
|
357
|
+
model: "deviceCode",
|
|
358
|
+
where: [
|
|
359
|
+
{
|
|
360
|
+
field: "id",
|
|
361
|
+
value: deviceCodeRecord.id
|
|
362
|
+
},
|
|
363
|
+
authorization.ownershipWhere,
|
|
364
|
+
{
|
|
365
|
+
field: "status",
|
|
366
|
+
value: "approved"
|
|
367
|
+
}
|
|
368
|
+
]
|
|
369
|
+
});
|
|
370
|
+
if (!claimedDeviceCode?.userId) throw new APIError("BAD_REQUEST", {
|
|
371
|
+
error: "invalid_grant",
|
|
372
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.INVALID_DEVICE_CODE.message
|
|
373
|
+
});
|
|
374
|
+
return {
|
|
375
|
+
claimedDeviceCode,
|
|
376
|
+
authorizationContext: authorization.context,
|
|
377
|
+
redemptionContext,
|
|
378
|
+
user
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const deviceToken = (opts, grant) => createAuthEndpoint("/device/token", {
|
|
382
|
+
method: "POST",
|
|
383
|
+
body: deviceTokenBodySchema,
|
|
384
|
+
error: deviceTokenErrorSchema,
|
|
385
|
+
metadata: {
|
|
386
|
+
noStore: true,
|
|
387
|
+
openapi: {
|
|
388
|
+
description: `Exchange device code for access token
|
|
389
|
+
|
|
390
|
+
Follow [rfc8628#section-3.4](https://datatracker.ietf.org/doc/html/rfc8628#section-3.4)`,
|
|
391
|
+
responses: {
|
|
392
|
+
200: {
|
|
393
|
+
description: "Success",
|
|
394
|
+
content: { "application/json": { schema: {
|
|
395
|
+
type: "object",
|
|
396
|
+
properties: {
|
|
397
|
+
session: { $ref: "#/components/schemas/Session" },
|
|
398
|
+
user: { $ref: "#/components/schemas/User" }
|
|
399
|
+
}
|
|
400
|
+
} } }
|
|
401
|
+
},
|
|
402
|
+
400: {
|
|
403
|
+
description: "Error response",
|
|
404
|
+
content: { "application/json": { schema: {
|
|
405
|
+
type: "object",
|
|
406
|
+
properties: {
|
|
407
|
+
error: {
|
|
408
|
+
type: "string",
|
|
409
|
+
enum: [
|
|
410
|
+
"authorization_pending",
|
|
411
|
+
"slow_down",
|
|
412
|
+
"expired_token",
|
|
413
|
+
"access_denied",
|
|
414
|
+
"invalid_request",
|
|
415
|
+
"invalid_grant"
|
|
416
|
+
]
|
|
417
|
+
},
|
|
418
|
+
error_description: { type: "string" }
|
|
419
|
+
}
|
|
420
|
+
} } }
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}, async (ctx) => {
|
|
426
|
+
const { device_code, client_id } = ctx.body;
|
|
427
|
+
if (opts.validateClient) {
|
|
428
|
+
if (!await opts.validateClient(client_id)) throw new APIError("BAD_REQUEST", {
|
|
429
|
+
error: "invalid_grant",
|
|
430
|
+
error_description: "Invalid client ID"
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
const { claimedDeviceCode, user } = await redeemDeviceCode({
|
|
434
|
+
ctx,
|
|
435
|
+
deviceCode: device_code,
|
|
436
|
+
authorizeRedemption: async (deviceCodeRecord) => {
|
|
437
|
+
if (deviceCodeRecord.clientId && deviceCodeRecord.clientId !== client_id) throw new APIError("BAD_REQUEST", {
|
|
438
|
+
error: "invalid_grant",
|
|
439
|
+
error_description: "Client ID mismatch"
|
|
440
|
+
});
|
|
441
|
+
await grant?.assertSessionRedemption({
|
|
442
|
+
ctx,
|
|
443
|
+
deviceCode: deviceCodeRecord
|
|
444
|
+
});
|
|
445
|
+
return {
|
|
446
|
+
ownershipWhere: {
|
|
447
|
+
field: "clientId",
|
|
448
|
+
value: client_id
|
|
449
|
+
},
|
|
450
|
+
context: void 0
|
|
451
|
+
};
|
|
452
|
+
},
|
|
453
|
+
prepareRedemption: () => void 0
|
|
454
|
+
});
|
|
455
|
+
const session = await ctx.context.internalAdapter.createSession(user.id);
|
|
456
|
+
if (!session) throw new APIError("INTERNAL_SERVER_ERROR", {
|
|
457
|
+
error: "server_error",
|
|
458
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.FAILED_TO_CREATE_SESSION.message
|
|
459
|
+
});
|
|
460
|
+
ctx.context.setNewSession({
|
|
461
|
+
session,
|
|
462
|
+
user
|
|
463
|
+
});
|
|
464
|
+
if (ctx.context.options.secondaryStorage) await ctx.context.secondaryStorage?.set(session.token, JSON.stringify({
|
|
465
|
+
user,
|
|
466
|
+
session
|
|
467
|
+
}), Math.floor((new Date(session.expiresAt).getTime() - Date.now()) / 1e3));
|
|
468
|
+
ctx.setHeader("Cache-Control", "no-store");
|
|
469
|
+
ctx.setHeader("Pragma", "no-cache");
|
|
470
|
+
return ctx.json({
|
|
471
|
+
access_token: session.token,
|
|
472
|
+
token_type: "Bearer",
|
|
473
|
+
expires_in: Math.floor((new Date(session.expiresAt).getTime() - Date.now()) / 1e3),
|
|
474
|
+
scope: claimedDeviceCode.scope || ""
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
const deviceVerify = (grant) => createAuthEndpoint("/device", {
|
|
478
|
+
method: "GET",
|
|
479
|
+
query: z.object({ user_code: z.string().meta({ description: "The user code to verify" }) }),
|
|
480
|
+
error: z.object({
|
|
481
|
+
error: z.enum(["invalid_request"]).meta({ description: "Error code" }),
|
|
482
|
+
error_description: z.string().meta({ description: "Detailed error description" })
|
|
483
|
+
}),
|
|
484
|
+
metadata: { openapi: {
|
|
485
|
+
description: "Verify user code and get device authorization status",
|
|
486
|
+
responses: { 200: {
|
|
487
|
+
description: "Device authorization status",
|
|
488
|
+
content: { "application/json": { schema: {
|
|
489
|
+
type: "object",
|
|
490
|
+
properties: {
|
|
491
|
+
user_code: {
|
|
492
|
+
type: "string",
|
|
493
|
+
description: "The user code to verify"
|
|
494
|
+
},
|
|
495
|
+
status: {
|
|
496
|
+
type: "string",
|
|
497
|
+
enum: [
|
|
498
|
+
"pending",
|
|
499
|
+
"approved",
|
|
500
|
+
"denied"
|
|
501
|
+
],
|
|
502
|
+
description: "Current status of the device authorization"
|
|
503
|
+
},
|
|
504
|
+
client_id: {
|
|
505
|
+
type: "string",
|
|
506
|
+
description: "The client requesting authorization, returned only to the authenticated user who owns this request"
|
|
507
|
+
},
|
|
508
|
+
scope: {
|
|
509
|
+
type: "string",
|
|
510
|
+
description: "The requested scopes, returned only to the authenticated user who owns this request"
|
|
511
|
+
},
|
|
512
|
+
...grant?.verificationOpenAPIProperties
|
|
513
|
+
}
|
|
514
|
+
} } }
|
|
515
|
+
} }
|
|
516
|
+
} }
|
|
517
|
+
}, async (ctx) => {
|
|
518
|
+
const { user_code } = ctx.query;
|
|
519
|
+
const deviceCodeRecord = await findDeviceCodeByUserCode(ctx, user_code);
|
|
520
|
+
if (!deviceCodeRecord) throw new APIError("BAD_REQUEST", {
|
|
521
|
+
error: "invalid_request",
|
|
522
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.INVALID_USER_CODE.message
|
|
523
|
+
});
|
|
524
|
+
if (deviceCodeRecord.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("BAD_REQUEST", {
|
|
525
|
+
error: "expired_token",
|
|
526
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.EXPIRED_USER_CODE.message
|
|
527
|
+
});
|
|
528
|
+
const session = await getSessionFromCtx(ctx);
|
|
529
|
+
if (session?.user?.id && !deviceCodeRecord.userId && deviceCodeRecord.status === "pending") {
|
|
530
|
+
if (await ctx.context.adapter.incrementOne({
|
|
531
|
+
model: "deviceCode",
|
|
532
|
+
where: [
|
|
533
|
+
{
|
|
534
|
+
field: "id",
|
|
535
|
+
value: deviceCodeRecord.id
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
field: "status",
|
|
539
|
+
value: "pending"
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
field: "userId",
|
|
543
|
+
operator: "eq",
|
|
544
|
+
value: null
|
|
545
|
+
}
|
|
546
|
+
],
|
|
547
|
+
increment: {},
|
|
548
|
+
set: { userId: session.user.id }
|
|
549
|
+
})) deviceCodeRecord.userId = session.user.id;
|
|
550
|
+
}
|
|
551
|
+
const canReviewRequest = session?.user.id !== void 0 && deviceCodeRecord.userId === session.user.id;
|
|
552
|
+
const grantContext = canReviewRequest ? grant?.getVerificationContext(deviceCodeRecord) : void 0;
|
|
553
|
+
const response = {
|
|
554
|
+
...canReviewRequest ? grantContext : void 0,
|
|
555
|
+
user_code,
|
|
556
|
+
status: deviceCodeRecord.status,
|
|
557
|
+
...canReviewRequest ? {
|
|
558
|
+
client_id: deviceCodeRecord.clientId,
|
|
559
|
+
scope: deviceCodeRecord.scope
|
|
560
|
+
} : {}
|
|
561
|
+
};
|
|
562
|
+
return ctx.json(response);
|
|
563
|
+
});
|
|
564
|
+
const deviceApprove = createAuthEndpoint("/device/approve", {
|
|
565
|
+
method: "POST",
|
|
566
|
+
body: z.object({ userCode: z.string().meta({ description: "The user code to approve" }) }),
|
|
567
|
+
error: z.object({
|
|
568
|
+
error: z.enum([
|
|
569
|
+
"invalid_request",
|
|
570
|
+
"expired_token",
|
|
571
|
+
"device_code_already_processed",
|
|
572
|
+
"unauthorized",
|
|
573
|
+
"access_denied"
|
|
574
|
+
]).meta({ description: "Error code" }),
|
|
575
|
+
error_description: z.string().meta({ description: "Detailed error description" })
|
|
576
|
+
}),
|
|
577
|
+
requireHeaders: true,
|
|
578
|
+
metadata: { openapi: {
|
|
579
|
+
description: "Approve device authorization",
|
|
580
|
+
responses: { 200: {
|
|
581
|
+
description: "Success",
|
|
582
|
+
content: { "application/json": { schema: {
|
|
583
|
+
type: "object",
|
|
584
|
+
properties: { success: { type: "boolean" } }
|
|
585
|
+
} } }
|
|
586
|
+
} }
|
|
587
|
+
} }
|
|
588
|
+
}, async (ctx) => {
|
|
589
|
+
const session = await getSessionFromCtx(ctx);
|
|
590
|
+
if (!session) throw new APIError("UNAUTHORIZED", {
|
|
591
|
+
error: "unauthorized",
|
|
592
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.AUTHENTICATION_REQUIRED.message
|
|
593
|
+
});
|
|
594
|
+
const { userCode } = ctx.body;
|
|
595
|
+
const deviceCodeRecord = await findDeviceCodeByUserCode(ctx, userCode);
|
|
596
|
+
if (!deviceCodeRecord) throw new APIError("BAD_REQUEST", {
|
|
597
|
+
error: "invalid_request",
|
|
598
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.INVALID_USER_CODE.message
|
|
599
|
+
});
|
|
600
|
+
if (deviceCodeRecord.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("BAD_REQUEST", {
|
|
601
|
+
error: "expired_token",
|
|
602
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.EXPIRED_USER_CODE.message
|
|
603
|
+
});
|
|
604
|
+
if (deviceCodeRecord.status !== "pending") throw new APIError("BAD_REQUEST", {
|
|
605
|
+
error: "invalid_request",
|
|
606
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_ALREADY_PROCESSED.message
|
|
607
|
+
});
|
|
608
|
+
if (!deviceCodeRecord.userId) throw new APIError("BAD_REQUEST", {
|
|
609
|
+
error: "invalid_request",
|
|
610
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_NOT_CLAIMED.message
|
|
611
|
+
});
|
|
612
|
+
if (deviceCodeRecord.userId !== session.user.id) throw new APIError("FORBIDDEN", {
|
|
613
|
+
error: "access_denied",
|
|
614
|
+
error_description: "You are not authorized to approve this device authorization"
|
|
615
|
+
});
|
|
616
|
+
await ctx.context.adapter.update({
|
|
617
|
+
model: "deviceCode",
|
|
618
|
+
where: [{
|
|
619
|
+
field: "id",
|
|
620
|
+
value: deviceCodeRecord.id
|
|
621
|
+
}],
|
|
622
|
+
update: {
|
|
623
|
+
status: "approved",
|
|
624
|
+
userId: session.user.id
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
return ctx.json({ success: true });
|
|
628
|
+
});
|
|
629
|
+
const deviceDeny = createAuthEndpoint("/device/deny", {
|
|
630
|
+
method: "POST",
|
|
631
|
+
body: z.object({ userCode: z.string().meta({ description: "The user code to deny" }) }),
|
|
632
|
+
error: z.object({
|
|
633
|
+
error: z.enum([
|
|
634
|
+
"invalid_request",
|
|
635
|
+
"expired_token",
|
|
636
|
+
"unauthorized",
|
|
637
|
+
"access_denied"
|
|
638
|
+
]).meta({ description: "Error code" }),
|
|
639
|
+
error_description: z.string().meta({ description: "Detailed error description" })
|
|
640
|
+
}),
|
|
641
|
+
requireHeaders: true,
|
|
642
|
+
metadata: { openapi: {
|
|
643
|
+
description: "Deny device authorization",
|
|
644
|
+
responses: { 200: {
|
|
645
|
+
description: "Success",
|
|
646
|
+
content: { "application/json": { schema: {
|
|
647
|
+
type: "object",
|
|
648
|
+
properties: { success: { type: "boolean" } }
|
|
649
|
+
} } }
|
|
650
|
+
} }
|
|
651
|
+
} }
|
|
652
|
+
}, async (ctx) => {
|
|
653
|
+
const session = await getSessionFromCtx(ctx);
|
|
654
|
+
if (!session) throw new APIError("UNAUTHORIZED", {
|
|
655
|
+
error: "unauthorized",
|
|
656
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.AUTHENTICATION_REQUIRED.message
|
|
657
|
+
});
|
|
658
|
+
const { userCode } = ctx.body;
|
|
659
|
+
const deviceCodeRecord = await findDeviceCodeByUserCode(ctx, userCode);
|
|
660
|
+
if (!deviceCodeRecord) throw new APIError("BAD_REQUEST", {
|
|
661
|
+
error: "invalid_request",
|
|
662
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.INVALID_USER_CODE.message
|
|
663
|
+
});
|
|
664
|
+
if (deviceCodeRecord.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("BAD_REQUEST", {
|
|
665
|
+
error: "expired_token",
|
|
666
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.EXPIRED_USER_CODE.message
|
|
667
|
+
});
|
|
668
|
+
if (deviceCodeRecord.status !== "pending") throw new APIError("BAD_REQUEST", {
|
|
669
|
+
error: "invalid_request",
|
|
670
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_ALREADY_PROCESSED.message
|
|
671
|
+
});
|
|
672
|
+
if (!deviceCodeRecord.userId) throw new APIError("BAD_REQUEST", {
|
|
673
|
+
error: "invalid_request",
|
|
674
|
+
error_description: DEVICE_AUTHORIZATION_ERROR_CODES.DEVICE_CODE_NOT_CLAIMED.message
|
|
675
|
+
});
|
|
676
|
+
if (deviceCodeRecord.userId !== session.user.id) throw new APIError("FORBIDDEN", {
|
|
677
|
+
error: "access_denied",
|
|
678
|
+
error_description: "You are not authorized to deny this device authorization"
|
|
679
|
+
});
|
|
680
|
+
await ctx.context.adapter.update({
|
|
681
|
+
model: "deviceCode",
|
|
682
|
+
where: [{
|
|
683
|
+
field: "id",
|
|
684
|
+
value: deviceCodeRecord.id
|
|
685
|
+
}],
|
|
686
|
+
update: {
|
|
687
|
+
status: "denied",
|
|
688
|
+
userId: session.user.id
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
return ctx.json({ success: true });
|
|
692
|
+
});
|
|
693
|
+
/**
|
|
694
|
+
* @internal
|
|
695
|
+
*/
|
|
696
|
+
const buildVerificationUris = (verificationUri, baseURL, userCode) => {
|
|
697
|
+
const uri = verificationUri || "/device";
|
|
698
|
+
let verificationUrl;
|
|
699
|
+
try {
|
|
700
|
+
verificationUrl = new URL(uri);
|
|
701
|
+
} catch {
|
|
702
|
+
verificationUrl = new URL(uri, baseURL);
|
|
703
|
+
}
|
|
704
|
+
const verificationUriCompleteUrl = new URL(verificationUrl);
|
|
705
|
+
verificationUriCompleteUrl.searchParams.set("user_code", userCode);
|
|
706
|
+
return {
|
|
707
|
+
verificationUri: verificationUrl.toString(),
|
|
708
|
+
verificationUriComplete: verificationUriCompleteUrl.toString()
|
|
709
|
+
};
|
|
710
|
+
};
|
|
711
|
+
/**
|
|
712
|
+
* @internal
|
|
713
|
+
*/
|
|
714
|
+
const defaultGenerateDeviceCode = (length) => {
|
|
715
|
+
return generateRandomString(length, "a-z", "A-Z", "0-9");
|
|
716
|
+
};
|
|
717
|
+
/**
|
|
718
|
+
* @internal
|
|
719
|
+
*/
|
|
720
|
+
const defaultGenerateUserCode = (length) => {
|
|
721
|
+
const chars = new Uint8Array(length);
|
|
722
|
+
return Array.from(crypto.getRandomValues(chars)).map((byte) => defaultCharset[byte % 32]).join("");
|
|
723
|
+
};
|
|
724
|
+
//#endregion
|
|
725
|
+
export { deviceApprove, deviceCode, deviceDeny, deviceToken, deviceVerify, redeemDeviceCode };
|