This is page 2 of 67. Use http://codebase.md/better-auth/better-auth?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .gitattributes ├── .github │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ └── feature_request.yml │ ├── renovate.json5 │ └── workflows │ ├── ci.yml │ ├── e2e.yml │ ├── preview.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .vscode │ └── settings.json ├── banner-dark.png ├── banner.png ├── biome.json ├── bump.config.ts ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── demo │ ├── expo-example │ │ ├── .env.example │ │ ├── .gitignore │ │ ├── app.config.ts │ │ ├── assets │ │ │ ├── bg-image.jpeg │ │ │ ├── fonts │ │ │ │ └── SpaceMono-Regular.ttf │ │ │ ├── icon.png │ │ │ └── images │ │ │ ├── adaptive-icon.png │ │ │ ├── favicon.png │ │ │ ├── logo.png │ │ │ ├── partial-react-logo.png │ │ │ ├── react-logo.png │ │ │ ├── [email protected] │ │ │ ├── [email protected] │ │ │ └── splash.png │ │ ├── babel.config.js │ │ ├── components.json │ │ ├── expo-env.d.ts │ │ ├── index.ts │ │ ├── metro.config.js │ │ ├── nativewind-env.d.ts │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── app │ │ │ │ ├── _layout.tsx │ │ │ │ ├── api │ │ │ │ │ └── auth │ │ │ │ │ └── [...route]+api.ts │ │ │ │ ├── dashboard.tsx │ │ │ │ ├── forget-password.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── sign-up.tsx │ │ │ ├── components │ │ │ │ ├── icons │ │ │ │ │ └── google.tsx │ │ │ │ └── ui │ │ │ │ ├── avatar.tsx │ │ │ │ ├── button.tsx │ │ │ │ ├── card.tsx │ │ │ │ ├── dialog.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── separator.tsx │ │ │ │ └── text.tsx │ │ │ ├── global.css │ │ │ └── lib │ │ │ ├── auth-client.ts │ │ │ ├── auth.ts │ │ │ ├── icons │ │ │ │ ├── iconWithClassName.ts │ │ │ │ └── X.tsx │ │ │ └── utils.ts │ │ ├── tailwind.config.js │ │ └── tsconfig.json │ └── nextjs │ ├── .env.example │ ├── .gitignore │ ├── app │ │ ├── (auth) │ │ │ ├── forget-password │ │ │ │ └── page.tsx │ │ │ ├── reset-password │ │ │ │ └── page.tsx │ │ │ ├── sign-in │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ │ └── two-factor │ │ │ ├── otp │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── accept-invitation │ │ │ └── [id] │ │ │ ├── invitation-error.tsx │ │ │ └── page.tsx │ │ ├── admin │ │ │ └── page.tsx │ │ ├── api │ │ │ └── auth │ │ │ └── [...all] │ │ │ └── route.ts │ │ ├── apps │ │ │ └── register │ │ │ └── page.tsx │ │ ├── client-test │ │ │ └── page.tsx │ │ ├── dashboard │ │ │ ├── change-plan.tsx │ │ │ ├── client.tsx │ │ │ ├── organization-card.tsx │ │ │ ├── page.tsx │ │ │ ├── upgrade-button.tsx │ │ │ └── user-card.tsx │ │ ├── device │ │ │ ├── approve │ │ │ │ └── page.tsx │ │ │ ├── denied │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── success │ │ │ └── page.tsx │ │ ├── favicon.ico │ │ ├── features.tsx │ │ ├── fonts │ │ │ ├── GeistMonoVF.woff │ │ │ └── GeistVF.woff │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── oauth │ │ │ └── authorize │ │ │ ├── concet-buttons.tsx │ │ │ └── page.tsx │ │ ├── page.tsx │ │ └── pricing │ │ └── page.tsx │ ├── components │ │ ├── account-switch.tsx │ │ ├── blocks │ │ │ └── pricing.tsx │ │ ├── logo.tsx │ │ ├── one-tap.tsx │ │ ├── sign-in-btn.tsx │ │ ├── sign-in.tsx │ │ ├── sign-up.tsx │ │ ├── theme-provider.tsx │ │ ├── theme-toggle.tsx │ │ ├── tier-labels.tsx │ │ ├── ui │ │ │ ├── accordion.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── alert.tsx │ │ │ ├── aspect-ratio.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── button.tsx │ │ │ ├── calendar.tsx │ │ │ ├── card.tsx │ │ │ ├── carousel.tsx │ │ │ ├── chart.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── command.tsx │ │ │ ├── context-menu.tsx │ │ │ ├── copy-button.tsx │ │ │ ├── dialog.tsx │ │ │ ├── drawer.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── hover-card.tsx │ │ │ ├── input-otp.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── menubar.tsx │ │ │ ├── navigation-menu.tsx │ │ │ ├── pagination.tsx │ │ │ ├── password-input.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── resizable.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── slider.tsx │ │ │ ├── sonner.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── tabs2.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toast.tsx │ │ │ ├── toaster.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── toggle.tsx │ │ │ └── tooltip.tsx │ │ └── wrapper.tsx │ ├── components.json │ ├── hooks │ │ └── use-toast.ts │ ├── lib │ │ ├── auth-client.ts │ │ ├── auth-types.ts │ │ ├── auth.ts │ │ ├── email │ │ │ ├── invitation.tsx │ │ │ ├── resend.ts │ │ │ └── reset-password.tsx │ │ ├── metadata.ts │ │ ├── shared.ts │ │ └── utils.ts │ ├── middleware.ts │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ │ ├── __og.png │ │ ├── _og.png │ │ ├── favicon │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon.ico │ │ │ ├── light │ │ │ │ ├── android-chrome-192x192.png │ │ │ │ ├── android-chrome-512x512.png │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ ├── favicon.ico │ │ │ │ └── site.webmanifest │ │ │ └── site.webmanifest │ │ ├── logo.svg │ │ └── og.png │ ├── README.md │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── turbo.json ├── docker-compose.yml ├── docs │ ├── .env.example │ ├── .gitignore │ ├── app │ │ ├── api │ │ │ ├── ai-chat │ │ │ │ └── route.ts │ │ │ ├── analytics │ │ │ │ ├── conversation │ │ │ │ │ └── route.ts │ │ │ │ ├── event │ │ │ │ │ └── route.ts │ │ │ │ └── feedback │ │ │ │ └── route.ts │ │ │ ├── chat │ │ │ │ └── route.ts │ │ │ ├── og │ │ │ │ └── route.tsx │ │ │ ├── og-release │ │ │ │ └── route.tsx │ │ │ ├── search │ │ │ │ └── route.ts │ │ │ └── support │ │ │ └── route.ts │ │ ├── blog │ │ │ ├── _components │ │ │ │ ├── _layout.tsx │ │ │ │ ├── blog-list.tsx │ │ │ │ ├── changelog-layout.tsx │ │ │ │ ├── default-changelog.tsx │ │ │ │ ├── fmt-dates.tsx │ │ │ │ ├── icons.tsx │ │ │ │ ├── stat-field.tsx │ │ │ │ └── support.tsx │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── changelogs │ │ │ ├── _components │ │ │ │ ├── _layout.tsx │ │ │ │ ├── changelog-layout.tsx │ │ │ │ ├── default-changelog.tsx │ │ │ │ ├── fmt-dates.tsx │ │ │ │ ├── grid-pattern.tsx │ │ │ │ ├── icons.tsx │ │ │ │ └── stat-field.tsx │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── community │ │ │ ├── _components │ │ │ │ ├── header.tsx │ │ │ │ └── stats.tsx │ │ │ └── page.tsx │ │ ├── docs │ │ │ ├── [[...slug]] │ │ │ │ ├── page.client.tsx │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── lib │ │ │ └── get-llm-text.ts │ │ ├── global.css │ │ ├── layout.config.tsx │ │ ├── layout.tsx │ │ ├── llms.txt │ │ │ ├── [...slug] │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ ├── not-found.tsx │ │ ├── page.tsx │ │ ├── reference │ │ │ └── route.ts │ │ ├── sitemap.xml │ │ ├── static.json │ │ │ └── route.ts │ │ └── v1 │ │ ├── _components │ │ │ └── v1-text.tsx │ │ ├── bg-line.tsx │ │ └── page.tsx │ ├── assets │ │ ├── Geist.ttf │ │ └── GeistMono.ttf │ ├── components │ │ ├── ai-chat-modal.tsx │ │ ├── anchor-scroll-fix.tsx │ │ ├── api-method-tabs.tsx │ │ ├── api-method.tsx │ │ ├── banner.tsx │ │ ├── blocks │ │ │ └── features.tsx │ │ ├── builder │ │ │ ├── beam.tsx │ │ │ ├── code-tabs │ │ │ │ ├── code-editor.tsx │ │ │ │ ├── code-tabs.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── tab-bar.tsx │ │ │ │ └── theme.ts │ │ │ ├── index.tsx │ │ │ ├── sign-in.tsx │ │ │ ├── sign-up.tsx │ │ │ ├── social-provider.tsx │ │ │ ├── store.ts │ │ │ └── tabs.tsx │ │ ├── display-techstack.tsx │ │ ├── divider-text.tsx │ │ ├── docs │ │ │ ├── docs.client.tsx │ │ │ ├── docs.tsx │ │ │ ├── layout │ │ │ │ ├── nav.tsx │ │ │ │ ├── theme-toggle.tsx │ │ │ │ ├── toc-thumb.tsx │ │ │ │ └── toc.tsx │ │ │ ├── page.client.tsx │ │ │ ├── page.tsx │ │ │ ├── shared.tsx │ │ │ └── ui │ │ │ ├── button.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── popover.tsx │ │ │ └── scroll-area.tsx │ │ ├── endpoint.tsx │ │ ├── features.tsx │ │ ├── floating-ai-search.tsx │ │ ├── fork-button.tsx │ │ ├── generate-apple-jwt.tsx │ │ ├── generate-secret.tsx │ │ ├── github-stat.tsx │ │ ├── icons.tsx │ │ ├── landing │ │ │ ├── gradient-bg.tsx │ │ │ ├── grid-pattern.tsx │ │ │ ├── hero.tsx │ │ │ ├── section-svg.tsx │ │ │ ├── section.tsx │ │ │ ├── spotlight.tsx │ │ │ └── testimonials.tsx │ │ ├── logo-context-menu.tsx │ │ ├── logo.tsx │ │ ├── markdown-renderer.tsx │ │ ├── markdown.tsx │ │ ├── mdx │ │ │ ├── add-to-cursor.tsx │ │ │ └── database-tables.tsx │ │ ├── message-feedback.tsx │ │ ├── mobile-search-icon.tsx │ │ ├── nav-bar.tsx │ │ ├── nav-link.tsx │ │ ├── nav-mobile.tsx │ │ ├── promo-card.tsx │ │ ├── resource-card.tsx │ │ ├── resource-grid.tsx │ │ ├── resource-section.tsx │ │ ├── ripple.tsx │ │ ├── search-dialog.tsx │ │ ├── side-bar.tsx │ │ ├── sidebar-content.tsx │ │ ├── techstack-icons.tsx │ │ ├── theme-provider.tsx │ │ ├── theme-toggler.tsx │ │ └── ui │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── aside-link.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── background-beams.tsx │ │ ├── background-boxes.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── callout.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── chart.tsx │ │ ├── checkbox.tsx │ │ ├── code-block.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── dynamic-code-block.tsx │ │ ├── fade-in.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── sparkles.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ ├── tooltip-docs.tsx │ │ ├── tooltip.tsx │ │ └── use-copy-button.tsx │ ├── components.json │ ├── content │ │ ├── blogs │ │ │ ├── 0-supabase-auth-to-planetscale-migration.mdx │ │ │ ├── 1-3.mdx │ │ │ ├── authjs-joins-better-auth.mdx │ │ │ └── seed-round.mdx │ │ ├── changelogs │ │ │ ├── 1-2.mdx │ │ │ └── 1.0.mdx │ │ └── docs │ │ ├── adapters │ │ │ ├── community-adapters.mdx │ │ │ ├── drizzle.mdx │ │ │ ├── mongo.mdx │ │ │ ├── mssql.mdx │ │ │ ├── mysql.mdx │ │ │ ├── other-relational-databases.mdx │ │ │ ├── postgresql.mdx │ │ │ ├── prisma.mdx │ │ │ └── sqlite.mdx │ │ ├── authentication │ │ │ ├── apple.mdx │ │ │ ├── atlassian.mdx │ │ │ ├── cognito.mdx │ │ │ ├── discord.mdx │ │ │ ├── dropbox.mdx │ │ │ ├── email-password.mdx │ │ │ ├── facebook.mdx │ │ │ ├── figma.mdx │ │ │ ├── github.mdx │ │ │ ├── gitlab.mdx │ │ │ ├── google.mdx │ │ │ ├── huggingface.mdx │ │ │ ├── kakao.mdx │ │ │ ├── kick.mdx │ │ │ ├── line.mdx │ │ │ ├── linear.mdx │ │ │ ├── linkedin.mdx │ │ │ ├── microsoft.mdx │ │ │ ├── naver.mdx │ │ │ ├── notion.mdx │ │ │ ├── other-social-providers.mdx │ │ │ ├── paypal.mdx │ │ │ ├── reddit.mdx │ │ │ ├── roblox.mdx │ │ │ ├── salesforce.mdx │ │ │ ├── slack.mdx │ │ │ ├── spotify.mdx │ │ │ ├── tiktok.mdx │ │ │ ├── twitch.mdx │ │ │ ├── twitter.mdx │ │ │ ├── vk.mdx │ │ │ └── zoom.mdx │ │ ├── basic-usage.mdx │ │ ├── comparison.mdx │ │ ├── concepts │ │ │ ├── api.mdx │ │ │ ├── cli.mdx │ │ │ ├── client.mdx │ │ │ ├── cookies.mdx │ │ │ ├── database.mdx │ │ │ ├── email.mdx │ │ │ ├── hooks.mdx │ │ │ ├── oauth.mdx │ │ │ ├── plugins.mdx │ │ │ ├── rate-limit.mdx │ │ │ ├── session-management.mdx │ │ │ ├── typescript.mdx │ │ │ └── users-accounts.mdx │ │ ├── examples │ │ │ ├── astro.mdx │ │ │ ├── next-js.mdx │ │ │ ├── nuxt.mdx │ │ │ ├── remix.mdx │ │ │ └── svelte-kit.mdx │ │ ├── guides │ │ │ ├── auth0-migration-guide.mdx │ │ │ ├── browser-extension-guide.mdx │ │ │ ├── clerk-migration-guide.mdx │ │ │ ├── create-a-db-adapter.mdx │ │ │ ├── next-auth-migration-guide.mdx │ │ │ ├── optimizing-for-performance.mdx │ │ │ ├── saml-sso-with-okta.mdx │ │ │ ├── supabase-migration-guide.mdx │ │ │ └── your-first-plugin.mdx │ │ ├── installation.mdx │ │ ├── integrations │ │ │ ├── astro.mdx │ │ │ ├── convex.mdx │ │ │ ├── elysia.mdx │ │ │ ├── expo.mdx │ │ │ ├── express.mdx │ │ │ ├── fastify.mdx │ │ │ ├── hono.mdx │ │ │ ├── lynx.mdx │ │ │ ├── nestjs.mdx │ │ │ ├── next.mdx │ │ │ ├── nitro.mdx │ │ │ ├── nuxt.mdx │ │ │ ├── remix.mdx │ │ │ ├── solid-start.mdx │ │ │ ├── svelte-kit.mdx │ │ │ ├── tanstack.mdx │ │ │ └── waku.mdx │ │ ├── introduction.mdx │ │ ├── meta.json │ │ ├── plugins │ │ │ ├── 2fa.mdx │ │ │ ├── admin.mdx │ │ │ ├── anonymous.mdx │ │ │ ├── api-key.mdx │ │ │ ├── autumn.mdx │ │ │ ├── bearer.mdx │ │ │ ├── captcha.mdx │ │ │ ├── community-plugins.mdx │ │ │ ├── device-authorization.mdx │ │ │ ├── dodopayments.mdx │ │ │ ├── dub.mdx │ │ │ ├── email-otp.mdx │ │ │ ├── generic-oauth.mdx │ │ │ ├── have-i-been-pwned.mdx │ │ │ ├── jwt.mdx │ │ │ ├── last-login-method.mdx │ │ │ ├── magic-link.mdx │ │ │ ├── mcp.mdx │ │ │ ├── multi-session.mdx │ │ │ ├── oauth-proxy.mdx │ │ │ ├── oidc-provider.mdx │ │ │ ├── one-tap.mdx │ │ │ ├── one-time-token.mdx │ │ │ ├── open-api.mdx │ │ │ ├── organization.mdx │ │ │ ├── passkey.mdx │ │ │ ├── phone-number.mdx │ │ │ ├── polar.mdx │ │ │ ├── siwe.mdx │ │ │ ├── sso.mdx │ │ │ ├── stripe.mdx │ │ │ └── username.mdx │ │ └── reference │ │ ├── contributing.mdx │ │ ├── faq.mdx │ │ ├── options.mdx │ │ ├── resources.mdx │ │ ├── security.mdx │ │ └── telemetry.mdx │ ├── hooks │ │ └── use-mobile.ts │ ├── ignore-build.sh │ ├── lib │ │ ├── blog.ts │ │ ├── chat │ │ │ └── inkeep-qa-schema.ts │ │ ├── constants.ts │ │ ├── export-search-indexes.ts │ │ ├── inkeep-analytics.ts │ │ ├── is-active.ts │ │ ├── metadata.ts │ │ ├── source.ts │ │ └── utils.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── proxy.ts │ ├── public │ │ ├── avatars │ │ │ └── beka.jpg │ │ ├── blogs │ │ │ ├── authjs-joins.png │ │ │ ├── seed-round.png │ │ │ └── supabase-ps.png │ │ ├── branding │ │ │ ├── better-auth-brand-assets.zip │ │ │ ├── better-auth-logo-dark.png │ │ │ ├── better-auth-logo-dark.svg │ │ │ ├── better-auth-logo-light.png │ │ │ ├── better-auth-logo-light.svg │ │ │ ├── better-auth-logo-wordmark-dark.png │ │ │ ├── better-auth-logo-wordmark-dark.svg │ │ │ ├── better-auth-logo-wordmark-light.png │ │ │ └── better-auth-logo-wordmark-light.svg │ │ ├── extension-id.png │ │ ├── favicon │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon.ico │ │ │ ├── light │ │ │ │ ├── android-chrome-192x192.png │ │ │ │ ├── android-chrome-512x512.png │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ ├── favicon.ico │ │ │ │ └── site.webmanifest │ │ │ └── site.webmanifest │ │ ├── images │ │ │ └── blogs │ │ │ └── better auth (1).png │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── LogoDark.webp │ │ ├── LogoLight.webp │ │ ├── og.png │ │ ├── open-api-reference.png │ │ ├── people-say │ │ │ ├── code-with-antonio.jpg │ │ │ ├── dagmawi-babi.png │ │ │ ├── dax.png │ │ │ ├── dev-ed.png │ │ │ ├── egoist.png │ │ │ ├── guillermo-rauch.png │ │ │ ├── jonathan-wilke.png │ │ │ ├── josh-tried-coding.jpg │ │ │ ├── kitze.jpg │ │ │ ├── lazar-nikolov.png │ │ │ ├── nizzy.png │ │ │ ├── omar-mcadam.png │ │ │ ├── ryan-vogel.jpg │ │ │ ├── saltyatom.jpg │ │ │ ├── sebastien-chopin.png │ │ │ ├── shreyas-mididoddi.png │ │ │ ├── tech-nerd.png │ │ │ ├── theo.png │ │ │ ├── vybhav-bhargav.png │ │ │ └── xavier-pladevall.jpg │ │ ├── plus.svg │ │ ├── release-og │ │ │ ├── 1-2.png │ │ │ ├── 1-3.png │ │ │ └── changelog-og.png │ │ └── v1-og.png │ ├── README.md │ ├── scripts │ │ ├── endpoint-to-doc │ │ │ ├── index.ts │ │ │ ├── input.ts │ │ │ ├── output.mdx │ │ │ └── readme.md │ │ └── sync-orama.ts │ ├── source.config.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── turbo.json ├── e2e │ ├── integration │ │ ├── package.json │ │ ├── playwright.config.ts │ │ ├── solid-vinxi │ │ │ ├── .gitignore │ │ │ ├── app.config.ts │ │ │ ├── e2e │ │ │ │ ├── test.spec.ts │ │ │ │ └── utils.ts │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── favicon.ico │ │ │ ├── src │ │ │ │ ├── app.tsx │ │ │ │ ├── entry-client.tsx │ │ │ │ ├── entry-server.tsx │ │ │ │ ├── global.d.ts │ │ │ │ ├── lib │ │ │ │ │ ├── auth-client.ts │ │ │ │ │ └── auth.ts │ │ │ │ └── routes │ │ │ │ ├── [...404].tsx │ │ │ │ ├── api │ │ │ │ │ └── auth │ │ │ │ │ └── [...all].ts │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ │ ├── test-utils │ │ │ ├── package.json │ │ │ └── src │ │ │ └── playwright.ts │ │ └── vanilla-node │ │ ├── e2e │ │ │ ├── app.ts │ │ │ ├── domain.spec.ts │ │ │ ├── postgres-js.spec.ts │ │ │ ├── test.spec.ts │ │ │ └── utils.ts │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── main.ts │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── smoke │ ├── package.json │ ├── test │ │ ├── bun.spec.ts │ │ ├── cloudflare.spec.ts │ │ ├── deno.spec.ts │ │ ├── fixtures │ │ │ ├── bun-simple.ts │ │ │ ├── cloudflare │ │ │ │ ├── .gitignore │ │ │ │ ├── drizzle │ │ │ │ │ ├── 0000_clean_vector.sql │ │ │ │ │ └── meta │ │ │ │ │ ├── _journal.json │ │ │ │ │ └── 0000_snapshot.json │ │ │ │ ├── drizzle.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── src │ │ │ │ │ ├── auth-schema.ts │ │ │ │ │ ├── db.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── test │ │ │ │ │ ├── apply-migrations.ts │ │ │ │ │ ├── env.d.ts │ │ │ │ │ └── index.test.ts │ │ │ │ ├── tsconfig.json │ │ │ │ ├── vitest.config.ts │ │ │ │ ├── worker-configuration.d.ts │ │ │ │ └── wrangler.json │ │ │ ├── deno-simple.ts │ │ │ ├── tsconfig-exact-optional-property-types │ │ │ │ ├── package.json │ │ │ │ ├── src │ │ │ │ │ ├── index.ts │ │ │ │ │ └── user-additional-fields.ts │ │ │ │ └── tsconfig.json │ │ │ ├── tsconfig-verbatim-module-syntax-node10 │ │ │ │ ├── package.json │ │ │ │ ├── src │ │ │ │ │ └── index.ts │ │ │ │ └── tsconfig.json │ │ │ └── vite │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── client.ts │ │ │ │ └── server.ts │ │ │ ├── tsconfig.json │ │ │ └── vite.config.ts │ │ ├── ssr.ts │ │ ├── typecheck.spec.ts │ │ └── vite.spec.ts │ └── tsconfig.json ├── LICENSE.md ├── package.json ├── packages │ ├── better-auth │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── __snapshots__ │ │ │ │ └── init.test.ts.snap │ │ │ ├── adapters │ │ │ │ ├── adapter-factory │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── test │ │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ │ └── adapter-factory.test.ts.snap │ │ │ │ │ │ └── adapter-factory.test.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── create-test-suite.ts │ │ │ │ ├── drizzle-adapter │ │ │ │ │ ├── drizzle-adapter.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── test │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── adapter.drizzle.mysql.test.ts │ │ │ │ │ ├── adapter.drizzle.pg.test.ts │ │ │ │ │ ├── adapter.drizzle.sqlite.test.ts │ │ │ │ │ └── generate-schema.ts │ │ │ │ ├── index.ts │ │ │ │ ├── kysely-adapter │ │ │ │ │ ├── bun-sqlite-dialect.ts │ │ │ │ │ ├── dialect.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── kysely-adapter.ts │ │ │ │ │ ├── node-sqlite-dialect.ts │ │ │ │ │ ├── test │ │ │ │ │ │ ├── adapter.kysely.mssql.test.ts │ │ │ │ │ │ ├── adapter.kysely.mysql.test.ts │ │ │ │ │ │ ├── adapter.kysely.pg.test.ts │ │ │ │ │ │ ├── adapter.kysely.sqlite.test.ts │ │ │ │ │ │ └── node-sqlite-dialect.test.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── memory-adapter │ │ │ │ │ ├── adapter.memory.test.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── memory-adapter.ts │ │ │ │ ├── mongodb-adapter │ │ │ │ │ ├── adapter.mongo-db.test.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── mongodb-adapter.ts │ │ │ │ ├── prisma-adapter │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── prisma-adapter.ts │ │ │ │ │ └── test │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── base.prisma │ │ │ │ │ ├── generate-auth-config.ts │ │ │ │ │ ├── generate-prisma-schema.ts │ │ │ │ │ ├── get-prisma-client.ts │ │ │ │ │ ├── prisma.mysql.test.ts │ │ │ │ │ ├── prisma.pg.test.ts │ │ │ │ │ ├── prisma.sqlite.test.ts │ │ │ │ │ └── push-prisma-schema.ts │ │ │ │ ├── test-adapter.ts │ │ │ │ ├── test.ts │ │ │ │ ├── tests │ │ │ │ │ ├── auth-flow.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── normal.ts │ │ │ │ │ ├── number-id.ts │ │ │ │ │ ├── performance.ts │ │ │ │ │ └── transactions.ts │ │ │ │ └── utils.ts │ │ │ ├── api │ │ │ │ ├── check-endpoint-conflicts.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── middlewares │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── origin-check.test.ts │ │ │ │ │ └── origin-check.ts │ │ │ │ ├── rate-limiter │ │ │ │ │ ├── index.ts │ │ │ │ │ └── rate-limiter.test.ts │ │ │ │ ├── routes │ │ │ │ │ ├── account.test.ts │ │ │ │ │ ├── account.ts │ │ │ │ │ ├── callback.ts │ │ │ │ │ ├── email-verification.test.ts │ │ │ │ │ ├── email-verification.ts │ │ │ │ │ ├── error.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── ok.ts │ │ │ │ │ ├── reset-password.test.ts │ │ │ │ │ ├── reset-password.ts │ │ │ │ │ ├── session-api.test.ts │ │ │ │ │ ├── session.ts │ │ │ │ │ ├── sign-in.test.ts │ │ │ │ │ ├── sign-in.ts │ │ │ │ │ ├── sign-out.test.ts │ │ │ │ │ ├── sign-out.ts │ │ │ │ │ ├── sign-up.test.ts │ │ │ │ │ ├── sign-up.ts │ │ │ │ │ ├── update-user.test.ts │ │ │ │ │ └── update-user.ts │ │ │ │ ├── to-auth-endpoints.test.ts │ │ │ │ └── to-auth-endpoints.ts │ │ │ ├── auth.test.ts │ │ │ ├── auth.ts │ │ │ ├── call.test.ts │ │ │ ├── client │ │ │ │ ├── client-ssr.test.ts │ │ │ │ ├── client.test.ts │ │ │ │ ├── config.ts │ │ │ │ ├── fetch-plugins.ts │ │ │ │ ├── index.ts │ │ │ │ ├── lynx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── lynx-store.ts │ │ │ │ ├── parser.ts │ │ │ │ ├── path-to-object.ts │ │ │ │ ├── plugins │ │ │ │ │ ├── index.ts │ │ │ │ │ └── infer-plugin.ts │ │ │ │ ├── proxy.ts │ │ │ │ ├── query.ts │ │ │ │ ├── react │ │ │ │ │ ├── index.ts │ │ │ │ │ └── react-store.ts │ │ │ │ ├── session-atom.ts │ │ │ │ ├── solid │ │ │ │ │ ├── index.ts │ │ │ │ │ └── solid-store.ts │ │ │ │ ├── svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── test-plugin.ts │ │ │ │ ├── types.ts │ │ │ │ ├── url.test.ts │ │ │ │ ├── vanilla.ts │ │ │ │ └── vue │ │ │ │ ├── index.ts │ │ │ │ └── vue-store.ts │ │ │ ├── cookies │ │ │ │ ├── check-cookies.ts │ │ │ │ ├── cookie-utils.ts │ │ │ │ ├── cookies.test.ts │ │ │ │ └── index.ts │ │ │ ├── crypto │ │ │ │ ├── buffer.ts │ │ │ │ ├── hash.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jwt.ts │ │ │ │ ├── password.test.ts │ │ │ │ ├── password.ts │ │ │ │ └── random.ts │ │ │ ├── db │ │ │ │ ├── db.test.ts │ │ │ │ ├── field.ts │ │ │ │ ├── get-migration.ts │ │ │ │ ├── get-schema.ts │ │ │ │ ├── get-tables.test.ts │ │ │ │ ├── get-tables.ts │ │ │ │ ├── index.ts │ │ │ │ ├── internal-adapter.test.ts │ │ │ │ ├── internal-adapter.ts │ │ │ │ ├── schema.ts │ │ │ │ ├── secondary-storage.test.ts │ │ │ │ ├── to-zod.ts │ │ │ │ ├── utils.ts │ │ │ │ └── with-hooks.ts │ │ │ ├── index.ts │ │ │ ├── init.test.ts │ │ │ ├── init.ts │ │ │ ├── integrations │ │ │ │ ├── next-js.ts │ │ │ │ ├── node.ts │ │ │ │ ├── react-start.ts │ │ │ │ ├── solid-start.ts │ │ │ │ └── svelte-kit.ts │ │ │ ├── oauth2 │ │ │ │ ├── index.ts │ │ │ │ ├── link-account.test.ts │ │ │ │ ├── link-account.ts │ │ │ │ ├── state.ts │ │ │ │ └── utils.ts │ │ │ ├── plugins │ │ │ │ ├── access │ │ │ │ │ ├── access.test.ts │ │ │ │ │ ├── access.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── additional-fields │ │ │ │ │ ├── additional-fields.test.ts │ │ │ │ │ └── client.ts │ │ │ │ ├── admin │ │ │ │ │ ├── access │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── statement.ts │ │ │ │ │ ├── admin.test.ts │ │ │ │ │ ├── admin.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── error-codes.ts │ │ │ │ │ ├── has-permission.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── anonymous │ │ │ │ │ ├── anon.test.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── api-key │ │ │ │ │ ├── api-key.test.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── rate-limit.ts │ │ │ │ │ ├── routes │ │ │ │ │ │ ├── create-api-key.ts │ │ │ │ │ │ ├── delete-all-expired-api-keys.ts │ │ │ │ │ │ ├── delete-api-key.ts │ │ │ │ │ │ ├── get-api-key.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── list-api-keys.ts │ │ │ │ │ │ ├── update-api-key.ts │ │ │ │ │ │ └── verify-api-key.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── bearer │ │ │ │ │ ├── bearer.test.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── captcha │ │ │ │ │ ├── captcha.test.ts │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── error-codes.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── utils.ts │ │ │ │ │ └── verify-handlers │ │ │ │ │ ├── captchafox.ts │ │ │ │ │ ├── cloudflare-turnstile.ts │ │ │ │ │ ├── google-recaptcha.ts │ │ │ │ │ ├── h-captcha.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── custom-session │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── custom-session.test.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── device-authorization │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── device-authorization.test.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── schema.ts │ │ │ │ ├── email-otp │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── email-otp.test.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── generic-oauth │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── generic-oauth.test.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── haveibeenpwned │ │ │ │ │ ├── haveibeenpwned.test.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jwt │ │ │ │ │ ├── adapter.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── jwt.test.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── sign.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── last-login-method │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── custom-prefix.test.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── last-login-method.test.ts │ │ │ │ ├── magic-link │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── magic-link.test.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── mcp │ │ │ │ │ ├── authorize.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── mcp.test.ts │ │ │ │ ├── multi-session │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── multi-session.test.ts │ │ │ │ ├── oauth-proxy │ │ │ │ │ ├── index.ts │ │ │ │ │ └── oauth-proxy.test.ts │ │ │ │ ├── oidc-provider │ │ │ │ │ ├── authorize.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── oidc.test.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── ui.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── one-tap │ │ │ │ │ ├── client.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── one-time-token │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── one-time-token.test.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── open-api │ │ │ │ │ ├── generator.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── logo.ts │ │ │ │ │ └── open-api.test.ts │ │ │ │ ├── organization │ │ │ │ │ ├── access │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── statement.ts │ │ │ │ │ ├── adapter.ts │ │ │ │ │ ├── call.ts │ │ │ │ │ ├── client.test.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── error-codes.ts │ │ │ │ │ ├── has-permission.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── organization-hook.test.ts │ │ │ │ │ ├── organization.test.ts │ │ │ │ │ ├── organization.ts │ │ │ │ │ ├── permission.ts │ │ │ │ │ ├── routes │ │ │ │ │ │ ├── crud-access-control.test.ts │ │ │ │ │ │ ├── crud-access-control.ts │ │ │ │ │ │ ├── crud-invites.ts │ │ │ │ │ │ ├── crud-members.test.ts │ │ │ │ │ │ ├── crud-members.ts │ │ │ │ │ │ ├── crud-org.test.ts │ │ │ │ │ │ ├── crud-org.ts │ │ │ │ │ │ └── crud-team.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── team.test.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── passkey │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── passkey.test.ts │ │ │ │ ├── phone-number │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── phone-number-error.ts │ │ │ │ │ └── phone-number.test.ts │ │ │ │ ├── siwe │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── siwe.test.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── sso │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── sso.test.ts │ │ │ │ ├── two-factor │ │ │ │ │ ├── backup-codes │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── constant.ts │ │ │ │ │ ├── error-code.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── otp │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── totp │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── two-factor.test.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── utils.ts │ │ │ │ │ └── verify-two-factor.ts │ │ │ │ └── username │ │ │ │ ├── client.ts │ │ │ │ ├── error-codes.ts │ │ │ │ ├── index.ts │ │ │ │ ├── schema.ts │ │ │ │ └── username.test.ts │ │ │ ├── social-providers │ │ │ │ └── index.ts │ │ │ ├── social.test.ts │ │ │ ├── test-utils │ │ │ │ ├── headers.ts │ │ │ │ ├── index.ts │ │ │ │ ├── state.ts │ │ │ │ └── test-instance.ts │ │ │ ├── types │ │ │ │ ├── adapter.ts │ │ │ │ ├── api.ts │ │ │ │ ├── helper.ts │ │ │ │ ├── index.ts │ │ │ │ ├── models.ts │ │ │ │ ├── plugins.ts │ │ │ │ └── types.test.ts │ │ │ └── utils │ │ │ ├── await-object.ts │ │ │ ├── boolean.ts │ │ │ ├── clone.ts │ │ │ ├── constants.ts │ │ │ ├── date.ts │ │ │ ├── ensure-utc.ts │ │ │ ├── get-request-ip.ts │ │ │ ├── hashing.ts │ │ │ ├── hide-metadata.ts │ │ │ ├── id.ts │ │ │ ├── import-util.ts │ │ │ ├── index.ts │ │ │ ├── is-atom.ts │ │ │ ├── is-promise.ts │ │ │ ├── json.ts │ │ │ ├── merger.ts │ │ │ ├── middleware-response.ts │ │ │ ├── misc.ts │ │ │ ├── password.ts │ │ │ ├── plugin-helper.ts │ │ │ ├── shim.ts │ │ │ ├── time.ts │ │ │ ├── url.ts │ │ │ └── wildcard.ts │ │ ├── tsconfig.json │ │ ├── tsdown.config.ts │ │ └── vitest.config.ts │ ├── cli │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── commands │ │ │ │ ├── generate.ts │ │ │ │ ├── info.ts │ │ │ │ ├── init.ts │ │ │ │ ├── login.ts │ │ │ │ ├── mcp.ts │ │ │ │ ├── migrate.ts │ │ │ │ └── secret.ts │ │ │ ├── generators │ │ │ │ ├── auth-config.ts │ │ │ │ ├── drizzle.ts │ │ │ │ ├── index.ts │ │ │ │ ├── kysely.ts │ │ │ │ ├── prisma.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ └── utils │ │ │ ├── add-svelte-kit-env-modules.ts │ │ │ ├── check-package-managers.ts │ │ │ ├── format-ms.ts │ │ │ ├── get-config.ts │ │ │ ├── get-package-info.ts │ │ │ ├── get-tsconfig-info.ts │ │ │ └── install-dependencies.ts │ │ ├── test │ │ │ ├── __snapshots__ │ │ │ │ ├── auth-schema-mysql-enum.txt │ │ │ │ ├── auth-schema-mysql-number-id.txt │ │ │ │ ├── auth-schema-mysql-passkey-number-id.txt │ │ │ │ ├── auth-schema-mysql-passkey.txt │ │ │ │ ├── auth-schema-mysql.txt │ │ │ │ ├── auth-schema-number-id.txt │ │ │ │ ├── auth-schema-pg-enum.txt │ │ │ │ ├── auth-schema-pg-passkey.txt │ │ │ │ ├── auth-schema-sqlite-enum.txt │ │ │ │ ├── auth-schema-sqlite-number-id.txt │ │ │ │ ├── auth-schema-sqlite-passkey-number-id.txt │ │ │ │ ├── auth-schema-sqlite-passkey.txt │ │ │ │ ├── auth-schema-sqlite.txt │ │ │ │ ├── auth-schema.txt │ │ │ │ ├── migrations.sql │ │ │ │ ├── schema-mongodb.prisma │ │ │ │ ├── schema-mysql-custom.prisma │ │ │ │ ├── schema-mysql.prisma │ │ │ │ ├── schema-numberid.prisma │ │ │ │ └── schema.prisma │ │ │ ├── generate-all-db.test.ts │ │ │ ├── generate.test.ts │ │ │ ├── get-config.test.ts │ │ │ ├── info.test.ts │ │ │ └── migrate.test.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.test.json │ │ └── tsdown.config.ts │ ├── core │ │ ├── package.json │ │ ├── src │ │ │ ├── async_hooks │ │ │ │ └── index.ts │ │ │ ├── context │ │ │ │ ├── index.ts │ │ │ │ └── transaction.ts │ │ │ ├── db │ │ │ │ ├── adapter │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── plugin.ts │ │ │ │ ├── schema │ │ │ │ │ ├── account.ts │ │ │ │ │ ├── rate-limit.ts │ │ │ │ │ ├── session.ts │ │ │ │ │ ├── shared.ts │ │ │ │ │ ├── user.ts │ │ │ │ │ └── verification.ts │ │ │ │ └── type.ts │ │ │ ├── env │ │ │ │ ├── color-depth.ts │ │ │ │ ├── env-impl.ts │ │ │ │ ├── index.ts │ │ │ │ ├── logger.test.ts │ │ │ │ └── logger.ts │ │ │ ├── error │ │ │ │ ├── codes.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── middleware │ │ │ │ └── index.ts │ │ │ ├── oauth2 │ │ │ │ ├── client-credentials-token.ts │ │ │ │ ├── create-authorization-url.ts │ │ │ │ ├── index.ts │ │ │ │ ├── oauth-provider.ts │ │ │ │ ├── refresh-access-token.ts │ │ │ │ ├── utils.ts │ │ │ │ └── validate-authorization-code.ts │ │ │ ├── social-providers │ │ │ │ ├── apple.ts │ │ │ │ ├── atlassian.ts │ │ │ │ ├── cognito.ts │ │ │ │ ├── discord.ts │ │ │ │ ├── dropbox.ts │ │ │ │ ├── facebook.ts │ │ │ │ ├── figma.ts │ │ │ │ ├── github.ts │ │ │ │ ├── gitlab.ts │ │ │ │ ├── google.ts │ │ │ │ ├── huggingface.ts │ │ │ │ ├── index.ts │ │ │ │ ├── kakao.ts │ │ │ │ ├── kick.ts │ │ │ │ ├── line.ts │ │ │ │ ├── linear.ts │ │ │ │ ├── linkedin.ts │ │ │ │ ├── microsoft-entra-id.ts │ │ │ │ ├── naver.ts │ │ │ │ ├── notion.ts │ │ │ │ ├── paypal.ts │ │ │ │ ├── reddit.ts │ │ │ │ ├── roblox.ts │ │ │ │ ├── salesforce.ts │ │ │ │ ├── slack.ts │ │ │ │ ├── spotify.ts │ │ │ │ ├── tiktok.ts │ │ │ │ ├── twitch.ts │ │ │ │ ├── twitter.ts │ │ │ │ ├── vk.ts │ │ │ │ └── zoom.ts │ │ │ ├── types │ │ │ │ ├── context.ts │ │ │ │ ├── cookie.ts │ │ │ │ ├── helper.ts │ │ │ │ ├── index.ts │ │ │ │ ├── init-options.ts │ │ │ │ ├── plugin-client.ts │ │ │ │ └── plugin.ts │ │ │ └── utils │ │ │ ├── error-codes.ts │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── expo │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── client.ts │ │ │ ├── expo.test.ts │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── sso │ │ ├── package.json │ │ ├── src │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ ├── oidc.test.ts │ │ │ └── saml.test.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── stripe │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ ├── client.ts │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ ├── schema.ts │ │ │ ├── stripe.test.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ ├── tsdown.config.ts │ │ └── vitest.config.ts │ └── telemetry │ ├── package.json │ ├── src │ │ ├── detectors │ │ │ ├── detect-auth-config.ts │ │ │ ├── detect-database.ts │ │ │ ├── detect-framework.ts │ │ │ ├── detect-project-info.ts │ │ │ ├── detect-runtime.ts │ │ │ └── detect-system-info.ts │ │ ├── index.ts │ │ ├── project-id.ts │ │ ├── telemetry.test.ts │ │ ├── types.ts │ │ └── utils │ │ ├── hash.ts │ │ ├── id.ts │ │ ├── import-util.ts │ │ └── package-json.ts │ ├── tsconfig.json │ └── tsdown.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── README.md ├── SECURITY.md ├── tsconfig.json └── turbo.json ``` # Files -------------------------------------------------------------------------------- /packages/better-auth/src/client/session-atom.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { BetterFetch } from "@better-fetch/fetch"; 2 | import { atom } from "nanostores"; 3 | import { useAuthQuery } from "./query"; 4 | import type { Session, User } from "../types"; 5 | 6 | export function getSessionAtom($fetch: BetterFetch) { 7 | const $signal = atom<boolean>(false); 8 | const session = useAuthQuery<{ 9 | user: User; 10 | session: Session; 11 | }>($signal, "/get-session", $fetch, { 12 | method: "GET", 13 | }); 14 | return { 15 | session, 16 | $sessionSignal: $signal, 17 | }; 18 | } 19 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/api/routes/sign-out.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect } from "vitest"; 2 | import { getTestInstance } from "../../test-utils/test-instance"; 3 | 4 | describe("sign-out", async (it) => { 5 | const { signInWithTestUser, client } = await getTestInstance(); 6 | 7 | it("should sign out", async () => { 8 | const { runWithUser } = await signInWithTestUser(); 9 | await runWithUser(async () => { 10 | const res = await client.signOut(); 11 | expect(res.data).toMatchObject({ 12 | success: true, 13 | }); 14 | }); 15 | }); 16 | }); 17 | ``` -------------------------------------------------------------------------------- /e2e/smoke/test/fixtures/cloudflare/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["esnext"], 5 | "module": "esnext", 6 | "outDir": "./dist", 7 | "moduleResolution": "bundler", 8 | "types": [ 9 | "@cloudflare/workers-types/2023-07-01", 10 | "@cloudflare/workers-types/experimental", 11 | "@cloudflare/vitest-pool-workers" 12 | ], 13 | "noEmit": true, 14 | "isolatedModules": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "skipLibCheck": true, 17 | "strict": true 18 | } 19 | } 20 | ``` -------------------------------------------------------------------------------- /e2e/integration/solid-vinxi/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "better-auth-solid-vinxi-e2e", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vinxi dev", 7 | "build": "vinxi build", 8 | "start": "vinxi start" 9 | }, 10 | "dependencies": { 11 | "@solidjs/router": "^0.15.3", 12 | "@solidjs/start": "^1.1.7", 13 | "better-auth": "workspace:*", 14 | "better-sqlite3": "^12.2.0", 15 | "solid-js": "^1.9.7", 16 | "vinxi": "^0.5.8" 17 | }, 18 | "devDependencies": { 19 | "@better-auth/test-utils": "workspace:*" 20 | } 21 | } 22 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/phone-number/client.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { phoneNumber } from "."; 2 | import type { BetterAuthClientPlugin } from "@better-auth/core"; 3 | 4 | export const phoneNumberClient = () => { 5 | return { 6 | id: "phoneNumber", 7 | $InferServerPlugin: {} as ReturnType<typeof phoneNumber>, 8 | atomListeners: [ 9 | { 10 | matcher(path) { 11 | return ( 12 | path === "/phone-number/update" || path === "/phone-number/verify" 13 | ); 14 | }, 15 | signal: "$sessionSignal", 16 | }, 17 | ], 18 | } satisfies BetterAuthClientPlugin; 19 | }; 20 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/device-authorization/client.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { deviceAuthorization } from "."; 2 | import type { BetterAuthClientPlugin } from "@better-auth/core"; 3 | 4 | export const deviceAuthorizationClient = () => { 5 | return { 6 | id: "device-authorization", 7 | $InferServerPlugin: {} as ReturnType<typeof deviceAuthorization>, 8 | pathMethods: { 9 | "/device/code": "POST", 10 | "/device/token": "POST", 11 | "/device": "GET", 12 | "/device/approve": "POST", 13 | "/device/deny": "POST", 14 | }, 15 | } satisfies BetterAuthClientPlugin; 16 | }; 17 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/phone-number/phone-number-error.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { defineErrorCodes } from "@better-auth/core/utils"; 2 | 3 | export const ERROR_CODES = defineErrorCodes({ 4 | INVALID_PHONE_NUMBER: "Invalid phone number", 5 | PHONE_NUMBER_EXIST: "Phone number already exists", 6 | INVALID_PHONE_NUMBER_OR_PASSWORD: "Invalid phone number or password", 7 | UNEXPECTED_ERROR: "Unexpected error", 8 | OTP_NOT_FOUND: "OTP not found", 9 | OTP_EXPIRED: "OTP expired", 10 | INVALID_OTP: "Invalid OTP", 11 | PHONE_NUMBER_NOT_VERIFIED: "Phone number not verified", 12 | }); 13 | ``` -------------------------------------------------------------------------------- /e2e/smoke/test/fixtures/tsconfig-exact-optional-property-types/src/user-additional-fields.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { betterAuth } from "better-auth"; 2 | import Database from "better-sqlite3"; 3 | 4 | const auth = betterAuth({ 5 | database: new Database("./sqlite.db"), 6 | trustedOrigins: [], 7 | emailAndPassword: { 8 | enabled: true, 9 | }, 10 | user: { 11 | additionalFields: { 12 | timeZone: { 13 | type: "string", 14 | required: true, 15 | }, 16 | }, 17 | }, 18 | }); 19 | 20 | // expect no error here because timeZone is optional 21 | await auth.api.signUpEmail({ 22 | body: { 23 | email: "", 24 | password: "", 25 | name: "", 26 | timeZone: "", 27 | }, 28 | }); 29 | ``` -------------------------------------------------------------------------------- /packages/cli/src/commands/secret.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { logger } from "better-auth"; 2 | import chalk from "chalk"; 3 | import { Command } from "commander"; 4 | import Crypto from "crypto"; 5 | 6 | export const generateSecret = new Command("secret").action(() => { 7 | const secret = generateSecretHash(); 8 | logger.info(`\nAdd the following to your .env file: 9 | ${ 10 | chalk.gray("# Auth Secret") + chalk.green(`\nBETTER_AUTH_SECRET=${secret}`) 11 | }`); 12 | }); 13 | 14 | export const generateSecretHash = () => { 15 | return Crypto.randomBytes(32).toString("hex"); 16 | }; 17 | ``` -------------------------------------------------------------------------------- /packages/cli/src/generators/kysely.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { getMigrations } from "better-auth/db"; 2 | import type { SchemaGenerator } from "./types"; 3 | 4 | export const generateMigrations: SchemaGenerator = async ({ 5 | options, 6 | file, 7 | }) => { 8 | const { compileMigrations } = await getMigrations(options); 9 | const migrations = await compileMigrations(); 10 | return { 11 | code: migrations.trim() === ";" ? "" : migrations, 12 | fileName: 13 | file || 14 | `./better-auth_migrations/${new Date() 15 | .toISOString() 16 | .replace(/:/g, "-")}.sql`, 17 | }; 18 | }; 19 | ``` -------------------------------------------------------------------------------- /demo/expo-example/src/lib/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { PressableStateCallbackType } from "react-native"; 3 | import { twMerge } from "tailwind-merge"; 4 | 5 | export function cn(...inputs: ClassValue[]) { 6 | return twMerge(clsx(inputs)); 7 | } 8 | export function isTextChildren( 9 | children: 10 | | React.ReactNode 11 | | ((state: PressableStateCallbackType) => React.ReactNode), 12 | ) { 13 | return Array.isArray(children) 14 | ? children.every((child) => typeof child === "string") 15 | : typeof children === "string"; 16 | } 17 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/crypto/buffer.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Compare two buffers in constant time. 3 | */ 4 | export function constantTimeEqual( 5 | a: ArrayBuffer | Uint8Array, 6 | b: ArrayBuffer | Uint8Array, 7 | ): boolean { 8 | const aBuffer = new Uint8Array(a); 9 | const bBuffer = new Uint8Array(b); 10 | let c = aBuffer.length ^ bBuffer.length; 11 | const length = Math.max(aBuffer.length, bBuffer.length); 12 | for (let i = 0; i < length; i++) { 13 | c |= 14 | (i < aBuffer.length ? aBuffer[i]! : 0) ^ 15 | (i < bBuffer.length ? bBuffer[i]! : 0); 16 | } 17 | return c === 0; 18 | } 19 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/utils/await-object.ts: -------------------------------------------------------------------------------- ```typescript 1 | export async function awaitObject<T extends Record<string, Promise<any>>>( 2 | promises: T, 3 | ): Promise<{ [K in keyof T]: Awaited<T[K]> }> { 4 | const entries = Object.entries(promises) as [keyof T, T[keyof T]][]; 5 | const results = await Promise.all(entries.map(([, promise]) => promise)); 6 | 7 | const resolved: Partial<{ [K in keyof T]: Awaited<T[K]> }> = {}; 8 | entries.forEach(([key], index) => { 9 | resolved[key] = results[index]; 10 | }); 11 | 12 | return resolved as { [K in keyof T]: Awaited<T[K]> }; 13 | } 14 | ``` -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- ```yaml 1 | packages: 2 | - packages/** 3 | - docs 4 | - demo/* 5 | - e2e/** 6 | 7 | catalog: 8 | '@better-fetch/fetch': 1.1.18 9 | better-call: 1.0.19 10 | typescript: ^5.9.2 11 | tsdown: ^0.15.6 12 | vitest: ^3.2.4 13 | 14 | catalogs: 15 | react18: 16 | '@types/react': ^19.1.12 17 | '@types/react-dom': ^19.1.9 18 | react: 19.1.1 19 | react-dom: 19.1.1 20 | 21 | neverBuiltDependencies: [] 22 | 23 | overrides: 24 | brace-expansion@>=1.0.0 <=1.1.11: '>=1.1.12' 25 | cookie@<0.7.0: '>=0.7.0' 26 | esbuild@<=0.24.2: '>=0.25.0' 27 | miniflare>zod: ^3.25.1 28 | zod: ^4.1.5 29 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "esnext", 5 | "module": "esnext", 6 | "moduleResolution": "bundler", 7 | "downlevelIteration": true, 8 | "baseUrl": ".", 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "verbatimModuleSyntax": true, 12 | "noUncheckedIndexedAccess": true, 13 | "exactOptionalPropertyTypes": false, 14 | "incremental": true, 15 | "noErrorTruncation": true, 16 | "types": ["node", "bun"] 17 | }, 18 | "exclude": ["**/dist/**", "**/node_modules/**"] 19 | } 20 | ``` -------------------------------------------------------------------------------- /docs/components/logo.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { SVGProps } from "react"; 2 | import { cn } from "@/lib/utils"; 3 | export const Logo = (props: SVGProps<any>) => { 4 | return ( 5 | <svg 6 | width="60" 7 | height="45" 8 | viewBox="0 0 60 45" 9 | fill="none" 10 | className={cn("w-5 h-5", props.className)} 11 | xmlns="http://www.w3.org/2000/svg" 12 | > 13 | <path 14 | fillRule="evenodd" 15 | clipRule="evenodd" 16 | d="M0 0H15V15H30V30H15V45H0V30V15V0ZM45 30V15H30V0H45H60V15V30V45H45H30V30H45Z" 17 | className="fill-black dark:fill-white" 18 | /> 19 | </svg> 20 | ); 21 | }; 22 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/adapters/prisma-adapter/test/push-prisma-schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { execSync } from "node:child_process"; 2 | import { createRequire } from "node:module"; 3 | import { join } from "node:path"; 4 | 5 | export async function pushPrismaSchema( 6 | dialect: "sqlite" | "postgresql" | "mysql", 7 | ) { 8 | const node = process.execPath; 9 | const cli = createRequire(import.meta.url).resolve("prisma"); 10 | execSync(`${node} ${cli} db push --schema ./schema-${dialect}.prisma`, { 11 | stdio: "ignore", // use `inherit` if you want to see the output 12 | cwd: join(import.meta.dirname), 13 | }); 14 | } 15 | ``` -------------------------------------------------------------------------------- /e2e/integration/solid-vinxi/src/entry-server.tsx: -------------------------------------------------------------------------------- ```typescript 1 | // @refresh reload 2 | import { createHandler, StartServer } from "@solidjs/start/server"; 3 | 4 | export default createHandler(() => ( 5 | <StartServer 6 | document={({ assets, children, scripts }) => ( 7 | <html lang="en"> 8 | <head> 9 | <meta charset="utf-8" /> 10 | <meta name="viewport" content="width=device-width, initial-scale=1" /> 11 | <link rel="icon" href="/favicon.ico" /> 12 | {assets} 13 | </head> 14 | <body> 15 | <div id="app">{children}</div> 16 | {scripts} 17 | </body> 18 | </html> 19 | )} 20 | /> 21 | )); 22 | ``` -------------------------------------------------------------------------------- /packages/telemetry/src/detectors/detect-project-info.ts: -------------------------------------------------------------------------------- ```typescript 1 | // https://github.com/zkochan/packages/blob/main/which-pm-runs/index.js 2 | import { env } from "@better-auth/core/env"; 3 | 4 | export function detectPackageManager() { 5 | const userAgent = env.npm_config_user_agent; 6 | if (!userAgent) { 7 | return undefined; 8 | } 9 | 10 | const pmSpec = userAgent.split(" ")[0]!; 11 | const separatorPos = pmSpec.lastIndexOf("/"); 12 | const name = pmSpec.substring(0, separatorPos); 13 | 14 | return { 15 | name: name === "npminstall" ? "cnpm" : name, 16 | version: pmSpec.substring(separatorPos + 1), 17 | }; 18 | } 19 | ``` -------------------------------------------------------------------------------- /packages/core/src/db/schema/verification.ts: -------------------------------------------------------------------------------- ```typescript 1 | import * as z from "zod"; 2 | import { coreSchema } from "./shared"; 3 | 4 | export const verificationSchema = coreSchema.extend({ 5 | value: z.string(), 6 | expiresAt: z.date(), 7 | identifier: z.string(), 8 | }); 9 | 10 | /** 11 | * Verification schema type used by better-auth, note that it's possible that verification could have additional fields 12 | * 13 | * todo: we should use generics to extend this type with additional fields from plugins and options in the future 14 | */ 15 | export type Verification = z.infer<typeof verificationSchema>; 16 | ``` -------------------------------------------------------------------------------- /packages/core/src/types/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export type * from "./helper"; 2 | export type { 3 | BetterAuthAdvancedOptions, 4 | GenerateIdFn, 5 | BetterAuthRateLimitOptions, 6 | BetterAuthOptions, 7 | } from "./init-options"; 8 | export type { BetterAuthCookies } from "./cookie"; 9 | export type { 10 | AuthContext, 11 | GenericEndpointContext, 12 | InternalAdapter, 13 | } from "./context"; 14 | export type { BetterAuthPlugin, HookEndpointContext } from "./plugin"; 15 | export type { 16 | BetterAuthClientPlugin, 17 | BetterAuthClientOptions, 18 | ClientStore, 19 | ClientAtomListener, 20 | } from "./plugin-client"; 21 | ``` -------------------------------------------------------------------------------- /docs/app/blog/_components/fmt-dates.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { cn } from "@/lib/utils"; 2 | 3 | const dateFormatter = new Intl.DateTimeFormat("en-US", { 4 | year: "numeric", 5 | month: "short", 6 | day: "numeric", 7 | timeZone: "UTC", 8 | }); 9 | 10 | export function FormattedDate({ 11 | date, 12 | ...props 13 | }: React.ComponentPropsWithoutRef<"time"> & { date: string | Date }) { 14 | date = typeof date === "string" ? new Date(date) : date; 15 | 16 | return ( 17 | <time 18 | className={cn(props.className, "")} 19 | dateTime={date.toISOString()} 20 | {...props} 21 | > 22 | {dateFormatter.format(date)} 23 | </time> 24 | ); 25 | } 26 | ``` -------------------------------------------------------------------------------- /docs/app/changelogs/_components/fmt-dates.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { cn } from "@/lib/utils"; 2 | 3 | const dateFormatter = new Intl.DateTimeFormat("en-US", { 4 | year: "numeric", 5 | month: "short", 6 | day: "numeric", 7 | timeZone: "UTC", 8 | }); 9 | 10 | export function FormattedDate({ 11 | date, 12 | ...props 13 | }: React.ComponentPropsWithoutRef<"time"> & { date: string | Date }) { 14 | date = typeof date === "string" ? new Date(date) : date; 15 | 16 | return ( 17 | <time 18 | className={cn(props.className, "")} 19 | dateTime={date.toISOString()} 20 | {...props} 21 | > 22 | {dateFormatter.format(date)} 23 | </time> 24 | ); 25 | } 26 | ``` -------------------------------------------------------------------------------- /e2e/smoke/test/fixtures/bun-simple.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { betterAuth } from "better-auth"; 2 | import Database from "bun:sqlite"; 3 | import { getMigrations } from "better-auth/db"; 4 | 5 | const database = new Database(":memory:"); 6 | 7 | export const auth = betterAuth({ 8 | baseURL: "http://localhost:4000", 9 | database, 10 | emailAndPassword: { 11 | enabled: true, 12 | }, 13 | logger: { 14 | level: "debug", 15 | }, 16 | }); 17 | 18 | const { runMigrations } = await getMigrations(auth.options); 19 | 20 | await runMigrations(); 21 | 22 | const server = Bun.serve({ 23 | fetch: auth.handler, 24 | port: 0, 25 | }); 26 | 27 | console.log(server.port); 28 | ``` -------------------------------------------------------------------------------- /docs/components/resource-grid.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { cn } from "@/lib/utils"; 2 | import { ResourceCard } from "./resource-card"; 3 | 4 | interface ResourceGridProps { 5 | resources: { 6 | title: string; 7 | description: string; 8 | href: string; 9 | tags?: string[]; 10 | }[]; 11 | className?: string; 12 | } 13 | 14 | export function ResourceGrid({ resources, className }: ResourceGridProps) { 15 | return ( 16 | <div className={cn("grid gap-4 sm:grid-cols-2 lg:grid-cols-3", className)}> 17 | {resources.map((resource) => ( 18 | <ResourceCard key={resource.href} {...resource} /> 19 | ))} 20 | </div> 21 | ); 22 | } 23 | ``` -------------------------------------------------------------------------------- /docs/components/docs/shared.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import type { ReactNode } from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | 4 | export interface BaseLayoutProps { 5 | children?: ReactNode; 6 | } 7 | 8 | export function replaceOrDefault( 9 | obj: 10 | | { 11 | enabled?: boolean; 12 | component?: ReactNode; 13 | } 14 | | undefined, 15 | def: ReactNode, 16 | customComponentProps?: object, 17 | disabled?: ReactNode, 18 | ): ReactNode { 19 | if (obj?.enabled === false) return disabled; 20 | if (obj?.component !== undefined) 21 | return <Slot {...customComponentProps}>{obj.component}</Slot>; 22 | 23 | return def; 24 | } 25 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/test-utils/state.ts: -------------------------------------------------------------------------------- ```typescript 1 | import * as fs from "fs"; 2 | import path from "path"; 3 | 4 | export type State = "IDLE" | "RUNNING"; 5 | 6 | export function makeTestState(dirname: string) { 7 | const stateFilePath = path.join(dirname, "./state.txt"); 8 | 9 | function getState(): State { 10 | try { 11 | return fs 12 | .readFileSync(stateFilePath, "utf-8") 13 | .split("\n")[0]! 14 | .trim() as State; 15 | } catch { 16 | return "IDLE"; 17 | } 18 | } 19 | 20 | function setState(state: State) { 21 | fs.writeFileSync(stateFilePath, state, "utf-8"); 22 | } 23 | 24 | return { stateFilePath, getState, setState }; 25 | } 26 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/captcha/error-codes.ts: -------------------------------------------------------------------------------- ```typescript 1 | // These error codes are returned by the API 2 | import { defineErrorCodes } from "@better-auth/core/utils"; 3 | 4 | export const EXTERNAL_ERROR_CODES = defineErrorCodes({ 5 | VERIFICATION_FAILED: "Captcha verification failed", 6 | MISSING_RESPONSE: "Missing CAPTCHA response", 7 | UNKNOWN_ERROR: "Something went wrong", 8 | }); 9 | 10 | // These error codes are only visible in the server logs 11 | export const INTERNAL_ERROR_CODES = defineErrorCodes({ 12 | MISSING_SECRET_KEY: "Missing secret key", 13 | SERVICE_UNAVAILABLE: "CAPTCHA service unavailable", 14 | }); 15 | ``` -------------------------------------------------------------------------------- /demo/nextjs/components/theme-toggle.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | import { Moon, Sun } from "lucide-react"; 3 | import { useTheme } from "next-themes"; 4 | 5 | import { Button } from "@/components/ui/button"; 6 | 7 | export function ThemeToggle() { 8 | const { setTheme, theme } = useTheme(); 9 | 10 | return ( 11 | <Button 12 | variant="ghost" 13 | size="icon" 14 | onClick={() => setTheme(theme === "light" ? "dark" : "light")} 15 | > 16 | <Sun className="h-6 w-[1.3rem] dark:hidden" color="#000" /> 17 | <Moon className="hidden h-5 w-5 dark:block" /> 18 | <span className="sr-only">Toggle theme</span> 19 | </Button> 20 | ); 21 | } 22 | ``` -------------------------------------------------------------------------------- /docs/proxy.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { isMarkdownPreferred, rewritePath } from "fumadocs-core/negotiation"; 3 | 4 | const { rewrite: rewriteLLM } = rewritePath("/docs/*path", "/llms.txt/*path"); 5 | 6 | export function proxy(request: NextRequest) { 7 | if (isMarkdownPreferred(request)) { 8 | const result = rewriteLLM(request.nextUrl.pathname); 9 | 10 | if (result) { 11 | return NextResponse.rewrite(new URL(result, request.nextUrl)); 12 | } 13 | } 14 | 15 | return NextResponse.next(); 16 | } 17 | 18 | export const config = { 19 | matcher: "/docs/:path*", 20 | }; 21 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/username/error-codes.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { defineErrorCodes } from "@better-auth/core/utils"; 2 | 3 | export const USERNAME_ERROR_CODES = defineErrorCodes({ 4 | INVALID_USERNAME_OR_PASSWORD: "Invalid username or password", 5 | EMAIL_NOT_VERIFIED: "Email not verified", 6 | UNEXPECTED_ERROR: "Unexpected error", 7 | USERNAME_IS_ALREADY_TAKEN: "Username is already taken. Please try another.", 8 | USERNAME_TOO_SHORT: "Username is too short", 9 | USERNAME_TOO_LONG: "Username is too long", 10 | INVALID_USERNAME: "Username is invalid", 11 | INVALID_DISPLAY_USERNAME: "Display username is invalid", 12 | }); 13 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/siwe/schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { BetterAuthPluginDBSchema } from "@better-auth/core/db"; 2 | 3 | export const schema = { 4 | walletAddress: { 5 | fields: { 6 | userId: { 7 | type: "string", 8 | references: { 9 | model: "user", 10 | field: "id", 11 | }, 12 | required: true, 13 | }, 14 | address: { 15 | type: "string", 16 | required: true, 17 | }, 18 | chainId: { 19 | type: "number", 20 | required: true, 21 | }, 22 | isPrimary: { 23 | type: "boolean", 24 | defaultValue: false, 25 | }, 26 | createdAt: { 27 | type: "date", 28 | required: true, 29 | }, 30 | }, 31 | }, 32 | } satisfies BetterAuthPluginDBSchema; 33 | ``` -------------------------------------------------------------------------------- /packages/cli/src/utils/check-package-managers.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { exec } from "child_process"; 2 | 3 | function checkCommand(command: string): Promise<boolean> { 4 | return new Promise((resolve) => { 5 | exec(`${command} --version`, (error) => { 6 | if (error) { 7 | resolve(false); // Command not found or error occurred 8 | } else { 9 | resolve(true); // Command exists 10 | } 11 | }); 12 | }); 13 | } 14 | 15 | export async function checkPackageManagers(): Promise<{ 16 | hasPnpm: boolean; 17 | hasBun: boolean; 18 | }> { 19 | const hasPnpm = await checkCommand("pnpm"); 20 | const hasBun = await checkCommand("bun"); 21 | 22 | return { 23 | hasPnpm, 24 | hasBun, 25 | }; 26 | } 27 | ``` -------------------------------------------------------------------------------- /packages/core/src/db/schema/user.ts: -------------------------------------------------------------------------------- ```typescript 1 | import * as z from "zod"; 2 | import { coreSchema } from "./shared"; 3 | 4 | export const userSchema = coreSchema.extend({ 5 | email: z.string().transform((val) => val.toLowerCase()), 6 | emailVerified: z.boolean().default(false), 7 | name: z.string(), 8 | image: z.string().nullish(), 9 | }); 10 | 11 | /** 12 | * User schema type used by better-auth, note that it's possible that user could have additional fields 13 | * 14 | * todo: we should use generics to extend this type with additional fields from plugins and options in the future 15 | */ 16 | export type User = z.infer<typeof userSchema>; 17 | ``` -------------------------------------------------------------------------------- /packages/core/src/db/schema/session.ts: -------------------------------------------------------------------------------- ```typescript 1 | import * as z from "zod"; 2 | import { coreSchema } from "./shared"; 3 | 4 | export const sessionSchema = coreSchema.extend({ 5 | userId: z.coerce.string(), 6 | expiresAt: z.date(), 7 | token: z.string(), 8 | ipAddress: z.string().nullish(), 9 | userAgent: z.string().nullish(), 10 | }); 11 | 12 | /** 13 | * Session schema type used by better-auth, note that it's possible that session could have additional fields 14 | * 15 | * todo: we should use generics to extend this type with additional fields from plugins and options in the future 16 | */ 17 | export type Session = z.infer<typeof sessionSchema>; 18 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/email-otp/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { createHash } from "@better-auth/utils/hash"; 2 | import { base64Url } from "@better-auth/utils/base64"; 3 | export const defaultKeyHasher = async (otp: string) => { 4 | const hash = await createHash("SHA-256").digest( 5 | new TextEncoder().encode(otp), 6 | ); 7 | const hashed = base64Url.encode(new Uint8Array(hash), { 8 | padding: false, 9 | }); 10 | return hashed; 11 | }; 12 | 13 | export function splitAtLastColon(input: string): [string, string] { 14 | const idx = input.lastIndexOf(":"); 15 | if (idx === -1) { 16 | return [input, ""]; 17 | } 18 | return [input.slice(0, idx), input.slice(idx + 1)]; 19 | } 20 | ``` -------------------------------------------------------------------------------- /demo/expo-example/src/lib/auth.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { betterAuth } from "better-auth"; 2 | import { expo } from "@better-auth/expo"; 3 | import { Pool } from "pg"; 4 | 5 | export const auth = betterAuth({ 6 | database: new Pool({ 7 | connectionString: process.env.DATABASE_URL, 8 | }), 9 | emailAndPassword: { 10 | enabled: true, 11 | }, 12 | plugins: [expo()], 13 | socialProviders: { 14 | github: { 15 | clientId: process.env.GITHUB_CLIENT_ID!, 16 | clientSecret: process.env.GITHUB_CLIENT_SECRET!, 17 | }, 18 | google: { 19 | clientId: process.env.GOOGLE_CLIENT_ID!, 20 | clientSecret: process.env.GOOGLE_CLIENT_SECRET!, 21 | }, 22 | }, 23 | trustedOrigins: ["exp://"], 24 | }); 25 | ``` -------------------------------------------------------------------------------- /docs/hooks/use-mobile.ts: -------------------------------------------------------------------------------- ```typescript 1 | import * as React from "react"; 2 | 3 | const MOBILE_BREAKPOINT = 768; 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState<boolean | undefined>( 7 | undefined, 8 | ); 9 | 10 | React.useEffect(() => { 11 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); 12 | const onChange = () => { 13 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 14 | }; 15 | mql.addEventListener("change", onChange); 16 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 17 | return () => mql.removeEventListener("change", onChange); 18 | }, []); 19 | 20 | return !!isMobile; 21 | } 22 | ``` -------------------------------------------------------------------------------- /e2e/smoke/test/fixtures/deno-simple.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { betterAuth } from "better-auth"; 2 | import { DatabaseSync } from "node:sqlite"; 3 | import { getMigrations } from "better-auth/db"; 4 | 5 | const database = new DatabaseSync(":memory:"); 6 | 7 | export const auth = betterAuth({ 8 | baseURL: "http://localhost:4000", 9 | database, 10 | emailAndPassword: { 11 | enabled: true, 12 | }, 13 | logger: { 14 | level: "debug", 15 | }, 16 | }); 17 | 18 | const { runMigrations } = await getMigrations(auth.options); 19 | 20 | await runMigrations(); 21 | 22 | Deno.serve( 23 | { 24 | port: 0, 25 | onListen: ({ port }) => { 26 | console.log(`Listening on http://localhost:${port}`); 27 | }, 28 | }, 29 | auth.handler, 30 | ); 31 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/two-factor/error-code.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { defineErrorCodes } from "@better-auth/core/utils"; 2 | 3 | export const TWO_FACTOR_ERROR_CODES = defineErrorCodes({ 4 | OTP_NOT_ENABLED: "OTP not enabled", 5 | OTP_HAS_EXPIRED: "OTP has expired", 6 | TOTP_NOT_ENABLED: "TOTP not enabled", 7 | TWO_FACTOR_NOT_ENABLED: "Two factor isn't enabled", 8 | BACKUP_CODES_NOT_ENABLED: "Backup codes aren't enabled", 9 | INVALID_BACKUP_CODE: "Invalid backup code", 10 | INVALID_CODE: "Invalid code", 11 | TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE: 12 | "Too many attempts. Please request a new code.", 13 | INVALID_TWO_FACTOR_COOKIE: "Invalid two factor cookie", 14 | }); 15 | ``` -------------------------------------------------------------------------------- /e2e/integration/solid-vinxi/src/lib/auth.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { betterAuth } from "better-auth"; 2 | import Database from "better-sqlite3"; 3 | import { getMigrations } from "better-auth/db"; 4 | 5 | const database = new Database(":memory:"); 6 | const baseURL = process.env.BASE_URL || "http://localhost:3000"; 7 | 8 | export const auth = betterAuth({ 9 | database, 10 | baseURL, 11 | emailAndPassword: { 12 | enabled: true, 13 | }, 14 | }); 15 | 16 | const { runMigrations } = await getMigrations(auth.options); 17 | 18 | await runMigrations(); 19 | // Create an example user 20 | await auth.api.signUpEmail({ 21 | body: { 22 | name: "Test User", 23 | email: "[email protected]", 24 | password: "password123", 25 | }, 26 | }); 27 | ``` -------------------------------------------------------------------------------- /docs/lib/source.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { changelogCollection, docs, blogCollection } from "@/.source"; 2 | import { getPageTree } from "@/components/sidebar-content"; 3 | import { loader } from "fumadocs-core/source"; 4 | import { createMDXSource } from "fumadocs-mdx"; 5 | 6 | export let source = loader({ 7 | baseUrl: "/docs", 8 | source: docs.toFumadocsSource(), 9 | }); 10 | 11 | source = { ...source, pageTree: getPageTree() }; 12 | 13 | export const changelogs = loader({ 14 | baseUrl: "/changelogs", 15 | source: createMDXSource(changelogCollection), 16 | }); 17 | 18 | export const blogs = loader({ 19 | baseUrl: "/blogs", 20 | source: createMDXSource(blogCollection), 21 | }); 22 | ``` -------------------------------------------------------------------------------- /e2e/integration/solid-vinxi/src/routes/[...404].tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { A } from "@solidjs/router"; 2 | 3 | export default function NotFound() { 4 | return ( 5 | <main class="text-center mx-auto text-gray-700 p-4"> 6 | <h1 class="max-6-xs text-6xl text-sky-700 font-thin uppercase my-16"> 7 | Not Found 8 | </h1> 9 | <p class="mt-8"> 10 | Visit{" "} 11 | <a 12 | href="https://solidjs.com" 13 | target="_blank" 14 | class="text-sky-600 hover:underline" 15 | > 16 | solidjs.com 17 | </a>{" "} 18 | to learn how to build Solid apps. 19 | </p> 20 | <p class="my-4"> 21 | <A href="/" class="text-sky-600 hover:underline"> 22 | Home 23 | </A> 24 | </p> 25 | </main> 26 | ); 27 | } 28 | ``` -------------------------------------------------------------------------------- /packages/core/src/oauth2/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | export type { 2 | OAuth2Tokens, 3 | OAuthProvider, 4 | OAuth2UserInfo, 5 | ProviderOptions, 6 | } from "./oauth-provider"; 7 | 8 | export { generateCodeChallenge, getOAuth2Tokens } from "./utils"; 9 | export { createAuthorizationURL } from "./create-authorization-url"; 10 | export { 11 | createAuthorizationCodeRequest, 12 | validateAuthorizationCode, 13 | validateToken, 14 | } from "./validate-authorization-code"; 15 | export { 16 | createRefreshAccessTokenRequest, 17 | refreshAccessToken, 18 | } from "./refresh-access-token"; 19 | export { 20 | clientCredentialsToken, 21 | createClientCredentialsTokenRequest, 22 | } from "./client-credentials-token"; 23 | ``` -------------------------------------------------------------------------------- /packages/telemetry/src/detectors/detect-framework.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { getPackageVersion } from "../utils/package-json"; 2 | 3 | const FRAMEWORKS: Record<string, string> = { 4 | next: "next", 5 | nuxt: "nuxt", 6 | "@remix-run/server-runtime": "remix", 7 | astro: "astro", 8 | "@sveltejs/kit": "sveltekit", 9 | "solid-start": "solid-start", 10 | "tanstack-start": "tanstack-start", 11 | hono: "hono", 12 | express: "express", 13 | elysia: "elysia", 14 | expo: "expo", 15 | }; 16 | 17 | export async function detectFramework() { 18 | for (const [pkg, name] of Object.entries(FRAMEWORKS)) { 19 | const version = await getPackageVersion(pkg); 20 | if (version) return { name, version }; 21 | } 22 | return undefined; 23 | } 24 | ``` -------------------------------------------------------------------------------- /docs/content/docs/integrations/solid-start.mdx: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: SolidStart Integration 3 | description: Integrate Better Auth with SolidStart. 4 | --- 5 | 6 | Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation). 7 | 8 | ### Mount the handler 9 | 10 | We need to mount the handler to SolidStart server. Put the following code in your `*auth.ts` file inside `/routes/api/auth` folder. 11 | 12 | ```ts title="*auth.ts" 13 | import { auth } from "~/lib/auth"; 14 | import { toSolidStartHandler } from "better-auth/solid-start"; 15 | 16 | export const { GET, POST } = toSolidStartHandler(auth); 17 | ``` ``` -------------------------------------------------------------------------------- /packages/better-auth/src/client/plugins/infer-plugin.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { BetterAuthClientPlugin } from "@better-auth/core"; 2 | import type { BetterAuthOptions } from "@better-auth/core"; 3 | 4 | export const InferServerPlugin = < 5 | AuthOrOption extends 6 | | BetterAuthOptions 7 | | { 8 | options: BetterAuthOptions; 9 | }, 10 | ID extends string, 11 | >() => { 12 | type Option = AuthOrOption extends { options: infer O } ? O : AuthOrOption; 13 | type Plugin = Option["plugins"] extends Array<infer P> 14 | ? P extends { 15 | id: ID; 16 | } 17 | ? P 18 | : never 19 | : never; 20 | return { 21 | id: "infer-server-plugin", 22 | $InferServerPlugin: {} as Plugin, 23 | } satisfies BetterAuthClientPlugin; 24 | }; 25 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/oauth2/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { symmetricDecrypt, symmetricEncrypt } from "../crypto"; 2 | import type { AuthContext } from "@better-auth/core"; 3 | 4 | export function decryptOAuthToken(token: string, ctx: AuthContext) { 5 | if (!token) return token; 6 | if (ctx.options.account?.encryptOAuthTokens) { 7 | return symmetricDecrypt({ 8 | key: ctx.secret, 9 | data: token, 10 | }); 11 | } 12 | return token; 13 | } 14 | 15 | export function setTokenUtil( 16 | token: string | null | undefined, 17 | ctx: AuthContext, 18 | ) { 19 | if (ctx.options.account?.encryptOAuthTokens && token) { 20 | return symmetricEncrypt({ 21 | key: ctx.secret, 22 | data: token, 23 | }); 24 | } 25 | return token; 26 | } 27 | ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/label.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as LabelPrimitive from "@radix-ui/react-label"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps<typeof LabelPrimitive.Root>) { 12 | return ( 13 | <LabelPrimitive.Root 14 | data-slot="label" 15 | className={cn( 16 | "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", 17 | className, 18 | )} 19 | {...props} 20 | /> 21 | ); 22 | } 23 | 24 | export { Label }; 25 | ``` -------------------------------------------------------------------------------- /docs/components/ui/label.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as LabelPrimitive from "@radix-ui/react-label"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps<typeof LabelPrimitive.Root>) { 12 | return ( 13 | <LabelPrimitive.Root 14 | data-slot="label" 15 | className={cn( 16 | "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", 17 | className, 18 | )} 19 | {...props} 20 | /> 21 | ); 22 | } 23 | 24 | export { Label }; 25 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/client/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { BetterAuthOptions, BetterAuthPlugin } from "@better-auth/core"; 2 | import type { BetterAuthClientPlugin } from "@better-auth/core"; 3 | export * from "./vanilla"; 4 | export * from "./query"; 5 | export * from "./types"; 6 | 7 | export const InferPlugin = <T extends BetterAuthPlugin>() => { 8 | return { 9 | id: "infer-server-plugin", 10 | $InferServerPlugin: {} as T, 11 | } satisfies BetterAuthClientPlugin; 12 | }; 13 | 14 | export function InferAuth<O extends { options: BetterAuthOptions }>() { 15 | return {} as O["options"]; 16 | } 17 | 18 | // @ts-expect-error 19 | export type * from "nanostores"; 20 | export type * from "@better-fetch/fetch"; 21 | ``` -------------------------------------------------------------------------------- /e2e/smoke/test/fixtures/cloudflare/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "cloudflare", 3 | "private": true, 4 | "dependencies": { 5 | "better-auth": "workspace:*", 6 | "drizzle-orm": "^0.44.5", 7 | "hono": "^4.9.7" 8 | }, 9 | "devDependencies": { 10 | "@cloudflare/vitest-pool-workers": "^0.8.69", 11 | "@cloudflare/workers-types": "^4.20250903.0", 12 | "drizzle-kit": "^0.31.4", 13 | "wrangler": "4.33.2" 14 | }, 15 | "scripts": { 16 | "check": "tsc && wrangler deploy --dry-run", 17 | "dev": "wrangler dev", 18 | "migrate:local": "wrangler d1 migrations apply db --local", 19 | "cf-typegen": "wrangler types --env-interface CloudflareBindings", 20 | "e2e:smoke": "vitest" 21 | } 22 | } 23 | ``` -------------------------------------------------------------------------------- /e2e/integration/vanilla-node/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | "moduleResolution": "bundler", 9 | "allowImportingTsExtensions": true, 10 | "verbatimModuleSyntax": true, 11 | "esModuleInterop": true, 12 | "moduleDetection": "force", 13 | "noEmit": true, 14 | "strict": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "erasableSyntaxOnly": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "noUncheckedSideEffectImports": true 20 | }, 21 | "include": ["src", "e2e"] 22 | } 23 | ``` -------------------------------------------------------------------------------- /e2e/smoke/test/fixtures/cloudflare/vitest.config.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | defineWorkersProject, 3 | readD1Migrations, 4 | } from "@cloudflare/vitest-pool-workers/config"; 5 | import path from "node:path"; 6 | 7 | export default defineWorkersProject(async () => { 8 | const migrationsPath = path.join(__dirname, "drizzle"); 9 | const migrations = await readD1Migrations(migrationsPath); 10 | 11 | return { 12 | test: { 13 | setupFiles: ["./test/apply-migrations.ts"], 14 | poolOptions: { 15 | workers: { 16 | singleWorker: true, 17 | wrangler: { configPath: "./wrangler.json" }, 18 | miniflare: { 19 | d1Databases: ["DB"], 20 | bindings: { TEST_MIGRATIONS: migrations }, 21 | }, 22 | }, 23 | }, 24 | }, 25 | }; 26 | }); 27 | ``` -------------------------------------------------------------------------------- /packages/telemetry/src/detectors/detect-database.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { getPackageVersion } from "../utils/package-json"; 2 | import type { DetectionInfo } from "../types"; 3 | 4 | const DATABASES: Record<string, string> = { 5 | pg: "postgresql", 6 | mysql: "mysql", 7 | mariadb: "mariadb", 8 | sqlite3: "sqlite", 9 | "better-sqlite3": "sqlite", 10 | "@prisma/client": "prisma", 11 | mongoose: "mongodb", 12 | mongodb: "mongodb", 13 | "drizzle-orm": "drizzle", 14 | }; 15 | 16 | export async function detectDatabase(): Promise<DetectionInfo | undefined> { 17 | for (const [pkg, name] of Object.entries(DATABASES)) { 18 | const version = await getPackageVersion(pkg); 19 | if (version) return { name, version }; 20 | } 21 | return undefined; 22 | } 23 | ``` -------------------------------------------------------------------------------- /demo/nextjs/turbo.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "env": [ 7 | "TURSO_DATABASE_URL", 8 | "TURSO_AUTH_TOKEN", 9 | "RESEND_API_KEY", 10 | "BETTER_AUTH_EMAIL", 11 | "BETTER_AUTH_SECRET", 12 | "BETTER_AUTH_URL", 13 | "GITHUB_CLIENT_SECRET", 14 | "GITHUB_CLIENT_ID", 15 | "GOOGLE_CLIENT_ID", 16 | "GOOGLE_CLIENT_SECRET", 17 | "DISCORD_CLIENT_ID", 18 | "DISCORD_CLIENT_SECRET", 19 | "MICROSOFT_CLIENT_ID", 20 | "MICROSOFT_CLIENT_SECRET", 21 | "STRIPE_KEY", 22 | "STRIPE_WEBHOOK_SECRET" 23 | ] 24 | } 25 | } 26 | } 27 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/utils/json.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { logger } from "@better-auth/core/env"; 2 | 3 | export function safeJSONParse<T>(data: unknown): T | null { 4 | function reviver(_: string, value: any): any { 5 | if (typeof value === "string") { 6 | const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/; 7 | if (iso8601Regex.test(value)) { 8 | const date = new Date(value); 9 | if (!isNaN(date.getTime())) { 10 | return date; 11 | } 12 | } 13 | } 14 | return value; 15 | } 16 | try { 17 | if (typeof data !== "string") { 18 | return data as T; 19 | } 20 | return JSON.parse(data, reviver); 21 | } catch (e) { 22 | logger.error("Error parsing JSON", { error: e }); 23 | return null; 24 | } 25 | } 26 | ``` -------------------------------------------------------------------------------- /docs/app/blog/layout.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { Metadata } from "next"; 2 | 3 | export const metadata: Metadata = { 4 | title: "Blog - Better Auth", 5 | description: "Latest updates, articles, and insights about Better Auth", 6 | }; 7 | 8 | interface BlogLayoutProps { 9 | children: React.ReactNode; 10 | } 11 | 12 | export default function BlogLayout({ children }: BlogLayoutProps) { 13 | return ( 14 | <div 15 | className="relative flex min-h-screen flex-col" 16 | style={{ 17 | scrollbarWidth: "none", 18 | scrollbarColor: "transparent transparent", 19 | //@ts-expect-error 20 | "&::-webkit-scrollbar": { 21 | display: "none", 22 | }, 23 | }} 24 | > 25 | <main className="flex-1">{children}</main> 26 | </div> 27 | ); 28 | } 29 | ``` -------------------------------------------------------------------------------- /docs/app/community/_components/header.tsx: -------------------------------------------------------------------------------- ```typescript 1 | export default function CommunityHeader() { 2 | return ( 3 | <div className="h-full flex flex-col justify-center items-center text-white"> 4 | <div className="max-w-6xl mx-auto px-4 py-16"> 5 | <div className="text-center"> 6 | <h1 className="text-4xl tracking-tighter md:text-5xl mt-3 font-normal mb-6 text-stone-800 dark:text-white"> 7 | Open Source Community 8 | </h1> 9 | <p className="dark:text-gray-400 max-w-md mx-auto text-stone-800"> 10 | join <span className="italic font-bold">better-auth</span> community 11 | to get help, share ideas, and stay up to date. 12 | </p> 13 | </div> 14 | </div> 15 | </div> 16 | ); 17 | } 18 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/cookies/check-cookies.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { parseCookies } from "../cookies"; 2 | import type { AuthContext } from "@better-auth/core"; 3 | 4 | export const checkAuthCookie = async ( 5 | request: Request | Headers, 6 | auth: { 7 | $context: Promise<AuthContext>; 8 | }, 9 | ) => { 10 | const headers = request instanceof Headers ? request : request.headers; 11 | const cookies = headers.get("cookie"); 12 | if (!cookies) { 13 | return null; 14 | } 15 | const ctx = await auth.$context; 16 | const cookieName = ctx.authCookies.sessionToken.name; 17 | const parsedCookie = parseCookies(cookies); 18 | const sessionToken = parsedCookie.get(cookieName); 19 | if (sessionToken) { 20 | return sessionToken; 21 | } 22 | return null; 23 | }; 24 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/two-factor/schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { BetterAuthPluginDBSchema } from "@better-auth/core/db"; 2 | 3 | export const schema = { 4 | user: { 5 | fields: { 6 | twoFactorEnabled: { 7 | type: "boolean", 8 | required: false, 9 | defaultValue: false, 10 | input: false, 11 | }, 12 | }, 13 | }, 14 | twoFactor: { 15 | fields: { 16 | secret: { 17 | type: "string", 18 | required: true, 19 | returned: false, 20 | }, 21 | backupCodes: { 22 | type: "string", 23 | required: true, 24 | returned: false, 25 | }, 26 | userId: { 27 | type: "string", 28 | required: true, 29 | returned: false, 30 | references: { 31 | model: "user", 32 | field: "id", 33 | }, 34 | }, 35 | }, 36 | }, 37 | } satisfies BetterAuthPluginDBSchema; 38 | ``` -------------------------------------------------------------------------------- /docs/public/branding/better-auth-logo-dark.svg: -------------------------------------------------------------------------------- ``` 1 | <svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <rect width="500" height="500" fill="black"/> 3 | <rect x="69" y="121" width="86.9879" height="259" fill="white"/> 4 | <rect x="337.575" y="121" width="92.4247" height="259" fill="white"/> 5 | <rect x="427.282" y="121" width="83.4555" height="174.52" transform="rotate(90 427.282 121)" fill="white"/> 6 | <rect x="430" y="296.544" width="83.4555" height="177.238" transform="rotate(90 430 296.544)" fill="white"/> 7 | <rect x="252.762" y="204.455" width="92.0888" height="96.7741" transform="rotate(90 252.762 204.455)" fill="white"/> 8 | </svg> 9 | ``` -------------------------------------------------------------------------------- /docs/public/branding/better-auth-logo-light.svg: -------------------------------------------------------------------------------- ``` 1 | <svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <rect width="500" height="500" fill="white"/> 3 | <rect x="69" y="121" width="86.9879" height="259" fill="black"/> 4 | <rect x="337.575" y="121" width="92.4247" height="259" fill="black"/> 5 | <rect x="427.282" y="121" width="83.4555" height="174.52" transform="rotate(90 427.282 121)" fill="black"/> 6 | <rect x="430" y="296.544" width="83.4555" height="177.238" transform="rotate(90 430 296.544)" fill="black"/> 7 | <rect x="252.762" y="204.455" width="92.0888" height="96.7741" transform="rotate(90 252.762 204.455)" fill="black"/> 8 | </svg> 9 | ``` -------------------------------------------------------------------------------- /demo/nextjs/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules", "components/ui/**/*.tsx"] 27 | } 28 | ``` -------------------------------------------------------------------------------- /docs/app/llms.txt/[...slug]/route.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { type NextRequest, NextResponse } from "next/server"; 2 | import { source } from "@/lib/source"; 3 | import { notFound } from "next/navigation"; 4 | import { getLLMText } from "@/app/docs/lib/get-llm-text"; 5 | 6 | export const revalidate = false; 7 | 8 | export async function GET( 9 | _req: NextRequest, 10 | { params }: { params: Promise<{ slug: string[] }> }, 11 | ) { 12 | const slug = (await params).slug; 13 | const page = source.getPage(slug); 14 | if (!page) notFound(); 15 | 16 | return new NextResponse(await getLLMText(page), { 17 | headers: { 18 | "Content-Type": "text/markdown", 19 | }, 20 | }); 21 | } 22 | 23 | export function generateStaticParams() { 24 | return source.generateParams(); 25 | } 26 | ``` -------------------------------------------------------------------------------- /docs/components/landing/section-svg.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { Plus } from "lucide-react"; 2 | 3 | const SectionSvg = ({ crossesOffset }: { crossesOffset: string }) => { 4 | return ( 5 | <> 6 | <Plus 7 | className={`hidden absolute -top-[0.3125rem] h-6 w-6 ${ 8 | crossesOffset && crossesOffset 9 | } pointer-events-none lg:block lg:left-[3.275rem] text-neutral-300 dark:text-neutral-600 translate-y-[.5px]`} 10 | /> 11 | 12 | <Plus 13 | className={`hidden absolute -top-[0.3125rem] h-6 w-6 right-[1.4625rem] ${ 14 | crossesOffset && crossesOffset 15 | } pointer-events-none lg:block lg:right-[2.7750rem] text-neutral-300 dark:text-neutral-600 translate-y-[.5px]`} 16 | /> 17 | </> 18 | ); 19 | }; 20 | 21 | export default SectionSvg; 22 | ``` -------------------------------------------------------------------------------- /docs/components/builder/code-tabs/tab-bar.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { CodeTab } from "./code-tabs"; 2 | 3 | interface File { 4 | id: string; 5 | name: string; 6 | content: string; 7 | } 8 | 9 | interface TabBarProps { 10 | files: File[]; 11 | activeFileId: string; 12 | onTabClick: (fileId: string) => void; 13 | onTabClose: (fileId: string) => void; 14 | } 15 | 16 | export function TabBar({ 17 | files, 18 | activeFileId, 19 | onTabClick, 20 | onTabClose, 21 | }: TabBarProps) { 22 | return ( 23 | <div className="flex bg-muted border-b border-border"> 24 | {files.map((file) => ( 25 | <CodeTab 26 | key={file.id} 27 | fileName={file.name} 28 | isActive={file.id === activeFileId} 29 | onClick={() => onTabClick(file.id)} 30 | onClose={() => onTabClose(file.id)} 31 | /> 32 | ))} 33 | </div> 34 | ); 35 | } 36 | ``` -------------------------------------------------------------------------------- /packages/cli/src/utils/get-tsconfig-info.ts: -------------------------------------------------------------------------------- ```typescript 1 | import path from "path"; 2 | import fs from "fs"; 3 | 4 | export function stripJsonComments(jsonString: string): string { 5 | return jsonString 6 | .replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => 7 | g ? "" : m, 8 | ) 9 | .replace(/,(?=\s*[}\]])/g, ""); 10 | } 11 | export function getTsconfigInfo(cwd?: string, flatPath?: string) { 12 | let tsConfigPath: string; 13 | if (flatPath) { 14 | tsConfigPath = flatPath; 15 | } else { 16 | tsConfigPath = cwd 17 | ? path.join(cwd, "tsconfig.json") 18 | : path.join("tsconfig.json"); 19 | } 20 | try { 21 | const text = fs.readFileSync(tsConfigPath, "utf-8"); 22 | return JSON.parse(stripJsonComments(text)); 23 | } catch (error) { 24 | throw error; 25 | } 26 | } 27 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/crypto/hash.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { constantTimeEqual } from "./buffer"; 2 | import { createHash } from "@better-auth/utils/hash"; 3 | import { base64 } from "@better-auth/utils/base64"; 4 | 5 | export async function hashToBase64( 6 | data: string | ArrayBuffer, 7 | ): Promise<string> { 8 | const buffer = await createHash("SHA-256").digest(data); 9 | return base64.encode(buffer); 10 | } 11 | 12 | export async function compareHash( 13 | data: string | ArrayBuffer, 14 | hash: string, 15 | ): Promise<boolean> { 16 | const buffer = await createHash("SHA-256").digest( 17 | typeof data === "string" ? new TextEncoder().encode(data) : data, 18 | ); 19 | const hashBuffer = base64.decode(hash); 20 | return constantTimeEqual(buffer, hashBuffer); 21 | } 22 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/admin/schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { type BetterAuthPluginDBSchema } from "@better-auth/core/db"; 2 | 3 | export const schema = { 4 | user: { 5 | fields: { 6 | role: { 7 | type: "string", 8 | required: false, 9 | input: false, 10 | }, 11 | banned: { 12 | type: "boolean", 13 | defaultValue: false, 14 | required: false, 15 | input: false, 16 | }, 17 | banReason: { 18 | type: "string", 19 | required: false, 20 | input: false, 21 | }, 22 | banExpires: { 23 | type: "date", 24 | required: false, 25 | input: false, 26 | }, 27 | }, 28 | }, 29 | session: { 30 | fields: { 31 | impersonatedBy: { 32 | type: "string", 33 | required: false, 34 | }, 35 | }, 36 | }, 37 | } satisfies BetterAuthPluginDBSchema; 38 | 39 | export type AdminSchema = typeof schema; 40 | ``` -------------------------------------------------------------------------------- /demo/expo-example/src/components/ui/separator.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import * as SeparatorPrimitive from "@rn-primitives/separator"; 2 | import * as React from "react"; 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Separator = React.forwardRef< 6 | SeparatorPrimitive.RootRef, 7 | SeparatorPrimitive.RootProps 8 | >( 9 | ( 10 | { className, orientation = "horizontal", decorative = true, ...props }, 11 | ref, 12 | ) => ( 13 | <SeparatorPrimitive.Root 14 | ref={ref} 15 | decorative={decorative} 16 | orientation={orientation} 17 | className={cn( 18 | "shrink-0 bg-border", 19 | orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", 20 | className, 21 | )} 22 | {...props} 23 | /> 24 | ), 25 | ); 26 | Separator.displayName = SeparatorPrimitive.Root.displayName; 27 | 28 | export { Separator }; 29 | ``` -------------------------------------------------------------------------------- /docs/components/ui/separator.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Separator({ 9 | className, 10 | orientation = "horizontal", 11 | decorative = true, 12 | ...props 13 | }: React.ComponentProps<typeof SeparatorPrimitive.Root>) { 14 | return ( 15 | <SeparatorPrimitive.Root 16 | data-slot="separator-root" 17 | decorative={decorative} 18 | orientation={orientation} 19 | className={cn( 20 | "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", 21 | className, 22 | )} 23 | {...props} 24 | /> 25 | ); 26 | } 27 | 28 | export { Separator }; 29 | ``` -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "biome.enabled": true, 3 | "editor.defaultFormatter": "biomejs.biome", 4 | "[typescript]": { 5 | "editor.defaultFormatter": "biomejs.biome" 6 | }, 7 | "[jsonc]": { 8 | "editor.defaultFormatter": "biomejs.biome" 9 | }, 10 | "[svelte]": { 11 | "editor.defaultFormatter": "biomejs.biome" 12 | }, 13 | "[vue]": { 14 | "editor.defaultFormatter": "biomejs.biome" 15 | }, 16 | "[json]": { 17 | "editor.defaultFormatter": "biomejs.biome" 18 | }, 19 | "typescript.tsdk": "node_modules/typescript/lib", 20 | "[astro]": { 21 | "editor.defaultFormatter": "biomejs.biome" 22 | }, 23 | "[typescriptreact]": { 24 | "editor.defaultFormatter": "biomejs.biome" 25 | }, 26 | "[mdx]": { 27 | "editor.defaultFormatter": "unifiedjs.vscode-mdx" 28 | } 29 | } 30 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/admin/access/statement.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { createAccessControl } from "../../access"; 2 | 3 | export const defaultStatements = { 4 | user: [ 5 | "create", 6 | "list", 7 | "set-role", 8 | "ban", 9 | "impersonate", 10 | "delete", 11 | "set-password", 12 | "get", 13 | "update", 14 | ], 15 | session: ["list", "revoke", "delete"], 16 | } as const; 17 | 18 | export const defaultAc = createAccessControl(defaultStatements); 19 | 20 | export const adminAc = defaultAc.newRole({ 21 | user: [ 22 | "create", 23 | "list", 24 | "set-role", 25 | "ban", 26 | "impersonate", 27 | "delete", 28 | "set-password", 29 | "get", 30 | "update", 31 | ], 32 | session: ["list", "revoke", "delete"], 33 | }); 34 | 35 | export const userAc = defaultAc.newRole({ 36 | user: [], 37 | session: [], 38 | }); 39 | 40 | export const defaultRoles = { 41 | admin: adminAc, 42 | user: userAc, 43 | }; 44 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/captcha/constants.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { Provider } from "./types"; 2 | 3 | export const defaultEndpoints = [ 4 | "/sign-up/email", 5 | "/sign-in/email", 6 | "/forget-password", 7 | ]; 8 | 9 | export const Providers = { 10 | CLOUDFLARE_TURNSTILE: "cloudflare-turnstile", 11 | GOOGLE_RECAPTCHA: "google-recaptcha", 12 | HCAPTCHA: "hcaptcha", 13 | CAPTCHAFOX: "captchafox", 14 | } as const; 15 | 16 | export const siteVerifyMap: Record<Provider, string> = { 17 | [Providers.CLOUDFLARE_TURNSTILE]: 18 | "https://challenges.cloudflare.com/turnstile/v0/siteverify", 19 | [Providers.GOOGLE_RECAPTCHA]: 20 | "https://www.google.com/recaptcha/api/siteverify", 21 | [Providers.HCAPTCHA]: "https://api.hcaptcha.com/siteverify", 22 | [Providers.CAPTCHAFOX]: "https://api.captchafox.com/siteverify", 23 | }; 24 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/integrations/node.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { toNodeHandler as toNode } from "better-call/node"; 2 | import type { Auth } from "../auth"; 3 | import type { IncomingHttpHeaders } from "http"; 4 | 5 | export const toNodeHandler = ( 6 | auth: 7 | | { 8 | handler: Auth["handler"]; 9 | } 10 | | Auth["handler"], 11 | ) => { 12 | return "handler" in auth ? toNode(auth.handler) : toNode(auth); 13 | }; 14 | 15 | export function fromNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers { 16 | const webHeaders = new Headers(); 17 | for (const [key, value] of Object.entries(nodeHeaders)) { 18 | if (value !== undefined) { 19 | if (Array.isArray(value)) { 20 | value.forEach((v) => webHeaders.append(key, v)); 21 | } else { 22 | webHeaders.set(key, value); 23 | } 24 | } 25 | } 26 | return webHeaders; 27 | } 28 | ``` -------------------------------------------------------------------------------- /packages/telemetry/src/project-id.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { generateId } from "./utils/id"; 2 | import { hashToBase64 } from "./utils/hash"; 3 | import { getNameFromLocalPackageJson } from "./utils/package-json"; 4 | 5 | let projectIdCached: string | null = null; 6 | 7 | export async function getProjectId( 8 | baseUrl: string | undefined, 9 | ): Promise<string> { 10 | if (projectIdCached) return projectIdCached; 11 | 12 | const projectName = await getNameFromLocalPackageJson(); 13 | if (projectName) { 14 | projectIdCached = await hashToBase64( 15 | baseUrl ? baseUrl + projectName : projectName, 16 | ); 17 | return projectIdCached; 18 | } 19 | 20 | if (baseUrl) { 21 | projectIdCached = await hashToBase64(baseUrl); 22 | return projectIdCached; 23 | } 24 | 25 | projectIdCached = generateId(32); 26 | return projectIdCached; 27 | } 28 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/test-utils/headers.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * converts set cookie containing headers to 3 | * cookie containing headers 4 | */ 5 | export function convertSetCookieToCookie(headers: Headers): Headers { 6 | const setCookieHeaders: string[] = []; 7 | headers.forEach((value, name) => { 8 | if (name.toLowerCase() === "set-cookie") { 9 | setCookieHeaders.push(value); 10 | } 11 | }); 12 | 13 | if (setCookieHeaders.length === 0) { 14 | return headers; 15 | } 16 | 17 | const existingCookies = headers.get("cookie") || ""; 18 | const cookies = existingCookies ? existingCookies.split("; ") : []; 19 | 20 | setCookieHeaders.forEach((setCookie) => { 21 | const cookiePair = setCookie.split(";")[0]!; 22 | cookies.push(cookiePair.trim()); 23 | }); 24 | 25 | headers.set("cookie", cookies.join("; ")); 26 | 27 | return headers; 28 | } 29 | ``` -------------------------------------------------------------------------------- /docs/components/ui/progress.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as ProgressPrimitive from "@radix-ui/react-progress"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Progress({ 9 | className, 10 | value, 11 | ...props 12 | }: React.ComponentProps<typeof ProgressPrimitive.Root>) { 13 | return ( 14 | <ProgressPrimitive.Root 15 | data-slot="progress" 16 | className={cn( 17 | "bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", 18 | className, 19 | )} 20 | {...props} 21 | > 22 | <ProgressPrimitive.Indicator 23 | data-slot="progress-indicator" 24 | className="bg-primary h-full w-full flex-1 transition-all" 25 | style={{ transform: `translateX(-${100 - (value || 0)}%)` }} 26 | /> 27 | </ProgressPrimitive.Root> 28 | ); 29 | } 30 | 31 | export { Progress }; 32 | ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/toaster.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import { useToast } from "@/hooks/use-toast"; 4 | import { 5 | Toast, 6 | ToastClose, 7 | ToastDescription, 8 | ToastProvider, 9 | ToastTitle, 10 | ToastViewport, 11 | } from "@/components/ui/toast"; 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast(); 15 | 16 | return ( 17 | <ToastProvider> 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | <Toast key={id} {...props}> 21 | <div className="grid gap-1"> 22 | {title && <ToastTitle>{title}</ToastTitle>} 23 | {description && ( 24 | <ToastDescription>{description}</ToastDescription> 25 | )} 26 | </div> 27 | {action} 28 | <ToastClose /> 29 | </Toast> 30 | ); 31 | })} 32 | <ToastViewport /> 33 | </ToastProvider> 34 | ); 35 | } 36 | ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/textarea.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} 7 | 8 | const Textarea = ({ 9 | ref, 10 | className, 11 | ...props 12 | }: TextareaProps & { 13 | ref: React.RefObject<HTMLTextAreaElement>; 14 | }) => { 15 | return ( 16 | <textarea 17 | className={cn( 18 | "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", 19 | className, 20 | )} 21 | ref={ref} 22 | {...props} 23 | /> 24 | ); 25 | }; 26 | Textarea.displayName = "Textarea"; 27 | 28 | export { Textarea }; 29 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/client/client-ssr.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | // @vitest-environment node 2 | import { expect, it, vi } from "vitest"; 3 | import { createAuthClient as createVueClient } from "./vue"; 4 | 5 | it("should call '/api/auth' for vue client", async () => { 6 | const customFetchImpl = vi.fn(async (url: string | Request | URL) => { 7 | expect(url).toBe("/api/auth/get-session"); 8 | return new Response(); 9 | }); 10 | process.env.BETTER_AUTH_URL = "http://localhost:3000"; 11 | // use DisposableStack when Node.js 24 is the minimum requirement 12 | using _ = { 13 | [Symbol.dispose]() { 14 | process.env.BETTER_AUTH_URL = undefined; 15 | }, 16 | }; 17 | const client = createVueClient({ 18 | fetchOptions: { 19 | customFetchImpl, 20 | }, 21 | }); 22 | await client.getSession(); 23 | expect(customFetchImpl).toBeCalled(); 24 | }); 25 | ``` -------------------------------------------------------------------------------- /docs/components/fork-button.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import Link from "next/link"; 2 | import { Button } from "./ui/button"; 3 | import { GitHubLogoIcon } from "@radix-ui/react-icons"; 4 | import { ExternalLink } from "lucide-react"; 5 | 6 | export function ForkButton({ url }: { url: string }) { 7 | return ( 8 | <div className="flex items-center gap-2 my-2"> 9 | <Link href={`https://codesandbox.io/p/github/${url}`} target="_blank"> 10 | <Button className="gap-2" variant="outline" size="sm"> 11 | <ExternalLink size={12} /> 12 | Open in Stackblitz 13 | </Button> 14 | </Link> 15 | <Link href={`https://github.com/${url}`} target="_blank"> 16 | <Button className="gap-2" variant="secondary" size="sm"> 17 | <GitHubLogoIcon /> 18 | View on GitHub 19 | </Button> 20 | </Link> 21 | </div> 22 | ); 23 | } 24 | ``` -------------------------------------------------------------------------------- /docs/components/mobile-search-icon.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import { Search } from "lucide-react"; 4 | import { useSearchContext } from "fumadocs-ui/provider"; 5 | import { Button } from "@/components/ui/button"; 6 | import { cn } from "@/lib/utils"; 7 | 8 | interface MobileSearchIconProps { 9 | className?: string; 10 | } 11 | 12 | export function MobileSearchIcon({ className }: MobileSearchIconProps) { 13 | const { setOpenSearch } = useSearchContext(); 14 | 15 | const handleSearchClick = () => { 16 | setOpenSearch(true); 17 | }; 18 | 19 | return ( 20 | <Button 21 | variant="ghost" 22 | size="icon" 23 | aria-label="Search" 24 | onClick={handleSearchClick} 25 | className={cn( 26 | "flex ring-0 shrink-0 md:hidden size-9 hover:bg-transparent", 27 | className, 28 | )} 29 | > 30 | <Search className="size-4" /> 31 | </Button> 32 | ); 33 | } 34 | ``` -------------------------------------------------------------------------------- /packages/stripe/src/client.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { BetterAuthClientPlugin } from "better-auth"; 2 | import type { stripe } from "./index"; 3 | 4 | export const stripeClient = < 5 | O extends { 6 | subscription: boolean; 7 | }, 8 | >( 9 | options?: O, 10 | ) => { 11 | return { 12 | id: "stripe-client", 13 | $InferServerPlugin: {} as ReturnType< 14 | typeof stripe< 15 | O["subscription"] extends true 16 | ? { 17 | stripeClient: any; 18 | stripeWebhookSecret: string; 19 | subscription: { 20 | enabled: true; 21 | plans: []; 22 | }; 23 | } 24 | : { 25 | stripeClient: any; 26 | stripeWebhookSecret: string; 27 | } 28 | > 29 | >, 30 | pathMethods: { 31 | "/subscription/restore": "POST", 32 | "/subscription/billing-portal": "POST", 33 | }, 34 | } satisfies BetterAuthClientPlugin; 35 | }; 36 | ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/separator.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Separator = ({ 9 | ref, 10 | className, 11 | orientation = "horizontal", 12 | decorative = true, 13 | ...props 14 | }: React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> & { 15 | ref: React.RefObject<React.ElementRef<typeof SeparatorPrimitive.Root>>; 16 | }) => ( 17 | <SeparatorPrimitive.Root 18 | ref={ref} 19 | decorative={decorative} 20 | orientation={orientation} 21 | className={cn( 22 | "shrink-0 bg-border", 23 | orientation === "horizontal" ? "h-px w-full" : "h-full w-px", 24 | className, 25 | )} 26 | {...props} 27 | /> 28 | ); 29 | Separator.displayName = SeparatorPrimitive.Root.displayName; 30 | 31 | export { Separator }; 32 | ``` -------------------------------------------------------------------------------- /docs/components/ui/textarea.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 | return ( 7 | <textarea 8 | data-slot="textarea" 9 | className={cn( 10 | "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 11 | className, 12 | )} 13 | {...props} 14 | /> 15 | ); 16 | } 17 | 18 | export { Textarea }; 19 | ``` -------------------------------------------------------------------------------- /docs/app/changelogs/_components/grid-pattern.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function GridPatterns() { 6 | return ( 7 | <div 8 | className={cn( 9 | "pointer-events-none", 10 | "fixed inset-y-0 left-0 z-0", 11 | "w-1/2 h-full", 12 | "overflow-hidden", 13 | )} 14 | aria-hidden="true" 15 | > 16 | <div className="absolute opacity-40 inset-0 w-full h-full bg-[radial-gradient(circle_at_center,rgba(0,0,0,0.04)_1px,transparent_1px)] dark:bg-[radial-gradient(circle_at_center,rgba(255,255,255,0.04)_1px,transparent_1px)] bg-[length:8px_8px]" /> 17 | <div className="absolute top-0 left-0 w-full h-full bg-gradient-to-br opacity-40 from-white/40 via-transparent to-white/10 dark:from-white/10 dark:to-transparent mix-blend-screen" /> 18 | </div> 19 | ); 20 | } 21 | 22 | export { GridPatterns }; 23 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/types/plugins.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { UnionToIntersection } from "./helper"; 2 | 3 | import type { BetterAuthPluginDBSchema } from "@better-auth/core/db"; 4 | import type { BetterAuthOptions, BetterAuthPlugin } from "@better-auth/core"; 5 | 6 | export type InferOptionSchema<S extends BetterAuthPluginDBSchema> = 7 | S extends Record<string, { fields: infer Fields }> 8 | ? { 9 | [K in keyof S]?: { 10 | modelName?: string; 11 | fields?: { 12 | [P in keyof Fields]?: string; 13 | }; 14 | }; 15 | } 16 | : never; 17 | 18 | export type InferPluginErrorCodes<O extends BetterAuthOptions> = 19 | O["plugins"] extends Array<infer P> 20 | ? UnionToIntersection< 21 | P extends BetterAuthPlugin 22 | ? P["$ERROR_CODES"] extends Record<string, any> 23 | ? P["$ERROR_CODES"] 24 | : {} 25 | : {} 26 | > 27 | : {}; 28 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/adapters/prisma-adapter/test/generate-auth-config.ts: -------------------------------------------------------------------------------- ```typescript 1 | import fs from "fs/promises"; 2 | import type { BetterAuthOptions } from "@better-auth/core"; 3 | import path from "path"; 4 | 5 | export const generateAuthConfigFile = async (_options: BetterAuthOptions) => { 6 | const options = { ..._options }; 7 | // biome-ignore lint/performance/noDelete: perf doesn't matter here. 8 | delete options.database; 9 | let code = `import { betterAuth } from "../../../auth"; 10 | import { prismaAdapter } from "../prisma-adapter"; 11 | import { PrismaClient } from "@prisma/client"; 12 | const db = new PrismaClient(); 13 | 14 | export const auth = betterAuth({ 15 | database: prismaAdapter(db, { 16 | provider: 'sqlite' 17 | }), 18 | ${JSON.stringify(options, null, 2).slice(1, -1)} 19 | })`; 20 | 21 | await fs.writeFile(path.join(import.meta.dirname, "auth.ts"), code); 22 | }; 23 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/api/routes/ok.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { HIDE_METADATA } from "../../utils/hide-metadata"; 2 | import { createAuthEndpoint } from "@better-auth/core/middleware"; 3 | 4 | export const ok = createAuthEndpoint( 5 | "/ok", 6 | { 7 | method: "GET", 8 | metadata: { 9 | ...HIDE_METADATA, 10 | openapi: { 11 | description: "Check if the API is working", 12 | responses: { 13 | "200": { 14 | description: "API is working", 15 | content: { 16 | "application/json": { 17 | schema: { 18 | type: "object", 19 | properties: { 20 | ok: { 21 | type: "boolean", 22 | description: "Indicates if the API is working", 23 | }, 24 | }, 25 | required: ["ok"], 26 | }, 27 | }, 28 | }, 29 | }, 30 | }, 31 | }, 32 | }, 33 | }, 34 | async (ctx) => { 35 | return ctx.json({ 36 | ok: true, 37 | }); 38 | }, 39 | ); 40 | ``` -------------------------------------------------------------------------------- /e2e/smoke/test/fixtures/tsconfig-exact-optional-property-types/src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { betterAuth } from "better-auth"; 2 | import { organization } from "better-auth/plugins"; 3 | import { nextCookies } from "better-auth/next-js"; 4 | import Database from "better-sqlite3"; 5 | import { createAuthClient } from "better-auth/react"; 6 | import { 7 | inferAdditionalFields, 8 | organizationClient, 9 | } from "better-auth/client/plugins"; 10 | 11 | const auth = betterAuth({ 12 | database: new Database("./sqlite.db"), 13 | trustedOrigins: [], 14 | emailAndPassword: { 15 | enabled: true, 16 | }, 17 | plugins: [organization(), nextCookies()], 18 | user: { 19 | additionalFields: {}, 20 | }, 21 | }); 22 | const authClient = createAuthClient({ 23 | baseURL: "http://localhost:3000", 24 | plugins: [inferAdditionalFields<typeof auth>(), organizationClient()], 25 | }); 26 | 27 | authClient.useActiveOrganization(); 28 | authClient.useSession(); 29 | ``` -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "react-jsx", 17 | "incremental": true, 18 | "paths": { 19 | "@/.source": ["./.source/index.ts"], 20 | "@/*": ["./*"] 21 | }, 22 | "plugins": [ 23 | { 24 | "name": "next" 25 | } 26 | ] 27 | }, 28 | "include": [ 29 | "next-env.d.ts", 30 | "**/*.ts", 31 | "**/*.tsx", 32 | ".next/types/**/*.ts", 33 | ".next/dev/types/**/*.ts" 34 | ], 35 | "exclude": ["node_modules"] 36 | } 37 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/types/adapter.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { 2 | DBAdapter, 3 | DBTransactionAdapter, 4 | Where, 5 | DBAdapterSchemaCreation, 6 | DBAdapterInstance, 7 | } from "@better-auth/core/db/adapter"; 8 | 9 | export type { Where }; 10 | 11 | /** 12 | * Adapter Interface 13 | * 14 | * @deprecated Use `DBAdapter` from `@better-auth/core/db/adapter` instead. 15 | */ 16 | export type Adapter = DBAdapter; 17 | 18 | /** 19 | * @deprecated Use `DBTransactionAdapter` from `@better-auth/core/db/adapter` instead. 20 | */ 21 | export type TransactionAdapter = DBTransactionAdapter; 22 | 23 | /** 24 | * @deprecated Use `DBAdapterSchemaCreation` from `@better-auth/core/db/adapter` instead. 25 | */ 26 | export type AdapterSchemaCreation = DBAdapterSchemaCreation; 27 | 28 | /** 29 | * @deprecated Use `DBAdapterInstance` from `@better-auth/core/db/adapter` instead. 30 | */ 31 | export type AdapterInstance = DBAdapterInstance; 32 | ``` -------------------------------------------------------------------------------- /docs/components/docs/ui/button.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { cva } from "class-variance-authority"; 2 | 3 | export const buttonVariants = cva( 4 | "inline-flex items-center justify-center rounded-md p-2 text-sm font-medium transition-colors duration-100 disabled:pointer-events-none disabled:opacity-50", 5 | { 6 | variants: { 7 | color: { 8 | primary: 9 | "bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/80", 10 | outline: "border hover:bg-fd-accent hover:text-fd-accent-foreground", 11 | ghost: "hover:bg-fd-accent hover:text-fd-accent-foreground", 12 | secondary: 13 | "border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground", 14 | }, 15 | size: { 16 | sm: "gap-1 p-0.5 text-xs", 17 | icon: "p-1.5 [&_svg]:size-5", 18 | "icon-sm": "p-1.5 [&_svg]:size-4.5", 19 | }, 20 | }, 21 | }, 22 | ); 23 | ``` -------------------------------------------------------------------------------- /demo/expo-example/src/components/ui/text.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import * as Slot from "@rn-primitives/slot"; 2 | import { SlottableTextProps, TextRef } from "@rn-primitives/types"; 3 | import * as React from "react"; 4 | import { Text as RNText } from "react-native"; 5 | import { cn } from "@/lib/utils"; 6 | 7 | const TextClassContext = React.createContext<string | undefined>(undefined); 8 | 9 | const Text = React.forwardRef<TextRef, SlottableTextProps>( 10 | ({ className, asChild = false, ...props }, ref) => { 11 | const textClass = React.useContext(TextClassContext); 12 | const Component = asChild ? Slot.Text : RNText; 13 | return ( 14 | <Component 15 | className={cn( 16 | "text-base text-foreground web:select-text", 17 | textClass, 18 | className, 19 | )} 20 | ref={ref} 21 | {...props} 22 | /> 23 | ); 24 | }, 25 | ); 26 | Text.displayName = "Text"; 27 | 28 | export { Text, TextClassContext }; 29 | ``` -------------------------------------------------------------------------------- /docs/lib/metadata.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { Metadata } from "next/types"; 2 | 3 | export function createMetadata(override: Metadata): Metadata { 4 | return { 5 | ...override, 6 | openGraph: { 7 | title: override.title ?? undefined, 8 | description: override.description ?? undefined, 9 | url: "https://better-auth.com", 10 | images: "https://better-auth.com/og.png", 11 | siteName: "Better Auth", 12 | ...override.openGraph, 13 | }, 14 | twitter: { 15 | card: "summary_large_image", 16 | creator: "@beakcru", 17 | title: override.title ?? undefined, 18 | description: override.description ?? undefined, 19 | images: "https://better-auth.com/og.png", 20 | ...override.twitter, 21 | }, 22 | }; 23 | } 24 | 25 | export const baseUrl = 26 | process.env.NODE_ENV === "development" 27 | ? new URL("http://localhost:3000") 28 | : new URL(`https://${process.env.VERCEL_URL!}`); 29 | ``` -------------------------------------------------------------------------------- /demo/expo-example/src/app/_layout.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { Slot } from "expo-router"; 2 | import "../global.css"; 3 | import { SafeAreaProvider } from "react-native-safe-area-context"; 4 | import { ImageBackground, View } from "react-native"; 5 | import { StyleSheet } from "react-native"; 6 | 7 | export default function RootLayout() { 8 | return ( 9 | <SafeAreaProvider> 10 | <ImageBackground 11 | className="z-0 flex items-center justify-center" 12 | source={require("../../assets/bg-image.jpeg")} 13 | resizeMode="cover" 14 | style={{ 15 | ...(StyleSheet.absoluteFill as any), 16 | width: "100%", 17 | }} 18 | > 19 | <View 20 | style={{ 21 | position: "absolute", 22 | top: 0, 23 | left: 0, 24 | right: 0, 25 | bottom: 0, 26 | backgroundColor: "black", 27 | opacity: 0.2, 28 | }} 29 | /> 30 | <Slot /> 31 | </ImageBackground> 32 | </SafeAreaProvider> 33 | ); 34 | } 35 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/utils/hashing.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { keccak_256 } from "@noble/hashes/sha3.js"; 2 | import { utf8ToBytes } from "@noble/hashes/utils.js"; 3 | 4 | /** 5 | * TS implementation of ERC-55 ("Mixed-case checksum address encoding") using @noble/hashes 6 | * @param address - The address to convert to a checksum address 7 | * @returns The checksummed address 8 | */ 9 | export function toChecksumAddress(address: string) { 10 | address = address.toLowerCase().replace("0x", ""); 11 | // Hash the address (treat it as UTF-8) and return as a hex string 12 | const hash = [...keccak_256(utf8ToBytes(address))] 13 | .map((v) => v.toString(16).padStart(2, "0")) 14 | .join(""); 15 | let ret = "0x"; 16 | 17 | for (let i = 0; i < 40; i++) { 18 | if (parseInt(hash[i]!, 16) >= 8) { 19 | ret += address[i]!.toUpperCase(); 20 | } else { 21 | ret += address[i]!; 22 | } 23 | } 24 | 25 | return ret; 26 | } 27 | ``` -------------------------------------------------------------------------------- /docs/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; 4 | 5 | function Collapsible({ 6 | ...props 7 | }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) { 8 | return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />; 9 | } 10 | 11 | function CollapsibleTrigger({ 12 | ...props 13 | }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) { 14 | return ( 15 | <CollapsiblePrimitive.CollapsibleTrigger 16 | data-slot="collapsible-trigger" 17 | {...props} 18 | /> 19 | ); 20 | } 21 | 22 | function CollapsibleContent({ 23 | ...props 24 | }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) { 25 | return ( 26 | <CollapsiblePrimitive.CollapsibleContent 27 | data-slot="collapsible-content" 28 | {...props} 29 | /> 30 | ); 31 | } 32 | 33 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }; 34 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/jwt/adapter.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { BetterAuthOptions } from "@better-auth/core"; 2 | import type { DBAdapter } from "@better-auth/core/db/adapter"; 3 | import type { Jwk } from "./types"; 4 | 5 | export const getJwksAdapter = (adapter: DBAdapter<BetterAuthOptions>) => { 6 | return { 7 | getAllKeys: async () => { 8 | return await adapter.findMany<Jwk>({ 9 | model: "jwks", 10 | }); 11 | }, 12 | getLatestKey: async () => { 13 | const key = await adapter.findMany<Jwk>({ 14 | model: "jwks", 15 | sortBy: { 16 | field: "createdAt", 17 | direction: "desc", 18 | }, 19 | limit: 1, 20 | }); 21 | 22 | return key[0]; 23 | }, 24 | createJwk: async (webKey: Omit<Jwk, "id">) => { 25 | const jwk = await adapter.create<Omit<Jwk, "id">, Jwk>({ 26 | model: "jwks", 27 | data: { 28 | ...webKey, 29 | createdAt: new Date(), 30 | }, 31 | }); 32 | 33 | return jwk; 34 | }, 35 | }; 36 | }; 37 | ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/progress.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as ProgressPrimitive from "@radix-ui/react-progress"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Progress = ({ 9 | ref, 10 | className, 11 | value, 12 | ...props 13 | }: React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> & { 14 | ref: React.RefObject<React.ElementRef<typeof ProgressPrimitive.Root>>; 15 | }) => ( 16 | <ProgressPrimitive.Root 17 | ref={ref} 18 | className={cn( 19 | "relative h-2 w-full overflow-hidden rounded-full bg-primary/20", 20 | className, 21 | )} 22 | {...props} 23 | > 24 | <ProgressPrimitive.Indicator 25 | className="h-full w-full flex-1 bg-primary transition-all" 26 | style={{ transform: `translateX(-${100 - (value || 0)}%)` }} 27 | /> 28 | </ProgressPrimitive.Root> 29 | ); 30 | Progress.displayName = ProgressPrimitive.Root.displayName; 31 | 32 | export { Progress }; 33 | ``` -------------------------------------------------------------------------------- /demo/nextjs/lib/metadata.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { Metadata } from "next/types"; 2 | 3 | export function createMetadata(override: Metadata): Metadata { 4 | return { 5 | ...override, 6 | openGraph: { 7 | title: override.title ?? undefined, 8 | description: override.description ?? undefined, 9 | url: "https://demo.better-auth.com", 10 | images: "https://demo.better-auth.com/og.png", 11 | siteName: "Better Auth", 12 | ...override.openGraph, 13 | }, 14 | twitter: { 15 | card: "summary_large_image", 16 | creator: "@beakcru", 17 | title: override.title ?? undefined, 18 | description: override.description ?? undefined, 19 | images: "https://demo.better-auth.com/og.png", 20 | ...override.twitter, 21 | }, 22 | }; 23 | } 24 | 25 | export const baseUrl = 26 | process.env.NODE_ENV === "development" 27 | ? new URL("http://localhost:3000") 28 | : new URL(`https://${process.env.VERCEL_URL!}`); 29 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/access/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { LiteralString } from "../../types/helper"; 2 | import type { AuthorizeResponse, createAccessControl } from "./access"; 3 | 4 | export type SubArray<T extends unknown[] | readonly unknown[] | any[]> = 5 | T[number][]; 6 | 7 | export type Subset< 8 | K extends keyof R, 9 | R extends Record< 10 | string | LiteralString, 11 | readonly string[] | readonly LiteralString[] 12 | >, 13 | > = { 14 | [P in K]: SubArray<R[P]>; 15 | }; 16 | 17 | export type Statements = { 18 | readonly [resource: string]: readonly LiteralString[]; 19 | }; 20 | 21 | export type AccessControl<TStatements extends Statements = Statements> = 22 | ReturnType<typeof createAccessControl<TStatements>>; 23 | 24 | export type Role<TStatements extends Statements = Record<string, any>> = { 25 | authorize: (request: any, connector?: "OR" | "AND") => AuthorizeResponse; 26 | statements: TStatements; 27 | }; 28 | ``` -------------------------------------------------------------------------------- /packages/core/src/middleware/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { createEndpoint, createMiddleware } from "better-call"; 2 | import type { AuthContext } from "../types"; 3 | 4 | export const optionsMiddleware = createMiddleware(async () => { 5 | /** 6 | * This will be passed on the instance of 7 | * the context. Used to infer the type 8 | * here. 9 | */ 10 | return {} as AuthContext; 11 | }); 12 | 13 | export const createAuthMiddleware = createMiddleware.create({ 14 | use: [ 15 | optionsMiddleware, 16 | /** 17 | * Only use for post hooks 18 | */ 19 | createMiddleware(async () => { 20 | return {} as { 21 | returned?: unknown; 22 | responseHeaders?: Headers; 23 | }; 24 | }), 25 | ], 26 | }); 27 | 28 | export const createAuthEndpoint = createEndpoint.create({ 29 | use: [optionsMiddleware], 30 | }); 31 | 32 | export type AuthEndpoint = ReturnType<typeof createAuthEndpoint>; 33 | export type AuthMiddleware = ReturnType<typeof createAuthMiddleware>; 34 | ``` -------------------------------------------------------------------------------- /docs/scripts/sync-orama.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { sync, type OramaDocument } from "fumadocs-core/search/orama-cloud"; 2 | import * as fs from "node:fs/promises"; 3 | import { CloudManager } from "@oramacloud/client"; 4 | import * as process from "node:process"; 5 | import "dotenv/config"; 6 | 7 | const filePath = ".next/server/app/static.json.body"; 8 | 9 | async function main() { 10 | const apiKey = process.env.ORAMA_PRIVATE_API_KEY; 11 | 12 | if (!apiKey) { 13 | console.log("no api key for Orama found, skipping"); 14 | return; 15 | } 16 | 17 | const content = await fs.readFile(filePath); 18 | const records = JSON.parse(content.toString()) as OramaDocument[]; 19 | const manager = new CloudManager({ api_key: apiKey }); 20 | 21 | await sync(manager, { 22 | index: process.env.ORAMA_INDEX_ID!, 23 | documents: records, 24 | autoDeploy: true, 25 | }); 26 | 27 | console.log(`search updated: ${records.length} records`); 28 | } 29 | 30 | void main(); 31 | ``` -------------------------------------------------------------------------------- /docs/app/api/analytics/conversation/route.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | logConversationToAnalytics, 3 | type InkeepMessage, 4 | } from "@/lib/inkeep-analytics"; 5 | import { NextRequest, NextResponse } from "next/server"; 6 | 7 | export const runtime = "edge"; 8 | 9 | export async function POST(req: NextRequest) { 10 | try { 11 | const { messages }: { messages: InkeepMessage[] } = await req.json(); 12 | 13 | if (!messages || !Array.isArray(messages)) { 14 | return NextResponse.json( 15 | { error: "Messages array is required" }, 16 | { status: 400 }, 17 | ); 18 | } 19 | 20 | const result = await logConversationToAnalytics({ 21 | type: "openai", 22 | messages, 23 | properties: { 24 | source: "better-auth-docs", 25 | timestamp: new Date().toISOString(), 26 | }, 27 | }); 28 | 29 | return NextResponse.json(result); 30 | } catch (error) { 31 | return NextResponse.json( 32 | { error: "Failed to log conversation" }, 33 | { status: 500 }, 34 | ); 35 | } 36 | } 37 | ``` -------------------------------------------------------------------------------- /docs/components/ui/sonner.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import { useTheme } from "next-themes"; 4 | import { Toaster as Sonner, ToasterProps } from "sonner"; 5 | 6 | const Toaster = ({ ...props }: ToasterProps) => { 7 | const { theme = "system" } = useTheme(); 8 | 9 | return ( 10 | <Sonner 11 | theme={theme as ToasterProps["theme"]} 12 | className="toaster group" 13 | toastOptions={{ 14 | classNames: { 15 | toast: 16 | "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg", 17 | description: "group-[.toast]:text-muted-foreground", 18 | actionButton: 19 | "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground font-medium", 20 | cancelButton: 21 | "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground font-medium", 22 | }, 23 | }} 24 | {...props} 25 | /> 26 | ); 27 | }; 28 | 29 | export { Toaster }; 30 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/api-key/routes/delete-all-expired-api-keys.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { createAuthEndpoint } from "@better-auth/core/middleware"; 2 | import type { AuthContext } from "@better-auth/core"; 3 | 4 | export function deleteAllExpiredApiKeysEndpoint({ 5 | deleteAllExpiredApiKeys, 6 | }: { 7 | deleteAllExpiredApiKeys( 8 | ctx: AuthContext, 9 | byPassLastCheckTime?: boolean, 10 | ): Promise<void>; 11 | }) { 12 | return createAuthEndpoint( 13 | "/api-key/delete-all-expired-api-keys", 14 | { 15 | method: "POST", 16 | metadata: { 17 | SERVER_ONLY: true, 18 | client: false, 19 | }, 20 | }, 21 | async (ctx) => { 22 | try { 23 | await deleteAllExpiredApiKeys(ctx.context, true); 24 | } catch (error) { 25 | ctx.context.logger.error( 26 | "[API KEY PLUGIN] Failed to delete expired API keys:", 27 | error, 28 | ); 29 | return ctx.json({ 30 | success: false, 31 | error: error, 32 | }); 33 | } 34 | 35 | return ctx.json({ success: true, error: null }); 36 | }, 37 | ); 38 | } 39 | ``` -------------------------------------------------------------------------------- /docs/app/api/analytics/feedback/route.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { submitFeedbackToAnalytics } from "@/lib/inkeep-analytics"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export const runtime = "edge"; 5 | 6 | export async function POST(req: NextRequest) { 7 | try { 8 | const { messageId, type, reasons } = await req.json(); 9 | 10 | if (!messageId || !type) { 11 | return NextResponse.json( 12 | { error: "messageId and type are required" }, 13 | { status: 400 }, 14 | ); 15 | } 16 | 17 | if (type !== "positive" && type !== "negative") { 18 | return NextResponse.json( 19 | { error: "type must be 'positive' or 'negative'" }, 20 | { status: 400 }, 21 | ); 22 | } 23 | 24 | const result = await submitFeedbackToAnalytics({ 25 | messageId, 26 | type, 27 | reasons, 28 | }); 29 | 30 | return NextResponse.json(result); 31 | } catch (error) { 32 | return NextResponse.json( 33 | { error: "Failed to submit feedback" }, 34 | { status: 500 }, 35 | ); 36 | } 37 | } 38 | ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/sonner.tsx: -------------------------------------------------------------------------------- ```typescript 1 | "use client"; 2 | 3 | import { useTheme } from "next-themes"; 4 | import { Toaster as Sonner } from "sonner"; 5 | 6 | type ToasterProps = React.ComponentProps<typeof Sonner>; 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = "system" } = useTheme(); 10 | 11 | return ( 12 | <Sonner 13 | theme={theme as ToasterProps["theme"]} 14 | className="toaster group" 15 | toastOptions={{ 16 | classNames: { 17 | toast: 18 | "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg", 19 | description: "group-[.toast]:text-muted-foreground", 20 | actionButton: 21 | "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground", 22 | cancelButton: 23 | "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground", 24 | }, 25 | }} 26 | {...props} 27 | /> 28 | ); 29 | }; 30 | 31 | export { Toaster }; 32 | ``` -------------------------------------------------------------------------------- /packages/core/src/utils/error-codes.ts: -------------------------------------------------------------------------------- ```typescript 1 | type UpperLetter = 2 | | "A" 3 | | "B" 4 | | "C" 5 | | "D" 6 | | "E" 7 | | "F" 8 | | "G" 9 | | "H" 10 | | "I" 11 | | "J" 12 | | "K" 13 | | "L" 14 | | "M" 15 | | "N" 16 | | "O" 17 | | "P" 18 | | "Q" 19 | | "R" 20 | | "S" 21 | | "T" 22 | | "U" 23 | | "V" 24 | | "W" 25 | | "X" 26 | | "Y" 27 | | "Z"; 28 | type SpecialCharacter = "_"; 29 | 30 | type IsValidUpperSnakeCase<S extends string> = S extends `${infer F}${infer R}` 31 | ? F extends UpperLetter | SpecialCharacter 32 | ? IsValidUpperSnakeCase<R> 33 | : false 34 | : true; 35 | 36 | type InvalidKeyError<K extends string> = 37 | `Invalid error code key: "${K}" - must only contain uppercase letters (A-Z) and underscores (_)`; 38 | 39 | type ValidateErrorCodes<T> = { 40 | [K in keyof T]: K extends string 41 | ? IsValidUpperSnakeCase<K> extends false 42 | ? InvalidKeyError<K> 43 | : T[K] 44 | : T[K]; 45 | }; 46 | 47 | export function defineErrorCodes<const T extends Record<string, string>>( 48 | codes: ValidateErrorCodes<T>, 49 | ): T { 50 | return codes as T; 51 | } 52 | ``` -------------------------------------------------------------------------------- /packages/stripe/src/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { StripeOptions } from "./types"; 2 | 3 | export async function getPlans(options: StripeOptions) { 4 | return typeof options?.subscription?.plans === "function" 5 | ? await options.subscription?.plans() 6 | : options.subscription?.plans; 7 | } 8 | 9 | export async function getPlanByPriceInfo( 10 | options: StripeOptions, 11 | priceId: string, 12 | priceLookupKey: string | null, 13 | ) { 14 | return await getPlans(options).then((res) => 15 | res?.find( 16 | (plan) => 17 | plan.priceId === priceId || 18 | plan.annualDiscountPriceId === priceId || 19 | (priceLookupKey && 20 | (plan.lookupKey === priceLookupKey || 21 | plan.annualDiscountLookupKey === priceLookupKey)), 22 | ), 23 | ); 24 | } 25 | 26 | export async function getPlanByName(options: StripeOptions, name: string) { 27 | return await getPlans(options).then((res) => 28 | res?.find((plan) => plan.name.toLowerCase() === name.toLowerCase()), 29 | ); 30 | } 31 | ``` -------------------------------------------------------------------------------- /packages/cli/CHANGELOG.md: -------------------------------------------------------------------------------- ```markdown 1 | # @better-auth/cli 2 | 3 | ## 1.3.4 4 | 5 | ### Patch Changes 6 | 7 | - 2bd2fa9: Added support for listing organization members with pagination, sorting, and filtering, and improved client inference for additional organization fields. Also fixed date handling in rate limits and tokens, improved Notion OAuth user extraction, and ensured session is always set in context. 8 | 9 | Organization 10 | 11 | - Added listMembers API with pagination, sorting, and filtering. 12 | - Added membersLimit param to getFullOrganization. 13 | - Improved client inference for additional fields in organization schemas. 14 | - Bug Fixes 15 | - Fixed date handling by casting DB values to Date objects before using date methods. 16 | - Fixed Notion OAuth to extract user info correctly. 17 | - Ensured session is set in context when reading from cookie cach 18 | 19 | - Updated dependencies [2bd2fa9] 20 | - [email protected] 21 | ``` -------------------------------------------------------------------------------- /packages/expo/CHANGELOG.md: -------------------------------------------------------------------------------- ```markdown 1 | # @better-auth/expo 2 | 3 | ## 1.3.4 4 | 5 | ### Patch Changes 6 | 7 | - 2bd2fa9: Added support for listing organization members with pagination, sorting, and filtering, and improved client inference for additional organization fields. Also fixed date handling in rate limits and tokens, improved Notion OAuth user extraction, and ensured session is always set in context. 8 | 9 | Organization 10 | 11 | - Added listMembers API with pagination, sorting, and filtering. 12 | - Added membersLimit param to getFullOrganization. 13 | - Improved client inference for additional fields in organization schemas. 14 | - Bug Fixes 15 | - Fixed date handling by casting DB values to Date objects before using date methods. 16 | - Fixed Notion OAuth to extract user info correctly. 17 | - Ensured session is set in context when reading from cookie cach 18 | 19 | - Updated dependencies [2bd2fa9] 20 | - [email protected] 21 | ``` -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- ``` 1 | { 2 | $schema: 'https://docs.renovatebot.com/renovate-schema.json', 3 | extends: [ 4 | 'config:recommended', 5 | 'schedule:weekly', 6 | 'group:allNonMajor', 7 | ':disablePeerDependencies', 8 | 'customManagers:biomeVersions', 9 | 'helpers:pinGitHubActionDigestsToSemver', 10 | ], 11 | labels: [ 12 | 'dependencies', 13 | ], 14 | rangeStrategy: 'bump', 15 | postUpdateOptions: [ 16 | 'pnpmDedupe', 17 | ], 18 | ignorePaths: [ 19 | '**/node_modules/**', 20 | ], 21 | packageRules: [ 22 | { 23 | groupName: 'github-actions', 24 | matchManagers: [ 25 | 'github-actions', 26 | ], 27 | }, 28 | { 29 | groupName: 'better-auth dependencies', 30 | matchManagers: [ 31 | 'npm', 32 | ], 33 | matchFileNames: [ 34 | 'packages/better-auth/**', 35 | ], 36 | }, 37 | ], 38 | ignoreDeps: [ 39 | '@biomejs/biome', 40 | '@types/node', 41 | 'drizzle-orm', 42 | 'node', 43 | 'npm', 44 | 'pnpm', 45 | ], 46 | } 47 | ``` -------------------------------------------------------------------------------- /packages/telemetry/src/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | export interface DetectionInfo { 2 | name: string; 3 | version: string | null; 4 | } 5 | 6 | export interface SystemInfo { 7 | // Software information 8 | systemPlatform: string; 9 | systemRelease: string; 10 | systemArchitecture: string; 11 | 12 | // Machine information 13 | cpuCount: number; 14 | cpuModel: string | null; 15 | cpuSpeed: number | null; 16 | memory: number; 17 | 18 | // Environment information 19 | isDocker: boolean; 20 | isTTY: boolean; 21 | isWSL: boolean; 22 | isCI: boolean; 23 | } 24 | 25 | export interface AuthConfigInfo { 26 | options: any; 27 | plugins: string[]; 28 | } 29 | 30 | export interface ProjectInfo { 31 | isGit: boolean; 32 | packageManager: DetectionInfo | null; 33 | } 34 | 35 | export interface TelemetryEvent { 36 | type: string; 37 | anonymousId?: string; 38 | payload: Record<string, any>; 39 | } 40 | 41 | export interface TelemetryContext { 42 | customTrack?: (event: TelemetryEvent) => Promise<void>; 43 | database?: string; 44 | adapter?: string; 45 | skipTestCheck?: boolean; 46 | } 47 | ``` -------------------------------------------------------------------------------- /demo/nextjs/app/pricing/page.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { Pricing } from "@/components/blocks/pricing"; 2 | 3 | const demoPlans = [ 4 | { 5 | name: "Plus", 6 | price: "20", 7 | yearlyPrice: "16", 8 | period: "per month", 9 | features: [ 10 | "Up to 10 projects", 11 | "Basic analytics", 12 | "48-hour support response time", 13 | "Limited API access", 14 | ], 15 | description: "Perfect for individuals and small projects", 16 | buttonText: "Start Free Trial", 17 | href: "/sign-up", 18 | isPopular: false, 19 | }, 20 | { 21 | name: "Pro", 22 | price: "50", 23 | yearlyPrice: "40", 24 | period: "per month", 25 | features: [ 26 | "Unlimited projects", 27 | "Advanced analytics", 28 | "24-hour support response time", 29 | "Full API access", 30 | "Priority support", 31 | ], 32 | description: "Ideal for growing teams and businesses", 33 | buttonText: "Get Started", 34 | href: "/sign-up", 35 | isPopular: true, 36 | }, 37 | ]; 38 | 39 | export default function Page() { 40 | return <Pricing plans={demoPlans} />; 41 | } 42 | ``` -------------------------------------------------------------------------------- /packages/cli/src/utils/install-dependencies.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { exec } from "child_process"; 2 | 3 | export function installDependencies({ 4 | dependencies, 5 | packageManager, 6 | cwd, 7 | }: { 8 | dependencies: string[]; 9 | packageManager: "npm" | "pnpm" | "bun" | "yarn"; 10 | cwd: string; 11 | }): Promise<boolean> { 12 | let installCommand: string; 13 | switch (packageManager) { 14 | case "npm": 15 | installCommand = "npm install --force"; 16 | break; 17 | case "pnpm": 18 | installCommand = "pnpm install"; 19 | break; 20 | case "bun": 21 | installCommand = "bun install"; 22 | break; 23 | case "yarn": 24 | installCommand = "yarn install"; 25 | break; 26 | default: 27 | throw new Error("Invalid package manager"); 28 | } 29 | const command = `${installCommand} ${dependencies.join(" ")}`; 30 | 31 | return new Promise((resolve, reject) => { 32 | exec(command, { cwd }, (error, stdout, stderr) => { 33 | if (error) { 34 | reject(new Error(stderr)); 35 | return; 36 | } 37 | resolve(true); 38 | }); 39 | }); 40 | } 41 | ``` -------------------------------------------------------------------------------- /docs/components/mdx/add-to-cursor.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import Link from "next/link"; 2 | 3 | export const AddToCursor = () => { 4 | return ( 5 | <div className="w-max"> 6 | <Link 7 | href="cursor://anysphere.cursor-deeplink/mcp/install?name=Better%20Auth&config=eyJ1cmwiOiJodHRwczovL21jcC5jaG9ua2llLmFpL2JldHRlci1hdXRoL2JldHRlci1hdXRoLWJ1aWxkZXIvbWNwIn0%3D" 8 | className="dark:hidden" 9 | > 10 | <img 11 | src="https://cursor.com/deeplink/mcp-install-dark.svg" 12 | alt="Add Better Auth MCP to Cursor" 13 | height="32" 14 | /> 15 | </Link> 16 | 17 | <Link 18 | href="cursor://anysphere.cursor-deeplink/mcp/install?name=Better%20Auth&config=eyJ1cmwiOiJodHRwczovL21jcC5jaG9ua2llLmFpL2JldHRlci1hdXRoL2JldHRlci1hdXRoLWJ1aWxkZXIvbWNwIn0%3D" 19 | className="dark:block hidden" 20 | > 21 | <img 22 | src="https://cursor.com/deeplink/mcp-install-light.svg" 23 | alt="Add Better Auth MCP to Cursor" 24 | height="32" 25 | /> 26 | </Link> 27 | </div> 28 | ); 29 | }; 30 | ``` -------------------------------------------------------------------------------- /packages/better-auth/src/adapters/memory-adapter/adapter.memory.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { getAuthTables } from "../../db"; 2 | import { testAdapter } from "../test-adapter"; 3 | import { memoryAdapter } from "./memory-adapter"; 4 | import { 5 | performanceTestSuite, 6 | normalTestSuite, 7 | transactionsTestSuite, 8 | authFlowTestSuite, 9 | numberIdTestSuite, 10 | } from "../tests"; 11 | let db: Record<string, any[]> = {}; 12 | 13 | const { execute } = await testAdapter({ 14 | adapter: () => { 15 | return memoryAdapter(db); 16 | }, 17 | runMigrations: (options) => { 18 | db = {}; 19 | const authTables = getAuthTables(options); 20 | const allModels = Object.keys(authTables); 21 | for (const model of allModels) { 22 | const modelName = authTables[model]?.modelName || model; 23 | db[modelName] = []; 24 | } 25 | }, 26 | tests: [ 27 | normalTestSuite(), 28 | transactionsTestSuite({ disableTests: { ALL: true } }), 29 | authFlowTestSuite(), 30 | numberIdTestSuite(), 31 | performanceTestSuite(), 32 | ], 33 | async onFinish() {}, 34 | }); 35 | 36 | execute(); 37 | ``` -------------------------------------------------------------------------------- /docs/app/api/analytics/event/route.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { logEventToAnalytics } from "@/lib/inkeep-analytics"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export const runtime = "edge"; 5 | 6 | export async function POST(req: NextRequest) { 7 | try { 8 | const { type, entityType, messageId, conversationId } = await req.json(); 9 | 10 | if (!type || !entityType) { 11 | return NextResponse.json( 12 | { error: "type and entityType are required" }, 13 | { status: 400 }, 14 | ); 15 | } 16 | 17 | if (entityType !== "message" && entityType !== "conversation") { 18 | return NextResponse.json( 19 | { error: "entityType must be 'message' or 'conversation'" }, 20 | { status: 400 }, 21 | ); 22 | } 23 | 24 | const result = await logEventToAnalytics({ 25 | type, 26 | entityType, 27 | messageId, 28 | conversationId, 29 | }); 30 | 31 | return NextResponse.json(result); 32 | } catch (error) { 33 | return NextResponse.json({ error: "Failed to log event" }, { status: 500 }); 34 | } 35 | } 36 | ```