@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,951 @@
|
|
|
1
|
+
import { getSessionDefaultFields, parseSessionOutput, parseUserOutput } from "./schema.mjs";
|
|
2
|
+
import { getDate } from "../utils/date.mjs";
|
|
3
|
+
import { assertValidUserInfo, assertValidUserInfoSource } from "../utils/validate-user-info.mjs";
|
|
4
|
+
import { getStorageOption, processIdentifier } from "./verification-token-storage.mjs";
|
|
5
|
+
import { getWithHooks } from "./with-hooks.mjs";
|
|
6
|
+
import { getCurrentAdapter, getCurrentAuthEndpointContext, queueAfterTransactionHook, runWithTransaction, tryGetCurrentAuthEndpointContext } from "@better-auth/core/context";
|
|
7
|
+
import { APIError, BetterAuthError } from "@better-auth/core/error";
|
|
8
|
+
import { generateId } from "@better-auth/core/utils/id";
|
|
9
|
+
import { safeJSONParse } from "@better-auth/core/utils/json";
|
|
10
|
+
import { getIP } from "@better-auth/core/utils/ip";
|
|
11
|
+
import { base64Url } from "@better-auth/utils/base64";
|
|
12
|
+
import { createHash } from "@better-auth/utils/hash";
|
|
13
|
+
//#region src/db/internal-adapter.ts
|
|
14
|
+
function getTTLSeconds(expiresAt, now = Date.now()) {
|
|
15
|
+
const expiresMs = typeof expiresAt === "number" ? expiresAt : expiresAt.getTime();
|
|
16
|
+
return Math.max(Math.floor((expiresMs - now) / 1e3), 0);
|
|
17
|
+
}
|
|
18
|
+
const createInternalAdapter = (adapter, ctx) => {
|
|
19
|
+
const logger = ctx.logger;
|
|
20
|
+
const options = ctx.options;
|
|
21
|
+
const secondaryStorage = options.secondaryStorage;
|
|
22
|
+
const verificationConsumeLocks = /* @__PURE__ */ new Map();
|
|
23
|
+
const sessionExpiration = options.session?.expiresIn || 3600 * 24 * 7;
|
|
24
|
+
const { createWithHooks, updateWithHooks, updateManyWithHooks, deleteWithHooks, deleteManyWithHooks, consumeOneWithHooks } = getWithHooks(adapter, ctx);
|
|
25
|
+
/**
|
|
26
|
+
* Ends the live session rows matched by `where` without physically deleting
|
|
27
|
+
* them.
|
|
28
|
+
*
|
|
29
|
+
* Used for `secondaryStorage` + `preserveSessionInDatabase`, where the row is
|
|
30
|
+
* kept for audit. The session-delete hooks still run (so OAuth token
|
|
31
|
+
* revocation and back-channel logout fire on session end), and the preserved
|
|
32
|
+
* row's `expiresAt` is set to now so every liveness check that keys off the
|
|
33
|
+
* session row (introspection, `/userinfo`) treats it as ended.
|
|
34
|
+
*
|
|
35
|
+
* Matching is restricted to still-live rows. The preserved row outlives the
|
|
36
|
+
* session, so without this a later delete call (a repeated `deleteSession`,
|
|
37
|
+
* or `deleteUserSessions` sweeping a user's accumulated preserved rows) would
|
|
38
|
+
* re-match it and re-fire the hooks, re-dispatching back-channel logout.
|
|
39
|
+
*/
|
|
40
|
+
const endPreservedSessions = (where) => {
|
|
41
|
+
const liveSessions = [...where, {
|
|
42
|
+
field: "expiresAt",
|
|
43
|
+
value: /* @__PURE__ */ new Date(),
|
|
44
|
+
operator: "gt"
|
|
45
|
+
}];
|
|
46
|
+
return deleteManyWithHooks(liveSessions, "session", {
|
|
47
|
+
fn: async () => {
|
|
48
|
+
await (await getCurrentAdapter(adapter)).updateMany({
|
|
49
|
+
model: "session",
|
|
50
|
+
where: liveSessions,
|
|
51
|
+
update: { expiresAt: /* @__PURE__ */ new Date() }
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
executeMainFn: false
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
async function refreshUserSessions(user) {
|
|
58
|
+
if (!secondaryStorage) return;
|
|
59
|
+
const listRaw = await secondaryStorage.get(`active-sessions-${user.id}`);
|
|
60
|
+
if (!listRaw) return;
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const validSessions = (safeJSONParse(listRaw) || []).filter((s) => s.expiresAt > now);
|
|
63
|
+
await Promise.all(validSessions.map(async ({ token }) => {
|
|
64
|
+
const cached = await secondaryStorage.get(token);
|
|
65
|
+
if (!cached) return;
|
|
66
|
+
const parsed = safeJSONParse(cached);
|
|
67
|
+
if (!parsed) return;
|
|
68
|
+
const sessionTTL = getTTLSeconds(parsed.session.expiresAt, now);
|
|
69
|
+
await secondaryStorage.set(token, JSON.stringify({
|
|
70
|
+
session: parsed.session,
|
|
71
|
+
user
|
|
72
|
+
}), Math.floor(sessionTTL));
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
async function getActiveSessionReferences(userId) {
|
|
76
|
+
if (!secondaryStorage) return [];
|
|
77
|
+
const activeSessions = await secondaryStorage.get(`active-sessions-${userId}`);
|
|
78
|
+
return activeSessions ? safeJSONParse(activeSessions) || [] : [];
|
|
79
|
+
}
|
|
80
|
+
async function deleteCachedUserSessions(userId, sessionReferences) {
|
|
81
|
+
if (!secondaryStorage) return;
|
|
82
|
+
const deletedTokens = new Set(sessionReferences.map((session) => session.token));
|
|
83
|
+
for (const { token } of sessionReferences) await secondaryStorage.delete(token);
|
|
84
|
+
const activeSessionsKey = `active-sessions-${userId}`;
|
|
85
|
+
const currentSessionReferences = await getActiveSessionReferences(userId);
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
const remainingSessionReferences = currentSessionReferences.filter((session) => session.expiresAt > now && !deletedTokens.has(session.token));
|
|
88
|
+
remainingSessionReferences.sort((a, b) => a.expiresAt - b.expiresAt);
|
|
89
|
+
const furthestExpiration = remainingSessionReferences.at(-1)?.expiresAt;
|
|
90
|
+
if (furthestExpiration) {
|
|
91
|
+
await secondaryStorage.set(activeSessionsKey, JSON.stringify(remainingSessionReferences), getTTLSeconds(furthestExpiration, now));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await secondaryStorage.delete(activeSessionsKey);
|
|
95
|
+
}
|
|
96
|
+
async function queueCachedUserSessionDeletion(userId, sessionReferences) {
|
|
97
|
+
if (!secondaryStorage) return;
|
|
98
|
+
const references = sessionReferences ?? await getActiveSessionReferences(userId);
|
|
99
|
+
await queueAfterTransactionHook(() => deleteCachedUserSessions(userId, references), { onError(error) {
|
|
100
|
+
logger.error("Failed to delete committed user sessions from secondary storage", error);
|
|
101
|
+
} });
|
|
102
|
+
}
|
|
103
|
+
async function withVerificationConsumeLock(key, fn) {
|
|
104
|
+
const previous = verificationConsumeLocks.get(key) ?? Promise.resolve();
|
|
105
|
+
let release;
|
|
106
|
+
const current = new Promise((resolve) => {
|
|
107
|
+
release = resolve;
|
|
108
|
+
});
|
|
109
|
+
const next = previous.catch(() => {}).then(() => current);
|
|
110
|
+
verificationConsumeLocks.set(key, next);
|
|
111
|
+
await previous.catch(() => {});
|
|
112
|
+
try {
|
|
113
|
+
return await fn();
|
|
114
|
+
} finally {
|
|
115
|
+
release();
|
|
116
|
+
if (verificationConsumeLocks.get(key) === next) verificationConsumeLocks.delete(key);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
createOAuthUser: async (user, account) => {
|
|
121
|
+
return runWithTransaction(adapter, async () => {
|
|
122
|
+
const createdUser = await createWithHooks({
|
|
123
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
124
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
125
|
+
...user,
|
|
126
|
+
email: user.email?.toLowerCase()
|
|
127
|
+
}, "user", void 0);
|
|
128
|
+
if (!createdUser) throw new APIError("BAD_REQUEST", { message: "Failed to create user" });
|
|
129
|
+
return {
|
|
130
|
+
user: createdUser,
|
|
131
|
+
account: await createWithHooks({
|
|
132
|
+
...account,
|
|
133
|
+
userId: createdUser.id,
|
|
134
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
135
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
136
|
+
}, "account", void 0)
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
createUser: async (user, source) => {
|
|
141
|
+
const data = {
|
|
142
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
143
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
144
|
+
...user,
|
|
145
|
+
email: user.email?.toLowerCase()
|
|
146
|
+
};
|
|
147
|
+
if (options.user?.validateUserInfo) {
|
|
148
|
+
const validationSource = {
|
|
149
|
+
...source,
|
|
150
|
+
action: "create-user"
|
|
151
|
+
};
|
|
152
|
+
assertValidUserInfoSource(validationSource);
|
|
153
|
+
let endpointContext;
|
|
154
|
+
try {
|
|
155
|
+
endpointContext = getCurrentAuthEndpointContext();
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.error("Unable to run validateUserInfo: missing endpoint context", error);
|
|
158
|
+
throw new APIError("FORBIDDEN", {
|
|
159
|
+
code: "validation_context_missing",
|
|
160
|
+
message: "User validation requires an endpoint context"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
await assertValidUserInfo(endpointContext, {
|
|
164
|
+
user: data,
|
|
165
|
+
source: validationSource
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return await createWithHooks(data, "user", void 0);
|
|
169
|
+
},
|
|
170
|
+
createAccount: async (account) => {
|
|
171
|
+
return await createWithHooks({
|
|
172
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
173
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
174
|
+
...account
|
|
175
|
+
}, "account", void 0);
|
|
176
|
+
},
|
|
177
|
+
listSessions: async (userId, options) => {
|
|
178
|
+
if (secondaryStorage) {
|
|
179
|
+
const currentList = await secondaryStorage.get(`active-sessions-${userId}`);
|
|
180
|
+
if (!currentList) return [];
|
|
181
|
+
const list = safeJSONParse(currentList) || [];
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
const seenTokens = /* @__PURE__ */ new Set();
|
|
184
|
+
const sessions = [];
|
|
185
|
+
for (const { token, expiresAt } of list) {
|
|
186
|
+
if (expiresAt <= now || seenTokens.has(token)) continue;
|
|
187
|
+
seenTokens.add(token);
|
|
188
|
+
const data = await secondaryStorage.get(token);
|
|
189
|
+
if (!data) continue;
|
|
190
|
+
try {
|
|
191
|
+
const parsed = typeof data === "string" ? JSON.parse(data) : data;
|
|
192
|
+
if (!parsed?.session) continue;
|
|
193
|
+
sessions.push(parseSessionOutput(ctx.options, {
|
|
194
|
+
...parsed.session,
|
|
195
|
+
expiresAt: new Date(parsed.session.expiresAt)
|
|
196
|
+
}));
|
|
197
|
+
} catch {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return sessions;
|
|
202
|
+
}
|
|
203
|
+
return await (await getCurrentAdapter(adapter)).findMany({
|
|
204
|
+
model: "session",
|
|
205
|
+
where: [{
|
|
206
|
+
field: "userId",
|
|
207
|
+
value: userId
|
|
208
|
+
}, ...options?.onlyActiveSessions ? [{
|
|
209
|
+
field: "expiresAt",
|
|
210
|
+
value: /* @__PURE__ */ new Date(),
|
|
211
|
+
operator: "gt"
|
|
212
|
+
}] : []]
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
listUsers: async (limit, offset, sortBy, where) => {
|
|
216
|
+
return await (await getCurrentAdapter(adapter)).findMany({
|
|
217
|
+
model: "user",
|
|
218
|
+
limit,
|
|
219
|
+
offset,
|
|
220
|
+
sortBy,
|
|
221
|
+
where
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
countTotalUsers: async (where) => {
|
|
225
|
+
const total = await (await getCurrentAdapter(adapter)).count({
|
|
226
|
+
model: "user",
|
|
227
|
+
where
|
|
228
|
+
});
|
|
229
|
+
if (typeof total === "string") return parseInt(total);
|
|
230
|
+
return total;
|
|
231
|
+
},
|
|
232
|
+
deleteUser: async (userId) => {
|
|
233
|
+
const sessionReferences = await getActiveSessionReferences(userId);
|
|
234
|
+
if (!secondaryStorage || options.session?.storeSessionInDatabase) await deleteManyWithHooks([{
|
|
235
|
+
field: "userId",
|
|
236
|
+
value: userId
|
|
237
|
+
}], "session", void 0);
|
|
238
|
+
await deleteManyWithHooks([{
|
|
239
|
+
field: "userId",
|
|
240
|
+
value: userId
|
|
241
|
+
}], "account", void 0);
|
|
242
|
+
if (await deleteWithHooks([{
|
|
243
|
+
field: "id",
|
|
244
|
+
value: userId
|
|
245
|
+
}], "user", void 0) !== null) await queueCachedUserSessionDeletion(userId, sessionReferences);
|
|
246
|
+
},
|
|
247
|
+
createSession: async (userId, dontRememberMe, override, overrideAll, storageOptions) => {
|
|
248
|
+
const headers = await (async () => {
|
|
249
|
+
const ctx = tryGetCurrentAuthEndpointContext();
|
|
250
|
+
return ctx?.headers || ctx?.request?.headers;
|
|
251
|
+
})();
|
|
252
|
+
const storeInDb = options.session?.storeSessionInDatabase;
|
|
253
|
+
const databaseSessionFallbackEnabled = storeInDb === true && options.session?.preserveSessionInDatabase !== true;
|
|
254
|
+
const { id: _, ...rest } = override || {};
|
|
255
|
+
let sessionId;
|
|
256
|
+
if (secondaryStorage && !storeInDb) {
|
|
257
|
+
const generatedId = ctx.generateId({ model: "session" });
|
|
258
|
+
sessionId = generatedId !== false ? generatedId : generateId();
|
|
259
|
+
}
|
|
260
|
+
const defaultAdditionalFields = getSessionDefaultFields(options);
|
|
261
|
+
const data = {
|
|
262
|
+
...sessionId ? { id: sessionId } : {},
|
|
263
|
+
ipAddress: headers ? getIP(headers, options) || "" : "",
|
|
264
|
+
userAgent: headers?.get("user-agent") || "",
|
|
265
|
+
...rest,
|
|
266
|
+
/**
|
|
267
|
+
* If the user doesn't want to be remembered
|
|
268
|
+
* set the session to expire in 1 day.
|
|
269
|
+
* The cookie will be set to expire at the end of the session
|
|
270
|
+
*/
|
|
271
|
+
expiresAt: dontRememberMe ? getDate(3600 * 24, "sec") : getDate(sessionExpiration, "sec"),
|
|
272
|
+
userId,
|
|
273
|
+
token: generateId(32),
|
|
274
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
275
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
276
|
+
...defaultAdditionalFields,
|
|
277
|
+
...overrideAll ? rest : {}
|
|
278
|
+
};
|
|
279
|
+
const mirrorSessionToSecondaryStorage = async (sessionData) => {
|
|
280
|
+
if (!secondaryStorage) return sessionData;
|
|
281
|
+
const currentList = await secondaryStorage.get(`active-sessions-${userId}`);
|
|
282
|
+
let list = [];
|
|
283
|
+
const now = Date.now();
|
|
284
|
+
if (currentList) {
|
|
285
|
+
list = safeJSONParse(currentList) || [];
|
|
286
|
+
list = list.filter((session) => session.expiresAt > now && session.token !== data.token);
|
|
287
|
+
}
|
|
288
|
+
const sorted = [...list, {
|
|
289
|
+
token: data.token,
|
|
290
|
+
expiresAt: data.expiresAt.getTime()
|
|
291
|
+
}].sort((a, b) => a.expiresAt - b.expiresAt);
|
|
292
|
+
const furthestSessionTTL = getTTLSeconds(sorted.at(-1)?.expiresAt ?? data.expiresAt.getTime(), now);
|
|
293
|
+
if (furthestSessionTTL > 0) await secondaryStorage.set(`active-sessions-${userId}`, JSON.stringify(sorted), furthestSessionTTL);
|
|
294
|
+
const user = await (await getCurrentAdapter(adapter)).findOne({
|
|
295
|
+
model: "user",
|
|
296
|
+
where: [{
|
|
297
|
+
field: "id",
|
|
298
|
+
value: userId
|
|
299
|
+
}]
|
|
300
|
+
});
|
|
301
|
+
const sessionTTL = getTTLSeconds(data.expiresAt, now);
|
|
302
|
+
if (sessionTTL > 0) await secondaryStorage.set(data.token, JSON.stringify({
|
|
303
|
+
session: sessionData,
|
|
304
|
+
user
|
|
305
|
+
}), sessionTTL);
|
|
306
|
+
return sessionData;
|
|
307
|
+
};
|
|
308
|
+
const res = await createWithHooks(data, "session", secondaryStorage ? {
|
|
309
|
+
fn: async (sessionData) => {
|
|
310
|
+
return storageOptions?.deferSecondaryStorageWrites ? sessionData : mirrorSessionToSecondaryStorage(sessionData);
|
|
311
|
+
},
|
|
312
|
+
executeMainFn: storeInDb
|
|
313
|
+
} : void 0);
|
|
314
|
+
if (secondaryStorage && storageOptions?.deferSecondaryStorageWrites && res) await queueAfterTransactionHook(async () => {
|
|
315
|
+
await mirrorSessionToSecondaryStorage(res);
|
|
316
|
+
}, databaseSessionFallbackEnabled ? { onError(error) {
|
|
317
|
+
logger.error("Failed to mirror committed session to secondary storage", error);
|
|
318
|
+
} } : void 0);
|
|
319
|
+
return res;
|
|
320
|
+
},
|
|
321
|
+
findSession: async (token) => {
|
|
322
|
+
if (secondaryStorage) {
|
|
323
|
+
const sessionStringified = await secondaryStorage.get(token);
|
|
324
|
+
if (!sessionStringified && (!options.session?.storeSessionInDatabase || ctx.options.session?.preserveSessionInDatabase)) return null;
|
|
325
|
+
if (sessionStringified) {
|
|
326
|
+
const s = safeJSONParse(sessionStringified);
|
|
327
|
+
if (!s) return null;
|
|
328
|
+
return {
|
|
329
|
+
session: parseSessionOutput(ctx.options, {
|
|
330
|
+
...s.session,
|
|
331
|
+
expiresAt: new Date(s.session.expiresAt),
|
|
332
|
+
createdAt: new Date(s.session.createdAt),
|
|
333
|
+
updatedAt: new Date(s.session.updatedAt)
|
|
334
|
+
}),
|
|
335
|
+
user: parseUserOutput(ctx.options, {
|
|
336
|
+
...s.user,
|
|
337
|
+
createdAt: new Date(s.user.createdAt),
|
|
338
|
+
updatedAt: new Date(s.user.updatedAt)
|
|
339
|
+
})
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const result = await (await getCurrentAdapter(adapter)).findOne({
|
|
344
|
+
model: "session",
|
|
345
|
+
where: [{
|
|
346
|
+
value: token,
|
|
347
|
+
field: "token"
|
|
348
|
+
}],
|
|
349
|
+
join: { user: true }
|
|
350
|
+
});
|
|
351
|
+
if (!result) return null;
|
|
352
|
+
const { user, ...session } = result;
|
|
353
|
+
if (!user) return null;
|
|
354
|
+
return {
|
|
355
|
+
session: parseSessionOutput(ctx.options, session),
|
|
356
|
+
user: parseUserOutput(ctx.options, user)
|
|
357
|
+
};
|
|
358
|
+
},
|
|
359
|
+
findSessions: async (sessionTokens, options) => {
|
|
360
|
+
if (secondaryStorage) {
|
|
361
|
+
const sessions = [];
|
|
362
|
+
for (const sessionToken of sessionTokens) {
|
|
363
|
+
const sessionStringified = await secondaryStorage.get(sessionToken);
|
|
364
|
+
if (sessionStringified) try {
|
|
365
|
+
const s = typeof sessionStringified === "string" ? JSON.parse(sessionStringified) : sessionStringified;
|
|
366
|
+
if (!s) continue;
|
|
367
|
+
const expiresAt = new Date(s.session.expiresAt);
|
|
368
|
+
if (options?.onlyActiveSessions && expiresAt <= /* @__PURE__ */ new Date()) continue;
|
|
369
|
+
const session = {
|
|
370
|
+
session: {
|
|
371
|
+
...s.session,
|
|
372
|
+
expiresAt: new Date(s.session.expiresAt)
|
|
373
|
+
},
|
|
374
|
+
user: {
|
|
375
|
+
...s.user,
|
|
376
|
+
createdAt: new Date(s.user.createdAt),
|
|
377
|
+
updatedAt: new Date(s.user.updatedAt)
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
sessions.push(session);
|
|
381
|
+
} catch {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return sessions;
|
|
386
|
+
}
|
|
387
|
+
const sessions = await (await getCurrentAdapter(adapter)).findMany({
|
|
388
|
+
model: "session",
|
|
389
|
+
where: [{
|
|
390
|
+
field: "token",
|
|
391
|
+
value: sessionTokens,
|
|
392
|
+
operator: "in"
|
|
393
|
+
}, ...options?.onlyActiveSessions ? [{
|
|
394
|
+
field: "expiresAt",
|
|
395
|
+
value: /* @__PURE__ */ new Date(),
|
|
396
|
+
operator: "gt"
|
|
397
|
+
}] : []],
|
|
398
|
+
join: { user: true }
|
|
399
|
+
});
|
|
400
|
+
if (!sessions.length) return [];
|
|
401
|
+
if (sessions.some((session) => !session.user)) return [];
|
|
402
|
+
return sessions.map((_session) => {
|
|
403
|
+
const { user, ...session } = _session;
|
|
404
|
+
return {
|
|
405
|
+
session,
|
|
406
|
+
user
|
|
407
|
+
};
|
|
408
|
+
});
|
|
409
|
+
},
|
|
410
|
+
updateSession: async (sessionToken, session) => {
|
|
411
|
+
return await updateWithHooks(session, [{
|
|
412
|
+
field: "token",
|
|
413
|
+
value: sessionToken
|
|
414
|
+
}], "session", secondaryStorage ? {
|
|
415
|
+
async fn(data) {
|
|
416
|
+
const currentSession = await secondaryStorage.get(sessionToken);
|
|
417
|
+
if (!currentSession) return null;
|
|
418
|
+
const parsedSession = safeJSONParse(currentSession);
|
|
419
|
+
if (!parsedSession) return null;
|
|
420
|
+
const mergedSession = {
|
|
421
|
+
...parsedSession.session,
|
|
422
|
+
...data,
|
|
423
|
+
expiresAt: new Date(data.expiresAt ?? parsedSession.session.expiresAt),
|
|
424
|
+
createdAt: new Date(parsedSession.session.createdAt),
|
|
425
|
+
updatedAt: new Date(data.updatedAt ?? parsedSession.session.updatedAt)
|
|
426
|
+
};
|
|
427
|
+
const updatedSession = parseSessionOutput(ctx.options, mergedSession);
|
|
428
|
+
const now = Date.now();
|
|
429
|
+
const expiresMs = new Date(updatedSession.expiresAt).getTime();
|
|
430
|
+
const sessionTTL = getTTLSeconds(expiresMs, now);
|
|
431
|
+
if (sessionTTL > 0) {
|
|
432
|
+
await secondaryStorage.set(sessionToken, JSON.stringify({
|
|
433
|
+
session: updatedSession,
|
|
434
|
+
user: parsedSession.user
|
|
435
|
+
}), sessionTTL);
|
|
436
|
+
const listKey = `active-sessions-${updatedSession.userId}`;
|
|
437
|
+
const listRaw = await secondaryStorage.get(listKey);
|
|
438
|
+
const sorted = (listRaw ? safeJSONParse(listRaw) || [] : []).filter((s) => s.token !== sessionToken && s.expiresAt > now).concat([{
|
|
439
|
+
token: sessionToken,
|
|
440
|
+
expiresAt: expiresMs
|
|
441
|
+
}]).sort((a, b) => a.expiresAt - b.expiresAt);
|
|
442
|
+
const furthestSessionExp = sorted.at(-1)?.expiresAt;
|
|
443
|
+
if (furthestSessionExp && furthestSessionExp > now) await secondaryStorage.set(listKey, JSON.stringify(sorted), getTTLSeconds(furthestSessionExp, now));
|
|
444
|
+
else await secondaryStorage.delete(listKey);
|
|
445
|
+
}
|
|
446
|
+
return updatedSession;
|
|
447
|
+
},
|
|
448
|
+
executeMainFn: options.session?.storeSessionInDatabase
|
|
449
|
+
} : void 0);
|
|
450
|
+
},
|
|
451
|
+
deleteSession: async (token) => {
|
|
452
|
+
if (secondaryStorage) {
|
|
453
|
+
const data = await secondaryStorage.get(token);
|
|
454
|
+
if (data) {
|
|
455
|
+
const { session } = safeJSONParse(data) ?? {};
|
|
456
|
+
if (!session) {
|
|
457
|
+
logger.error("Session not found in secondary storage");
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const userId = session.userId;
|
|
461
|
+
const currentList = await secondaryStorage.get(`active-sessions-${userId}`);
|
|
462
|
+
if (currentList) {
|
|
463
|
+
const list = safeJSONParse(currentList) || [];
|
|
464
|
+
const now = Date.now();
|
|
465
|
+
const filtered = list.filter((session) => session.expiresAt > now && session.token !== token);
|
|
466
|
+
const furthestSessionExp = filtered.sort((a, b) => a.expiresAt - b.expiresAt).at(-1)?.expiresAt;
|
|
467
|
+
if (filtered.length > 0 && furthestSessionExp && furthestSessionExp > Date.now()) await secondaryStorage.set(`active-sessions-${userId}`, JSON.stringify(filtered), getTTLSeconds(furthestSessionExp, now));
|
|
468
|
+
else await secondaryStorage.delete(`active-sessions-${userId}`);
|
|
469
|
+
} else logger.error("Active sessions list not found in secondary storage");
|
|
470
|
+
}
|
|
471
|
+
await secondaryStorage.delete(token);
|
|
472
|
+
if (!options.session?.storeSessionInDatabase) return;
|
|
473
|
+
if (ctx.options.session?.preserveSessionInDatabase) {
|
|
474
|
+
await endPreservedSessions([{
|
|
475
|
+
field: "token",
|
|
476
|
+
value: token
|
|
477
|
+
}]);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
await deleteWithHooks([{
|
|
482
|
+
field: "token",
|
|
483
|
+
value: token
|
|
484
|
+
}], "session", void 0);
|
|
485
|
+
},
|
|
486
|
+
deleteAccounts: async (userId) => {
|
|
487
|
+
await deleteManyWithHooks([{
|
|
488
|
+
field: "userId",
|
|
489
|
+
value: userId
|
|
490
|
+
}], "account", void 0);
|
|
491
|
+
},
|
|
492
|
+
/**
|
|
493
|
+
* Delete an account by its primary key.
|
|
494
|
+
*
|
|
495
|
+
* @param id - The account row's primary key, not its accountId.
|
|
496
|
+
*/
|
|
497
|
+
deleteAccount: async (id) => {
|
|
498
|
+
await deleteWithHooks([{
|
|
499
|
+
field: "id",
|
|
500
|
+
value: id
|
|
501
|
+
}], "account", void 0);
|
|
502
|
+
},
|
|
503
|
+
deleteUserSessions: async (userId) => {
|
|
504
|
+
const sessionReferences = await getActiveSessionReferences(userId);
|
|
505
|
+
if (secondaryStorage) {
|
|
506
|
+
if (!options.session?.storeSessionInDatabase) {
|
|
507
|
+
await queueCachedUserSessionDeletion(userId, sessionReferences);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (ctx.options.session?.preserveSessionInDatabase) {
|
|
511
|
+
if (await endPreservedSessions([{
|
|
512
|
+
field: "userId",
|
|
513
|
+
value: userId
|
|
514
|
+
}]) !== null) await queueCachedUserSessionDeletion(userId, sessionReferences);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (await deleteManyWithHooks([{
|
|
519
|
+
field: "userId",
|
|
520
|
+
value: userId
|
|
521
|
+
}], "session", void 0) !== null) await queueCachedUserSessionDeletion(userId, sessionReferences);
|
|
522
|
+
},
|
|
523
|
+
deleteSessions: async (sessionTokens) => {
|
|
524
|
+
if (secondaryStorage) {
|
|
525
|
+
await Promise.all(sessionTokens.map((token) => secondaryStorage.delete(token)));
|
|
526
|
+
if (!options.session?.storeSessionInDatabase) return;
|
|
527
|
+
if (ctx.options.session?.preserveSessionInDatabase) {
|
|
528
|
+
await endPreservedSessions([{
|
|
529
|
+
field: "token",
|
|
530
|
+
value: sessionTokens,
|
|
531
|
+
operator: "in"
|
|
532
|
+
}]);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
await deleteManyWithHooks([{
|
|
537
|
+
field: "token",
|
|
538
|
+
value: sessionTokens,
|
|
539
|
+
operator: "in"
|
|
540
|
+
}], "session", void 0);
|
|
541
|
+
},
|
|
542
|
+
findAccountOwnerByKey: async ({ providerId, accountId }) => {
|
|
543
|
+
const accountsWithUsers = await (await getCurrentAdapter(adapter)).findMany({
|
|
544
|
+
model: "account",
|
|
545
|
+
where: [{
|
|
546
|
+
field: "providerId",
|
|
547
|
+
value: providerId
|
|
548
|
+
}, {
|
|
549
|
+
field: "accountId",
|
|
550
|
+
value: accountId
|
|
551
|
+
}],
|
|
552
|
+
limit: 2,
|
|
553
|
+
join: { user: true }
|
|
554
|
+
});
|
|
555
|
+
if (accountsWithUsers.length > 1) throw new BetterAuthError(`Multiple accounts match the same accountId for provider ${JSON.stringify(providerId)}. Resolve duplicate account identities before continuing.`);
|
|
556
|
+
const accountWithUser = accountsWithUsers[0];
|
|
557
|
+
if (!accountWithUser) return null;
|
|
558
|
+
const { user, ...account } = accountWithUser;
|
|
559
|
+
return user ? {
|
|
560
|
+
kind: "owned",
|
|
561
|
+
user,
|
|
562
|
+
account
|
|
563
|
+
} : {
|
|
564
|
+
kind: "orphaned",
|
|
565
|
+
account
|
|
566
|
+
};
|
|
567
|
+
},
|
|
568
|
+
findUserByEmail: async (email, options) => {
|
|
569
|
+
const result = await (await getCurrentAdapter(adapter)).findOne({
|
|
570
|
+
model: "user",
|
|
571
|
+
where: [{
|
|
572
|
+
value: email.toLowerCase(),
|
|
573
|
+
field: "email"
|
|
574
|
+
}],
|
|
575
|
+
join: { ...options?.includeAccounts ? { account: true } : {} }
|
|
576
|
+
});
|
|
577
|
+
if (!result) return null;
|
|
578
|
+
const { account: accounts, ...user } = result;
|
|
579
|
+
return {
|
|
580
|
+
user,
|
|
581
|
+
accounts: accounts ?? []
|
|
582
|
+
};
|
|
583
|
+
},
|
|
584
|
+
findUserById: async (userId) => {
|
|
585
|
+
if (!userId) return null;
|
|
586
|
+
return await (await getCurrentAdapter(adapter)).findOne({
|
|
587
|
+
model: "user",
|
|
588
|
+
where: [{
|
|
589
|
+
field: "id",
|
|
590
|
+
value: userId
|
|
591
|
+
}]
|
|
592
|
+
});
|
|
593
|
+
},
|
|
594
|
+
linkAccount: async (account) => {
|
|
595
|
+
return await createWithHooks({
|
|
596
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
597
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
598
|
+
...account
|
|
599
|
+
}, "account", void 0);
|
|
600
|
+
},
|
|
601
|
+
updateUser: async (userId, data) => {
|
|
602
|
+
const user = await updateWithHooks({
|
|
603
|
+
...data,
|
|
604
|
+
...data.email ? { email: data.email.toLowerCase() } : {}
|
|
605
|
+
}, [{
|
|
606
|
+
field: "id",
|
|
607
|
+
value: userId
|
|
608
|
+
}], "user", void 0);
|
|
609
|
+
await queueAfterTransactionHook(() => refreshUserSessions(user), { onError(error) {
|
|
610
|
+
logger.error("Failed to refresh committed user sessions in secondary storage", error);
|
|
611
|
+
} });
|
|
612
|
+
return user;
|
|
613
|
+
},
|
|
614
|
+
updateUserByEmail: async (email, data) => {
|
|
615
|
+
const user = await updateWithHooks({
|
|
616
|
+
...data,
|
|
617
|
+
...data.email ? { email: data.email.toLowerCase() } : {}
|
|
618
|
+
}, [{
|
|
619
|
+
field: "email",
|
|
620
|
+
value: email.toLowerCase()
|
|
621
|
+
}], "user", void 0);
|
|
622
|
+
await queueAfterTransactionHook(() => refreshUserSessions(user), { onError(error) {
|
|
623
|
+
logger.error("Failed to refresh committed user sessions in secondary storage", error);
|
|
624
|
+
} });
|
|
625
|
+
return user;
|
|
626
|
+
},
|
|
627
|
+
updatePassword: async (userId, password) => {
|
|
628
|
+
await updateManyWithHooks({ password }, [
|
|
629
|
+
{
|
|
630
|
+
field: "userId",
|
|
631
|
+
value: userId
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
field: "providerId",
|
|
635
|
+
value: "credential"
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
field: "accountId",
|
|
639
|
+
value: userId
|
|
640
|
+
}
|
|
641
|
+
], "account", void 0);
|
|
642
|
+
},
|
|
643
|
+
findAccounts: async (userId) => {
|
|
644
|
+
return await (await getCurrentAdapter(adapter)).findMany({
|
|
645
|
+
model: "account",
|
|
646
|
+
where: [{
|
|
647
|
+
field: "userId",
|
|
648
|
+
value: userId
|
|
649
|
+
}]
|
|
650
|
+
});
|
|
651
|
+
},
|
|
652
|
+
findCredentialAccount: async (userId) => {
|
|
653
|
+
return (await getCurrentAdapter(adapter)).findOne({
|
|
654
|
+
model: "account",
|
|
655
|
+
where: [
|
|
656
|
+
{
|
|
657
|
+
field: "userId",
|
|
658
|
+
value: userId
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
field: "providerId",
|
|
662
|
+
value: "credential"
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
field: "accountId",
|
|
666
|
+
value: userId
|
|
667
|
+
}
|
|
668
|
+
]
|
|
669
|
+
});
|
|
670
|
+
},
|
|
671
|
+
findAccountByKey: async ({ providerId, accountId }) => {
|
|
672
|
+
const accounts = await (await getCurrentAdapter(adapter)).findMany({
|
|
673
|
+
model: "account",
|
|
674
|
+
limit: 2,
|
|
675
|
+
where: [{
|
|
676
|
+
field: "providerId",
|
|
677
|
+
value: providerId
|
|
678
|
+
}, {
|
|
679
|
+
field: "accountId",
|
|
680
|
+
value: accountId
|
|
681
|
+
}]
|
|
682
|
+
});
|
|
683
|
+
if (accounts.length > 1) throw new BetterAuthError(`Multiple accounts match the same accountId for provider ${JSON.stringify(providerId)}. Resolve duplicate account identities before continuing.`);
|
|
684
|
+
return accounts[0] ?? null;
|
|
685
|
+
},
|
|
686
|
+
findAccountByUserId: async (userId) => {
|
|
687
|
+
return await (await getCurrentAdapter(adapter)).findMany({
|
|
688
|
+
model: "account",
|
|
689
|
+
where: [{
|
|
690
|
+
field: "userId",
|
|
691
|
+
value: userId
|
|
692
|
+
}]
|
|
693
|
+
});
|
|
694
|
+
},
|
|
695
|
+
updateAccount: async (id, data) => {
|
|
696
|
+
return await updateWithHooks(data, [{
|
|
697
|
+
field: "id",
|
|
698
|
+
value: id
|
|
699
|
+
}], "account", void 0);
|
|
700
|
+
},
|
|
701
|
+
createVerificationValue: async (data) => {
|
|
702
|
+
const storageOption = getStorageOption(data.identifier, options.verification?.storeIdentifier);
|
|
703
|
+
const storedIdentifier = await processIdentifier(data.identifier, storageOption);
|
|
704
|
+
return await createWithHooks({
|
|
705
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
706
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
707
|
+
...data,
|
|
708
|
+
identifier: storedIdentifier
|
|
709
|
+
}, "verification", secondaryStorage ? {
|
|
710
|
+
async fn(verificationData) {
|
|
711
|
+
const ttl = getTTLSeconds(verificationData.expiresAt);
|
|
712
|
+
if (ttl > 0) await secondaryStorage.set(`verification:${storedIdentifier}`, JSON.stringify(verificationData), ttl);
|
|
713
|
+
return verificationData;
|
|
714
|
+
},
|
|
715
|
+
executeMainFn: options.verification?.storeInDatabase
|
|
716
|
+
} : void 0);
|
|
717
|
+
},
|
|
718
|
+
findVerificationValue: async (identifier) => {
|
|
719
|
+
const storageOption = getStorageOption(identifier, options.verification?.storeIdentifier);
|
|
720
|
+
const storedIdentifier = await processIdentifier(identifier, storageOption);
|
|
721
|
+
if (secondaryStorage) {
|
|
722
|
+
const cached = await secondaryStorage.get(`verification:${storedIdentifier}`);
|
|
723
|
+
if (cached) {
|
|
724
|
+
const parsed = safeJSONParse(cached);
|
|
725
|
+
if (parsed) return parsed;
|
|
726
|
+
}
|
|
727
|
+
if (storageOption && storageOption !== "plain") {
|
|
728
|
+
const plainCached = await secondaryStorage.get(`verification:${identifier}`);
|
|
729
|
+
if (plainCached) {
|
|
730
|
+
const parsed = safeJSONParse(plainCached);
|
|
731
|
+
if (parsed) return parsed;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (!options.verification?.storeInDatabase) return null;
|
|
735
|
+
}
|
|
736
|
+
const currentAdapter = await getCurrentAdapter(adapter);
|
|
737
|
+
async function findByIdentifier(id) {
|
|
738
|
+
return currentAdapter.findMany({
|
|
739
|
+
model: "verification",
|
|
740
|
+
where: [{
|
|
741
|
+
field: "identifier",
|
|
742
|
+
value: id
|
|
743
|
+
}],
|
|
744
|
+
sortBy: {
|
|
745
|
+
field: "createdAt",
|
|
746
|
+
direction: "desc"
|
|
747
|
+
},
|
|
748
|
+
limit: 1
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
let verification = await findByIdentifier(storedIdentifier);
|
|
752
|
+
if (!verification.length && storageOption && storageOption !== "plain") verification = await findByIdentifier(identifier);
|
|
753
|
+
if (!options.verification?.disableCleanup) await deleteManyWithHooks([{
|
|
754
|
+
field: "expiresAt",
|
|
755
|
+
value: /* @__PURE__ */ new Date(),
|
|
756
|
+
operator: "lt"
|
|
757
|
+
}], "verification", void 0);
|
|
758
|
+
return verification[0] || null;
|
|
759
|
+
},
|
|
760
|
+
deleteVerificationByIdentifier: async (identifier) => {
|
|
761
|
+
const storedIdentifier = await processIdentifier(identifier, getStorageOption(identifier, options.verification?.storeIdentifier));
|
|
762
|
+
if (secondaryStorage) await secondaryStorage.delete(`verification:${storedIdentifier}`);
|
|
763
|
+
if (!secondaryStorage || options.verification?.storeInDatabase) await deleteWithHooks([{
|
|
764
|
+
field: "identifier",
|
|
765
|
+
value: storedIdentifier
|
|
766
|
+
}], "verification", void 0);
|
|
767
|
+
},
|
|
768
|
+
/**
|
|
769
|
+
* Atomically consume a single-use verification row by `identifier` and
|
|
770
|
+
* return it. The first concurrent caller receives the latest row for the
|
|
771
|
+
* identifier; every other caller racing against it receives `null`.
|
|
772
|
+
*
|
|
773
|
+
* Race-safe replacement for the `findVerificationValue` then
|
|
774
|
+
* `deleteVerificationByIdentifier` pair. Callers MUST gate any state
|
|
775
|
+
* change (issue session, mint token, change password) on a non-null
|
|
776
|
+
* return value, because consuming one row invalidates the whole
|
|
777
|
+
* identifier and stale rows cannot be replayed.
|
|
778
|
+
*
|
|
779
|
+
* Rows past their `expiresAt` are treated as already invalid: the row
|
|
780
|
+
* is still deleted (so it cannot be replayed later) but `null` is
|
|
781
|
+
* returned. Callers do not need their own `expiresAt` gate.
|
|
782
|
+
*
|
|
783
|
+
* The secondary-storage-only path (`storeInDatabase: false`) consumes
|
|
784
|
+
* through `getAndDelete`, which is required on `SecondaryStorage` so
|
|
785
|
+
* single-use values are not read and deleted as separate operations.
|
|
786
|
+
*/
|
|
787
|
+
consumeVerificationValue: async (identifier) => {
|
|
788
|
+
const storageOption = getStorageOption(identifier, options.verification?.storeIdentifier);
|
|
789
|
+
const storedIdentifier = await processIdentifier(identifier, storageOption);
|
|
790
|
+
const identifiersToTry = storageOption && storageOption !== "plain" ? [storedIdentifier, identifier] : [storedIdentifier];
|
|
791
|
+
const hydrateCachedVerification = (raw) => {
|
|
792
|
+
if (!raw) return null;
|
|
793
|
+
const candidate = typeof raw === "string" ? safeJSONParse(raw) : typeof raw === "object" ? raw : null;
|
|
794
|
+
if (!candidate) return null;
|
|
795
|
+
const expiresAt = new Date(candidate.expiresAt);
|
|
796
|
+
if (!Number.isFinite(expiresAt.getTime())) return null;
|
|
797
|
+
return {
|
|
798
|
+
...candidate,
|
|
799
|
+
expiresAt
|
|
800
|
+
};
|
|
801
|
+
};
|
|
802
|
+
let consumed = null;
|
|
803
|
+
if (secondaryStorage && !options.verification?.storeInDatabase) {
|
|
804
|
+
const consumeCacheKey = async (key) => {
|
|
805
|
+
return hydrateCachedVerification(await secondaryStorage.getAndDelete(key));
|
|
806
|
+
};
|
|
807
|
+
for (const stored of identifiersToTry) {
|
|
808
|
+
const cached = await consumeCacheKey(`verification:${stored}`);
|
|
809
|
+
if (!cached) continue;
|
|
810
|
+
await Promise.all(identifiersToTry.filter((candidate) => candidate !== stored).map((candidate) => secondaryStorage.delete(`verification:${candidate}`)));
|
|
811
|
+
consumed = cached;
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
} else {
|
|
815
|
+
const consumeByIdentifier = async (id) => withVerificationConsumeLock(`verification:${id}`, () => runWithTransaction(adapter, async () => {
|
|
816
|
+
const txAdapter = await getCurrentAdapter(adapter);
|
|
817
|
+
const where = [{
|
|
818
|
+
field: "identifier",
|
|
819
|
+
value: id
|
|
820
|
+
}];
|
|
821
|
+
const latest = (await txAdapter.findMany({
|
|
822
|
+
model: "verification",
|
|
823
|
+
where,
|
|
824
|
+
sortBy: {
|
|
825
|
+
field: "createdAt",
|
|
826
|
+
direction: "desc"
|
|
827
|
+
},
|
|
828
|
+
limit: 1
|
|
829
|
+
}))[0] ?? null;
|
|
830
|
+
if (!latest) return null;
|
|
831
|
+
return consumeOneWithHooks("verification", [{
|
|
832
|
+
field: "id",
|
|
833
|
+
value: latest.id
|
|
834
|
+
}], async () => {
|
|
835
|
+
const row = await txAdapter.consumeOne({
|
|
836
|
+
model: "verification",
|
|
837
|
+
where: [{
|
|
838
|
+
field: "id",
|
|
839
|
+
value: latest.id
|
|
840
|
+
}]
|
|
841
|
+
});
|
|
842
|
+
if (!row) return null;
|
|
843
|
+
await txAdapter.deleteMany({
|
|
844
|
+
model: "verification",
|
|
845
|
+
where
|
|
846
|
+
});
|
|
847
|
+
return row;
|
|
848
|
+
}, latest);
|
|
849
|
+
}));
|
|
850
|
+
for (const stored of identifiersToTry) {
|
|
851
|
+
consumed = await consumeByIdentifier(stored);
|
|
852
|
+
if (consumed) break;
|
|
853
|
+
}
|
|
854
|
+
if (consumed && secondaryStorage) await Promise.all(identifiersToTry.map((stored) => secondaryStorage.delete(`verification:${stored}`)));
|
|
855
|
+
}
|
|
856
|
+
if (!consumed || consumed.expiresAt < /* @__PURE__ */ new Date()) return null;
|
|
857
|
+
return consumed;
|
|
858
|
+
},
|
|
859
|
+
/**
|
|
860
|
+
* First-writer-wins create keyed by a deterministic primary key derived
|
|
861
|
+
* from `identifier`. Returns `true` when this caller created the row and
|
|
862
|
+
* `false` when a row for the same identifier already existed.
|
|
863
|
+
*
|
|
864
|
+
* The dual of `consumeVerificationValue`: where consume races to delete a
|
|
865
|
+
* marker exactly once, reserve races to create a marker exactly once. Use
|
|
866
|
+
* it for replay tombstones (a SAML assertion id, a JWT `jti`) where the
|
|
867
|
+
* first caller wins and every later caller must observe that the marker is
|
|
868
|
+
* already taken.
|
|
869
|
+
*
|
|
870
|
+
* The `verification.identifier` column is non-unique, so uniqueness comes
|
|
871
|
+
* from a deterministic primary key (`SHA-256` of `reserve:<identifier>`).
|
|
872
|
+
* The database path is atomic: the primary key turns the INSERT into the
|
|
873
|
+
* first-writer-wins gate, and a duplicate is detected portably by
|
|
874
|
+
* re-reading the row rather than matching adapter-specific errors.
|
|
875
|
+
* Secondary-storage-only verification cannot enforce the deterministic
|
|
876
|
+
* primary-key gate, so this operation fails closed unless verification is
|
|
877
|
+
* backed by the database.
|
|
878
|
+
*
|
|
879
|
+
* The atomic guarantee requires the configured adapter to reject a
|
|
880
|
+
* duplicate primary key on insert, which every real database enforces. The
|
|
881
|
+
* in-memory adapter does not enforce primary-key uniqueness, so reservation
|
|
882
|
+
* is best-effort there (it is intended for development and tests).
|
|
883
|
+
*/
|
|
884
|
+
reserveVerificationValue: async (data) => {
|
|
885
|
+
const reservationId = base64Url.encode(new Uint8Array(await createHash("SHA-256").digest(new TextEncoder().encode("reserve:" + data.identifier))), { padding: false });
|
|
886
|
+
const storageOption = getStorageOption(data.identifier, options.verification?.storeIdentifier);
|
|
887
|
+
const storedIdentifier = await processIdentifier(data.identifier, storageOption);
|
|
888
|
+
if (secondaryStorage && !options.verification?.storeInDatabase) throw new BetterAuthError("reserveVerificationValue requires database-backed verification storage. Set verification.storeInDatabase to true for flows that reserve verification values.");
|
|
889
|
+
try {
|
|
890
|
+
await adapter.create({
|
|
891
|
+
model: "verification",
|
|
892
|
+
data: {
|
|
893
|
+
id: reservationId,
|
|
894
|
+
identifier: storedIdentifier,
|
|
895
|
+
value: data.value,
|
|
896
|
+
expiresAt: data.expiresAt,
|
|
897
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
898
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
899
|
+
},
|
|
900
|
+
forceAllowId: true
|
|
901
|
+
});
|
|
902
|
+
} catch (error) {
|
|
903
|
+
if (await adapter.findOne({
|
|
904
|
+
model: "verification",
|
|
905
|
+
where: [{
|
|
906
|
+
field: "id",
|
|
907
|
+
value: reservationId
|
|
908
|
+
}]
|
|
909
|
+
})) return false;
|
|
910
|
+
throw error;
|
|
911
|
+
}
|
|
912
|
+
if (secondaryStorage) {
|
|
913
|
+
const ttl = getTTLSeconds(data.expiresAt);
|
|
914
|
+
if (ttl > 0) await secondaryStorage.set(`verification:${storedIdentifier}`, JSON.stringify({
|
|
915
|
+
id: reservationId,
|
|
916
|
+
identifier: storedIdentifier,
|
|
917
|
+
value: data.value,
|
|
918
|
+
expiresAt: data.expiresAt
|
|
919
|
+
}), ttl);
|
|
920
|
+
}
|
|
921
|
+
return true;
|
|
922
|
+
},
|
|
923
|
+
updateVerificationByIdentifier: async (identifier, data) => {
|
|
924
|
+
const storedIdentifier = await processIdentifier(identifier, getStorageOption(identifier, options.verification?.storeIdentifier));
|
|
925
|
+
if (secondaryStorage) {
|
|
926
|
+
const cached = await secondaryStorage.get(`verification:${storedIdentifier}`);
|
|
927
|
+
if (cached) {
|
|
928
|
+
const parsed = safeJSONParse(cached);
|
|
929
|
+
if (parsed) {
|
|
930
|
+
const updated = {
|
|
931
|
+
...parsed,
|
|
932
|
+
...data
|
|
933
|
+
};
|
|
934
|
+
const expiresAt = updated.expiresAt ?? parsed.expiresAt;
|
|
935
|
+
const ttl = getTTLSeconds(expiresAt instanceof Date ? expiresAt : new Date(expiresAt));
|
|
936
|
+
if (ttl > 0) await secondaryStorage.set(`verification:${storedIdentifier}`, JSON.stringify(updated), ttl);
|
|
937
|
+
if (!options.verification?.storeInDatabase) return updated;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (!secondaryStorage || options.verification?.storeInDatabase) return await updateWithHooks(data, [{
|
|
942
|
+
field: "identifier",
|
|
943
|
+
value: storedIdentifier
|
|
944
|
+
}], "verification", void 0);
|
|
945
|
+
return data;
|
|
946
|
+
},
|
|
947
|
+
refreshUserSessions
|
|
948
|
+
};
|
|
949
|
+
};
|
|
950
|
+
//#endregion
|
|
951
|
+
export { createInternalAdapter };
|