This is page 2 of 49. Use http://codebase.md/better-auth/better-auth?lines=false&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 -------------------------------------------------------------------------------- /demo/nextjs/turbo.json: -------------------------------------------------------------------------------- ```json { "$schema": "https://turborepo.org/schema.json", "extends": ["//"], "tasks": { "build": { "env": [ "TURSO_DATABASE_URL", "TURSO_AUTH_TOKEN", "RESEND_API_KEY", "BETTER_AUTH_EMAIL", "BETTER_AUTH_SECRET", "BETTER_AUTH_URL", "GITHUB_CLIENT_SECRET", "GITHUB_CLIENT_ID", "GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET", "DISCORD_CLIENT_ID", "DISCORD_CLIENT_SECRET", "MICROSOFT_CLIENT_ID", "MICROSOFT_CLIENT_SECRET", "STRIPE_KEY", "STRIPE_WEBHOOK_SECRET" ] } } } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/utils/json.ts: -------------------------------------------------------------------------------- ```typescript import { logger } from "@better-auth/core/env"; export function safeJSONParse<T>(data: unknown): T | null { function reviver(_: string, value: any): any { if (typeof value === "string") { const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/; if (iso8601Regex.test(value)) { const date = new Date(value); if (!isNaN(date.getTime())) { return date; } } } return value; } try { if (typeof data !== "string") { return data as T; } return JSON.parse(data, reviver); } catch (e) { logger.error("Error parsing JSON", { error: e }); return null; } } ``` -------------------------------------------------------------------------------- /docs/app/blog/layout.tsx: -------------------------------------------------------------------------------- ```typescript import { Metadata } from "next"; export const metadata: Metadata = { title: "Blog - Better Auth", description: "Latest updates, articles, and insights about Better Auth", }; interface BlogLayoutProps { children: React.ReactNode; } export default function BlogLayout({ children }: BlogLayoutProps) { return ( <div className="relative flex min-h-screen flex-col" style={{ scrollbarWidth: "none", scrollbarColor: "transparent transparent", //@ts-expect-error "&::-webkit-scrollbar": { display: "none", }, }} > <main className="flex-1">{children}</main> </div> ); } ``` -------------------------------------------------------------------------------- /docs/app/community/_components/header.tsx: -------------------------------------------------------------------------------- ```typescript export default function CommunityHeader() { return ( <div className="h-full flex flex-col justify-center items-center text-white"> <div className="max-w-6xl mx-auto px-4 py-16"> <div className="text-center"> <h1 className="text-4xl tracking-tighter md:text-5xl mt-3 font-normal mb-6 text-stone-800 dark:text-white"> Open Source Community </h1> <p className="dark:text-gray-400 max-w-md mx-auto text-stone-800"> join <span className="italic font-bold">better-auth</span> community to get help, share ideas, and stay up to date. </p> </div> </div> </div> ); } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/cookies/check-cookies.ts: -------------------------------------------------------------------------------- ```typescript import { parseCookies } from "../cookies"; import type { AuthContext } from "@better-auth/core"; export const checkAuthCookie = async ( request: Request | Headers, auth: { $context: Promise<AuthContext>; }, ) => { const headers = request instanceof Headers ? request : request.headers; const cookies = headers.get("cookie"); if (!cookies) { return null; } const ctx = await auth.$context; const cookieName = ctx.authCookies.sessionToken.name; const parsedCookie = parseCookies(cookies); const sessionToken = parsedCookie.get(cookieName); if (sessionToken) { return sessionToken; } return null; }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/two-factor/schema.ts: -------------------------------------------------------------------------------- ```typescript import type { BetterAuthPluginDBSchema } from "@better-auth/core/db"; export const schema = { user: { fields: { twoFactorEnabled: { type: "boolean", required: false, defaultValue: false, input: false, }, }, }, twoFactor: { fields: { secret: { type: "string", required: true, returned: false, }, backupCodes: { type: "string", required: true, returned: false, }, userId: { type: "string", required: true, returned: false, references: { model: "user", field: "id", }, }, }, }, } satisfies BetterAuthPluginDBSchema; ``` -------------------------------------------------------------------------------- /docs/public/branding/better-auth-logo-dark.svg: -------------------------------------------------------------------------------- ``` <svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect width="500" height="500" fill="black"/> <rect x="69" y="121" width="86.9879" height="259" fill="white"/> <rect x="337.575" y="121" width="92.4247" height="259" fill="white"/> <rect x="427.282" y="121" width="83.4555" height="174.52" transform="rotate(90 427.282 121)" fill="white"/> <rect x="430" y="296.544" width="83.4555" height="177.238" transform="rotate(90 430 296.544)" fill="white"/> <rect x="252.762" y="204.455" width="92.0888" height="96.7741" transform="rotate(90 252.762 204.455)" fill="white"/> </svg> ``` -------------------------------------------------------------------------------- /docs/public/branding/better-auth-logo-light.svg: -------------------------------------------------------------------------------- ``` <svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect width="500" height="500" fill="white"/> <rect x="69" y="121" width="86.9879" height="259" fill="black"/> <rect x="337.575" y="121" width="92.4247" height="259" fill="black"/> <rect x="427.282" y="121" width="83.4555" height="174.52" transform="rotate(90 427.282 121)" fill="black"/> <rect x="430" y="296.544" width="83.4555" height="177.238" transform="rotate(90 430 296.544)" fill="black"/> <rect x="252.762" y="204.455" width="92.0888" height="96.7741" transform="rotate(90 252.762 204.455)" fill="black"/> </svg> ``` -------------------------------------------------------------------------------- /demo/nextjs/tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2017", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [ { "name": "next" } ], "paths": { "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules", "components/ui/**/*.tsx"] } ``` -------------------------------------------------------------------------------- /docs/app/llms.txt/[...slug]/route.ts: -------------------------------------------------------------------------------- ```typescript import { type NextRequest, NextResponse } from "next/server"; import { source } from "@/lib/source"; import { notFound } from "next/navigation"; import { getLLMText } from "@/app/docs/lib/get-llm-text"; export const revalidate = false; export async function GET( _req: NextRequest, { params }: { params: Promise<{ slug: string[] }> }, ) { const slug = (await params).slug; const page = source.getPage(slug); if (!page) notFound(); return new NextResponse(await getLLMText(page), { headers: { "Content-Type": "text/markdown", }, }); } export function generateStaticParams() { return source.generateParams(); } ``` -------------------------------------------------------------------------------- /docs/components/landing/section-svg.tsx: -------------------------------------------------------------------------------- ```typescript import { Plus } from "lucide-react"; const SectionSvg = ({ crossesOffset }: { crossesOffset: string }) => { return ( <> <Plus className={`hidden absolute -top-[0.3125rem] h-6 w-6 ${ crossesOffset && crossesOffset } pointer-events-none lg:block lg:left-[3.275rem] text-neutral-300 dark:text-neutral-600 translate-y-[.5px]`} /> <Plus className={`hidden absolute -top-[0.3125rem] h-6 w-6 right-[1.4625rem] ${ crossesOffset && crossesOffset } pointer-events-none lg:block lg:right-[2.7750rem] text-neutral-300 dark:text-neutral-600 translate-y-[.5px]`} /> </> ); }; export default SectionSvg; ``` -------------------------------------------------------------------------------- /docs/components/builder/code-tabs/tab-bar.tsx: -------------------------------------------------------------------------------- ```typescript import { CodeTab } from "./code-tabs"; interface File { id: string; name: string; content: string; } interface TabBarProps { files: File[]; activeFileId: string; onTabClick: (fileId: string) => void; onTabClose: (fileId: string) => void; } export function TabBar({ files, activeFileId, onTabClick, onTabClose, }: TabBarProps) { return ( <div className="flex bg-muted border-b border-border"> {files.map((file) => ( <CodeTab key={file.id} fileName={file.name} isActive={file.id === activeFileId} onClick={() => onTabClick(file.id)} onClose={() => onTabClose(file.id)} /> ))} </div> ); } ``` -------------------------------------------------------------------------------- /packages/cli/src/utils/get-tsconfig-info.ts: -------------------------------------------------------------------------------- ```typescript import path from "path"; import fs from "fs"; export function stripJsonComments(jsonString: string): string { return jsonString .replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m, ) .replace(/,(?=\s*[}\]])/g, ""); } export function getTsconfigInfo(cwd?: string, flatPath?: string) { let tsConfigPath: string; if (flatPath) { tsConfigPath = flatPath; } else { tsConfigPath = cwd ? path.join(cwd, "tsconfig.json") : path.join("tsconfig.json"); } try { const text = fs.readFileSync(tsConfigPath, "utf-8"); return JSON.parse(stripJsonComments(text)); } catch (error) { throw error; } } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/crypto/hash.ts: -------------------------------------------------------------------------------- ```typescript import { constantTimeEqual } from "./buffer"; import { createHash } from "@better-auth/utils/hash"; import { base64 } from "@better-auth/utils/base64"; export async function hashToBase64( data: string | ArrayBuffer, ): Promise<string> { const buffer = await createHash("SHA-256").digest(data); return base64.encode(buffer); } export async function compareHash( data: string | ArrayBuffer, hash: string, ): Promise<boolean> { const buffer = await createHash("SHA-256").digest( typeof data === "string" ? new TextEncoder().encode(data) : data, ); const hashBuffer = base64.decode(hash); return constantTimeEqual(buffer, hashBuffer); } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/admin/schema.ts: -------------------------------------------------------------------------------- ```typescript import { type BetterAuthPluginDBSchema } from "@better-auth/core/db"; export const schema = { user: { fields: { role: { type: "string", required: false, input: false, }, banned: { type: "boolean", defaultValue: false, required: false, input: false, }, banReason: { type: "string", required: false, input: false, }, banExpires: { type: "date", required: false, input: false, }, }, }, session: { fields: { impersonatedBy: { type: "string", required: false, }, }, }, } satisfies BetterAuthPluginDBSchema; export type AdminSchema = typeof schema; ``` -------------------------------------------------------------------------------- /demo/expo-example/src/components/ui/separator.tsx: -------------------------------------------------------------------------------- ```typescript import * as SeparatorPrimitive from "@rn-primitives/separator"; import * as React from "react"; import { cn } from "@/lib/utils"; const Separator = React.forwardRef< SeparatorPrimitive.RootRef, SeparatorPrimitive.RootProps >( ( { className, orientation = "horizontal", decorative = true, ...props }, ref, ) => ( <SeparatorPrimitive.Root ref={ref} decorative={decorative} orientation={orientation} className={cn( "shrink-0 bg-border", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", className, )} {...props} /> ), ); Separator.displayName = SeparatorPrimitive.Root.displayName; export { Separator }; ``` -------------------------------------------------------------------------------- /docs/components/ui/separator.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as React from "react"; import * as SeparatorPrimitive from "@radix-ui/react-separator"; import { cn } from "@/lib/utils"; function Separator({ className, orientation = "horizontal", decorative = true, ...props }: React.ComponentProps<typeof SeparatorPrimitive.Root>) { return ( <SeparatorPrimitive.Root data-slot="separator-root" decorative={decorative} orientation={orientation} className={cn( "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", className, )} {...props} /> ); } export { Separator }; ``` -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- ```json { "biome.enabled": true, "editor.defaultFormatter": "biomejs.biome", "[typescript]": { "editor.defaultFormatter": "biomejs.biome" }, "[jsonc]": { "editor.defaultFormatter": "biomejs.biome" }, "[svelte]": { "editor.defaultFormatter": "biomejs.biome" }, "[vue]": { "editor.defaultFormatter": "biomejs.biome" }, "[json]": { "editor.defaultFormatter": "biomejs.biome" }, "typescript.tsdk": "node_modules/typescript/lib", "[astro]": { "editor.defaultFormatter": "biomejs.biome" }, "[typescriptreact]": { "editor.defaultFormatter": "biomejs.biome" }, "[mdx]": { "editor.defaultFormatter": "unifiedjs.vscode-mdx" } } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/admin/access/statement.ts: -------------------------------------------------------------------------------- ```typescript import { createAccessControl } from "../../access"; export const defaultStatements = { user: [ "create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update", ], session: ["list", "revoke", "delete"], } as const; export const defaultAc = createAccessControl(defaultStatements); export const adminAc = defaultAc.newRole({ user: [ "create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update", ], session: ["list", "revoke", "delete"], }); export const userAc = defaultAc.newRole({ user: [], session: [], }); export const defaultRoles = { admin: adminAc, user: userAc, }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/captcha/constants.ts: -------------------------------------------------------------------------------- ```typescript import type { Provider } from "./types"; export const defaultEndpoints = [ "/sign-up/email", "/sign-in/email", "/forget-password", ]; export const Providers = { CLOUDFLARE_TURNSTILE: "cloudflare-turnstile", GOOGLE_RECAPTCHA: "google-recaptcha", HCAPTCHA: "hcaptcha", CAPTCHAFOX: "captchafox", } as const; export const siteVerifyMap: Record<Provider, string> = { [Providers.CLOUDFLARE_TURNSTILE]: "https://challenges.cloudflare.com/turnstile/v0/siteverify", [Providers.GOOGLE_RECAPTCHA]: "https://www.google.com/recaptcha/api/siteverify", [Providers.HCAPTCHA]: "https://api.hcaptcha.com/siteverify", [Providers.CAPTCHAFOX]: "https://api.captchafox.com/siteverify", }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/integrations/node.ts: -------------------------------------------------------------------------------- ```typescript import { toNodeHandler as toNode } from "better-call/node"; import type { Auth } from "../auth"; import type { IncomingHttpHeaders } from "http"; export const toNodeHandler = ( auth: | { handler: Auth["handler"]; } | Auth["handler"], ) => { return "handler" in auth ? toNode(auth.handler) : toNode(auth); }; export function fromNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers { const webHeaders = new Headers(); for (const [key, value] of Object.entries(nodeHeaders)) { if (value !== undefined) { if (Array.isArray(value)) { value.forEach((v) => webHeaders.append(key, v)); } else { webHeaders.set(key, value); } } } return webHeaders; } ``` -------------------------------------------------------------------------------- /packages/telemetry/src/project-id.ts: -------------------------------------------------------------------------------- ```typescript import { generateId } from "./utils/id"; import { hashToBase64 } from "./utils/hash"; import { getNameFromLocalPackageJson } from "./utils/package-json"; let projectIdCached: string | null = null; export async function getProjectId( baseUrl: string | undefined, ): Promise<string> { if (projectIdCached) return projectIdCached; const projectName = await getNameFromLocalPackageJson(); if (projectName) { projectIdCached = await hashToBase64( baseUrl ? baseUrl + projectName : projectName, ); return projectIdCached; } if (baseUrl) { projectIdCached = await hashToBase64(baseUrl); return projectIdCached; } projectIdCached = generateId(32); return projectIdCached; } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/test-utils/headers.ts: -------------------------------------------------------------------------------- ```typescript /** * converts set cookie containing headers to * cookie containing headers */ export function convertSetCookieToCookie(headers: Headers): Headers { const setCookieHeaders: string[] = []; headers.forEach((value, name) => { if (name.toLowerCase() === "set-cookie") { setCookieHeaders.push(value); } }); if (setCookieHeaders.length === 0) { return headers; } const existingCookies = headers.get("cookie") || ""; const cookies = existingCookies ? existingCookies.split("; ") : []; setCookieHeaders.forEach((setCookie) => { const cookiePair = setCookie.split(";")[0]!; cookies.push(cookiePair.trim()); }); headers.set("cookie", cookies.join("; ")); return headers; } ``` -------------------------------------------------------------------------------- /docs/components/ui/progress.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as React from "react"; import * as ProgressPrimitive from "@radix-ui/react-progress"; import { cn } from "@/lib/utils"; function Progress({ className, value, ...props }: React.ComponentProps<typeof ProgressPrimitive.Root>) { return ( <ProgressPrimitive.Root data-slot="progress" className={cn( "bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", className, )} {...props} > <ProgressPrimitive.Indicator data-slot="progress-indicator" className="bg-primary h-full w-full flex-1 transition-all" style={{ transform: `translateX(-${100 - (value || 0)}%)` }} /> </ProgressPrimitive.Root> ); } export { Progress }; ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/toaster.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { useToast } from "@/hooks/use-toast"; import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, } from "@/components/ui/toast"; export function Toaster() { const { toasts } = useToast(); return ( <ToastProvider> {toasts.map(function ({ id, title, description, action, ...props }) { return ( <Toast key={id} {...props}> <div className="grid gap-1"> {title && <ToastTitle>{title}</ToastTitle>} {description && ( <ToastDescription>{description}</ToastDescription> )} </div> {action} <ToastClose /> </Toast> ); })} <ToastViewport /> </ToastProvider> ); } ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/textarea.tsx: -------------------------------------------------------------------------------- ```typescript import * as React from "react"; import { cn } from "@/lib/utils"; export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} const Textarea = ({ ref, className, ...props }: TextareaProps & { ref: React.RefObject<HTMLTextAreaElement>; }) => { return ( <textarea className={cn( "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", className, )} ref={ref} {...props} /> ); }; Textarea.displayName = "Textarea"; export { Textarea }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/client/client-ssr.test.ts: -------------------------------------------------------------------------------- ```typescript // @vitest-environment node import { expect, it, vi } from "vitest"; import { createAuthClient as createVueClient } from "./vue"; it("should call '/api/auth' for vue client", async () => { const customFetchImpl = vi.fn(async (url: string | Request | URL) => { expect(url).toBe("/api/auth/get-session"); return new Response(); }); process.env.BETTER_AUTH_URL = "http://localhost:3000"; // use DisposableStack when Node.js 24 is the minimum requirement using _ = { [Symbol.dispose]() { process.env.BETTER_AUTH_URL = undefined; }, }; const client = createVueClient({ fetchOptions: { customFetchImpl, }, }); await client.getSession(); expect(customFetchImpl).toBeCalled(); }); ``` -------------------------------------------------------------------------------- /docs/components/fork-button.tsx: -------------------------------------------------------------------------------- ```typescript import Link from "next/link"; import { Button } from "./ui/button"; import { GitHubLogoIcon } from "@radix-ui/react-icons"; import { ExternalLink } from "lucide-react"; export function ForkButton({ url }: { url: string }) { return ( <div className="flex items-center gap-2 my-2"> <Link href={`https://codesandbox.io/p/github/${url}`} target="_blank"> <Button className="gap-2" variant="outline" size="sm"> <ExternalLink size={12} /> Open in Stackblitz </Button> </Link> <Link href={`https://github.com/${url}`} target="_blank"> <Button className="gap-2" variant="secondary" size="sm"> <GitHubLogoIcon /> View on GitHub </Button> </Link> </div> ); } ``` -------------------------------------------------------------------------------- /docs/components/mobile-search-icon.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { Search } from "lucide-react"; import { useSearchContext } from "fumadocs-ui/provider"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; interface MobileSearchIconProps { className?: string; } export function MobileSearchIcon({ className }: MobileSearchIconProps) { const { setOpenSearch } = useSearchContext(); const handleSearchClick = () => { setOpenSearch(true); }; return ( <Button variant="ghost" size="icon" aria-label="Search" onClick={handleSearchClick} className={cn( "flex ring-0 shrink-0 md:hidden size-9 hover:bg-transparent", className, )} > <Search className="size-4" /> </Button> ); } ``` -------------------------------------------------------------------------------- /packages/stripe/src/client.ts: -------------------------------------------------------------------------------- ```typescript import type { BetterAuthClientPlugin } from "better-auth"; import type { stripe } from "./index"; export const stripeClient = < O extends { subscription: boolean; }, >( options?: O, ) => { return { id: "stripe-client", $InferServerPlugin: {} as ReturnType< typeof stripe< O["subscription"] extends true ? { stripeClient: any; stripeWebhookSecret: string; subscription: { enabled: true; plans: []; }; } : { stripeClient: any; stripeWebhookSecret: string; } > >, pathMethods: { "/subscription/restore": "POST", "/subscription/billing-portal": "POST", }, } satisfies BetterAuthClientPlugin; }; ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/separator.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as React from "react"; import * as SeparatorPrimitive from "@radix-ui/react-separator"; import { cn } from "@/lib/utils"; const Separator = ({ ref, className, orientation = "horizontal", decorative = true, ...props }: React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> & { ref: React.RefObject<React.ElementRef<typeof SeparatorPrimitive.Root>>; }) => ( <SeparatorPrimitive.Root ref={ref} decorative={decorative} orientation={orientation} className={cn( "shrink-0 bg-border", orientation === "horizontal" ? "h-px w-full" : "h-full w-px", className, )} {...props} /> ); Separator.displayName = SeparatorPrimitive.Root.displayName; export { Separator }; ``` -------------------------------------------------------------------------------- /docs/components/ui/textarea.tsx: -------------------------------------------------------------------------------- ```typescript import * as React from "react"; import { cn } from "@/lib/utils"; function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { return ( <textarea data-slot="textarea" className={cn( "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", className, )} {...props} /> ); } export { Textarea }; ``` -------------------------------------------------------------------------------- /docs/app/changelogs/_components/grid-pattern.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { cn } from "@/lib/utils"; function GridPatterns() { return ( <div className={cn( "pointer-events-none", "fixed inset-y-0 left-0 z-0", "w-1/2 h-full", "overflow-hidden", )} aria-hidden="true" > <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]" /> <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" /> </div> ); } export { GridPatterns }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/types/plugins.ts: -------------------------------------------------------------------------------- ```typescript import type { UnionToIntersection } from "./helper"; import type { BetterAuthPluginDBSchema } from "@better-auth/core/db"; import type { BetterAuthOptions, BetterAuthPlugin } from "@better-auth/core"; export type InferOptionSchema<S extends BetterAuthPluginDBSchema> = S extends Record<string, { fields: infer Fields }> ? { [K in keyof S]?: { modelName?: string; fields?: { [P in keyof Fields]?: string; }; }; } : never; export type InferPluginErrorCodes<O extends BetterAuthOptions> = O["plugins"] extends Array<infer P> ? UnionToIntersection< P extends BetterAuthPlugin ? P["$ERROR_CODES"] extends Record<string, any> ? P["$ERROR_CODES"] : {} : {} > : {}; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/adapters/prisma-adapter/test/generate-auth-config.ts: -------------------------------------------------------------------------------- ```typescript import fs from "fs/promises"; import type { BetterAuthOptions } from "@better-auth/core"; import path from "path"; export const generateAuthConfigFile = async (_options: BetterAuthOptions) => { const options = { ..._options }; // biome-ignore lint/performance/noDelete: perf doesn't matter here. delete options.database; let code = `import { betterAuth } from "../../../auth"; import { prismaAdapter } from "../prisma-adapter"; import { PrismaClient } from "@prisma/client"; const db = new PrismaClient(); export const auth = betterAuth({ database: prismaAdapter(db, { provider: 'sqlite' }), ${JSON.stringify(options, null, 2).slice(1, -1)} })`; await fs.writeFile(path.join(import.meta.dirname, "auth.ts"), code); }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/api/routes/ok.ts: -------------------------------------------------------------------------------- ```typescript import { HIDE_METADATA } from "../../utils/hide-metadata"; import { createAuthEndpoint } from "@better-auth/core/middleware"; export const ok = createAuthEndpoint( "/ok", { method: "GET", metadata: { ...HIDE_METADATA, openapi: { description: "Check if the API is working", responses: { "200": { description: "API is working", content: { "application/json": { schema: { type: "object", properties: { ok: { type: "boolean", description: "Indicates if the API is working", }, }, required: ["ok"], }, }, }, }, }, }, }, }, async (ctx) => { return ctx.json({ ok: true, }); }, ); ``` -------------------------------------------------------------------------------- /e2e/smoke/test/fixtures/tsconfig-exact-optional-property-types/src/index.ts: -------------------------------------------------------------------------------- ```typescript import { betterAuth } from "better-auth"; import { organization } from "better-auth/plugins"; import { nextCookies } from "better-auth/next-js"; import Database from "better-sqlite3"; import { createAuthClient } from "better-auth/react"; import { inferAdditionalFields, organizationClient, } from "better-auth/client/plugins"; const auth = betterAuth({ database: new Database("./sqlite.db"), trustedOrigins: [], emailAndPassword: { enabled: true, }, plugins: [organization(), nextCookies()], user: { additionalFields: {}, }, }); const authClient = createAuthClient({ baseURL: "http://localhost:3000", plugins: [inferAdditionalFields<typeof auth>(), organizationClient()], }); authClient.useActiveOrganization(); authClient.useSession(); ``` -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "baseUrl": ".", "target": "ESNext", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "react-jsx", "incremental": true, "paths": { "@/.source": ["./.source/index.ts"], "@/*": ["./*"] }, "plugins": [ { "name": "next" } ] }, "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts" ], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/types/adapter.ts: -------------------------------------------------------------------------------- ```typescript import type { DBAdapter, DBTransactionAdapter, Where, DBAdapterSchemaCreation, DBAdapterInstance, } from "@better-auth/core/db/adapter"; export type { Where }; /** * Adapter Interface * * @deprecated Use `DBAdapter` from `@better-auth/core/db/adapter` instead. */ export type Adapter = DBAdapter; /** * @deprecated Use `DBTransactionAdapter` from `@better-auth/core/db/adapter` instead. */ export type TransactionAdapter = DBTransactionAdapter; /** * @deprecated Use `DBAdapterSchemaCreation` from `@better-auth/core/db/adapter` instead. */ export type AdapterSchemaCreation = DBAdapterSchemaCreation; /** * @deprecated Use `DBAdapterInstance` from `@better-auth/core/db/adapter` instead. */ export type AdapterInstance = DBAdapterInstance; ``` -------------------------------------------------------------------------------- /docs/components/docs/ui/button.tsx: -------------------------------------------------------------------------------- ```typescript import { cva } from "class-variance-authority"; export const buttonVariants = cva( "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", { variants: { color: { primary: "bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/80", outline: "border hover:bg-fd-accent hover:text-fd-accent-foreground", ghost: "hover:bg-fd-accent hover:text-fd-accent-foreground", secondary: "border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground", }, size: { sm: "gap-1 p-0.5 text-xs", icon: "p-1.5 [&_svg]:size-5", "icon-sm": "p-1.5 [&_svg]:size-4.5", }, }, }, ); ``` -------------------------------------------------------------------------------- /demo/expo-example/src/components/ui/text.tsx: -------------------------------------------------------------------------------- ```typescript import * as Slot from "@rn-primitives/slot"; import { SlottableTextProps, TextRef } from "@rn-primitives/types"; import * as React from "react"; import { Text as RNText } from "react-native"; import { cn } from "@/lib/utils"; const TextClassContext = React.createContext<string | undefined>(undefined); const Text = React.forwardRef<TextRef, SlottableTextProps>( ({ className, asChild = false, ...props }, ref) => { const textClass = React.useContext(TextClassContext); const Component = asChild ? Slot.Text : RNText; return ( <Component className={cn( "text-base text-foreground web:select-text", textClass, className, )} ref={ref} {...props} /> ); }, ); Text.displayName = "Text"; export { Text, TextClassContext }; ``` -------------------------------------------------------------------------------- /docs/lib/metadata.ts: -------------------------------------------------------------------------------- ```typescript import type { Metadata } from "next/types"; export function createMetadata(override: Metadata): Metadata { return { ...override, openGraph: { title: override.title ?? undefined, description: override.description ?? undefined, url: "https://better-auth.com", images: "https://better-auth.com/og.png", siteName: "Better Auth", ...override.openGraph, }, twitter: { card: "summary_large_image", creator: "@beakcru", title: override.title ?? undefined, description: override.description ?? undefined, images: "https://better-auth.com/og.png", ...override.twitter, }, }; } export const baseUrl = process.env.NODE_ENV === "development" ? new URL("http://localhost:3000") : new URL(`https://${process.env.VERCEL_URL!}`); ``` -------------------------------------------------------------------------------- /demo/expo-example/src/app/_layout.tsx: -------------------------------------------------------------------------------- ```typescript import { Slot } from "expo-router"; import "../global.css"; import { SafeAreaProvider } from "react-native-safe-area-context"; import { ImageBackground, View } from "react-native"; import { StyleSheet } from "react-native"; export default function RootLayout() { return ( <SafeAreaProvider> <ImageBackground className="z-0 flex items-center justify-center" source={require("../../assets/bg-image.jpeg")} resizeMode="cover" style={{ ...(StyleSheet.absoluteFill as any), width: "100%", }} > <View style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "black", opacity: 0.2, }} /> <Slot /> </ImageBackground> </SafeAreaProvider> ); } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/utils/hashing.ts: -------------------------------------------------------------------------------- ```typescript import { keccak_256 } from "@noble/hashes/sha3.js"; import { utf8ToBytes } from "@noble/hashes/utils.js"; /** * TS implementation of ERC-55 ("Mixed-case checksum address encoding") using @noble/hashes * @param address - The address to convert to a checksum address * @returns The checksummed address */ export function toChecksumAddress(address: string) { address = address.toLowerCase().replace("0x", ""); // Hash the address (treat it as UTF-8) and return as a hex string const hash = [...keccak_256(utf8ToBytes(address))] .map((v) => v.toString(16).padStart(2, "0")) .join(""); let ret = "0x"; for (let i = 0; i < 40; i++) { if (parseInt(hash[i]!, 16) >= 8) { ret += address[i]!.toUpperCase(); } else { ret += address[i]!; } } return ret; } ``` -------------------------------------------------------------------------------- /docs/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; function Collapsible({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) { return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />; } function CollapsibleTrigger({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) { return ( <CollapsiblePrimitive.CollapsibleTrigger data-slot="collapsible-trigger" {...props} /> ); } function CollapsibleContent({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) { return ( <CollapsiblePrimitive.CollapsibleContent data-slot="collapsible-content" {...props} /> ); } export { Collapsible, CollapsibleTrigger, CollapsibleContent }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/jwt/adapter.ts: -------------------------------------------------------------------------------- ```typescript import type { BetterAuthOptions } from "@better-auth/core"; import type { DBAdapter } from "@better-auth/core/db/adapter"; import type { Jwk } from "./types"; export const getJwksAdapter = (adapter: DBAdapter<BetterAuthOptions>) => { return { getAllKeys: async () => { return await adapter.findMany<Jwk>({ model: "jwks", }); }, getLatestKey: async () => { const key = await adapter.findMany<Jwk>({ model: "jwks", sortBy: { field: "createdAt", direction: "desc", }, limit: 1, }); return key[0]; }, createJwk: async (webKey: Omit<Jwk, "id">) => { const jwk = await adapter.create<Omit<Jwk, "id">, Jwk>({ model: "jwks", data: { ...webKey, createdAt: new Date(), }, }); return jwk; }, }; }; ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/progress.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as React from "react"; import * as ProgressPrimitive from "@radix-ui/react-progress"; import { cn } from "@/lib/utils"; const Progress = ({ ref, className, value, ...props }: React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> & { ref: React.RefObject<React.ElementRef<typeof ProgressPrimitive.Root>>; }) => ( <ProgressPrimitive.Root ref={ref} className={cn( "relative h-2 w-full overflow-hidden rounded-full bg-primary/20", className, )} {...props} > <ProgressPrimitive.Indicator className="h-full w-full flex-1 bg-primary transition-all" style={{ transform: `translateX(-${100 - (value || 0)}%)` }} /> </ProgressPrimitive.Root> ); Progress.displayName = ProgressPrimitive.Root.displayName; export { Progress }; ``` -------------------------------------------------------------------------------- /demo/nextjs/lib/metadata.ts: -------------------------------------------------------------------------------- ```typescript import type { Metadata } from "next/types"; export function createMetadata(override: Metadata): Metadata { return { ...override, openGraph: { title: override.title ?? undefined, description: override.description ?? undefined, url: "https://demo.better-auth.com", images: "https://demo.better-auth.com/og.png", siteName: "Better Auth", ...override.openGraph, }, twitter: { card: "summary_large_image", creator: "@beakcru", title: override.title ?? undefined, description: override.description ?? undefined, images: "https://demo.better-auth.com/og.png", ...override.twitter, }, }; } export const baseUrl = process.env.NODE_ENV === "development" ? new URL("http://localhost:3000") : new URL(`https://${process.env.VERCEL_URL!}`); ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/access/types.ts: -------------------------------------------------------------------------------- ```typescript import type { LiteralString } from "../../types/helper"; import type { AuthorizeResponse, createAccessControl } from "./access"; export type SubArray<T extends unknown[] | readonly unknown[] | any[]> = T[number][]; export type Subset< K extends keyof R, R extends Record< string | LiteralString, readonly string[] | readonly LiteralString[] >, > = { [P in K]: SubArray<R[P]>; }; export type Statements = { readonly [resource: string]: readonly LiteralString[]; }; export type AccessControl<TStatements extends Statements = Statements> = ReturnType<typeof createAccessControl<TStatements>>; export type Role<TStatements extends Statements = Record<string, any>> = { authorize: (request: any, connector?: "OR" | "AND") => AuthorizeResponse; statements: TStatements; }; ``` -------------------------------------------------------------------------------- /packages/core/src/middleware/index.ts: -------------------------------------------------------------------------------- ```typescript import { createEndpoint, createMiddleware } from "better-call"; import type { AuthContext } from "../types"; export const optionsMiddleware = createMiddleware(async () => { /** * This will be passed on the instance of * the context. Used to infer the type * here. */ return {} as AuthContext; }); export const createAuthMiddleware = createMiddleware.create({ use: [ optionsMiddleware, /** * Only use for post hooks */ createMiddleware(async () => { return {} as { returned?: unknown; responseHeaders?: Headers; }; }), ], }); export const createAuthEndpoint = createEndpoint.create({ use: [optionsMiddleware], }); export type AuthEndpoint = ReturnType<typeof createAuthEndpoint>; export type AuthMiddleware = ReturnType<typeof createAuthMiddleware>; ``` -------------------------------------------------------------------------------- /docs/scripts/sync-orama.ts: -------------------------------------------------------------------------------- ```typescript import { sync, type OramaDocument } from "fumadocs-core/search/orama-cloud"; import * as fs from "node:fs/promises"; import { CloudManager } from "@oramacloud/client"; import * as process from "node:process"; import "dotenv/config"; const filePath = ".next/server/app/static.json.body"; async function main() { const apiKey = process.env.ORAMA_PRIVATE_API_KEY; if (!apiKey) { console.log("no api key for Orama found, skipping"); return; } const content = await fs.readFile(filePath); const records = JSON.parse(content.toString()) as OramaDocument[]; const manager = new CloudManager({ api_key: apiKey }); await sync(manager, { index: process.env.ORAMA_INDEX_ID!, documents: records, autoDeploy: true, }); console.log(`search updated: ${records.length} records`); } void main(); ``` -------------------------------------------------------------------------------- /docs/app/api/analytics/conversation/route.ts: -------------------------------------------------------------------------------- ```typescript import { logConversationToAnalytics, type InkeepMessage, } from "@/lib/inkeep-analytics"; import { NextRequest, NextResponse } from "next/server"; export const runtime = "edge"; export async function POST(req: NextRequest) { try { const { messages }: { messages: InkeepMessage[] } = await req.json(); if (!messages || !Array.isArray(messages)) { return NextResponse.json( { error: "Messages array is required" }, { status: 400 }, ); } const result = await logConversationToAnalytics({ type: "openai", messages, properties: { source: "better-auth-docs", timestamp: new Date().toISOString(), }, }); return NextResponse.json(result); } catch (error) { return NextResponse.json( { error: "Failed to log conversation" }, { status: 500 }, ); } } ``` -------------------------------------------------------------------------------- /docs/components/ui/sonner.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { useTheme } from "next-themes"; import { Toaster as Sonner, ToasterProps } from "sonner"; const Toaster = ({ ...props }: ToasterProps) => { const { theme = "system" } = useTheme(); return ( <Sonner theme={theme as ToasterProps["theme"]} className="toaster group" toastOptions={{ classNames: { toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg", description: "group-[.toast]:text-muted-foreground", actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground font-medium", cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground font-medium", }, }} {...props} /> ); }; export { Toaster }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/api-key/routes/delete-all-expired-api-keys.ts: -------------------------------------------------------------------------------- ```typescript import { createAuthEndpoint } from "@better-auth/core/middleware"; import type { AuthContext } from "@better-auth/core"; export function deleteAllExpiredApiKeysEndpoint({ deleteAllExpiredApiKeys, }: { deleteAllExpiredApiKeys( ctx: AuthContext, byPassLastCheckTime?: boolean, ): Promise<void>; }) { return createAuthEndpoint( "/api-key/delete-all-expired-api-keys", { method: "POST", metadata: { SERVER_ONLY: true, client: false, }, }, async (ctx) => { try { await deleteAllExpiredApiKeys(ctx.context, true); } catch (error) { ctx.context.logger.error( "[API KEY PLUGIN] Failed to delete expired API keys:", error, ); return ctx.json({ success: false, error: error, }); } return ctx.json({ success: true, error: null }); }, ); } ``` -------------------------------------------------------------------------------- /docs/app/api/analytics/feedback/route.ts: -------------------------------------------------------------------------------- ```typescript import { submitFeedbackToAnalytics } from "@/lib/inkeep-analytics"; import { NextRequest, NextResponse } from "next/server"; export const runtime = "edge"; export async function POST(req: NextRequest) { try { const { messageId, type, reasons } = await req.json(); if (!messageId || !type) { return NextResponse.json( { error: "messageId and type are required" }, { status: 400 }, ); } if (type !== "positive" && type !== "negative") { return NextResponse.json( { error: "type must be 'positive' or 'negative'" }, { status: 400 }, ); } const result = await submitFeedbackToAnalytics({ messageId, type, reasons, }); return NextResponse.json(result); } catch (error) { return NextResponse.json( { error: "Failed to submit feedback" }, { status: 500 }, ); } } ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/sonner.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { useTheme } from "next-themes"; import { Toaster as Sonner } from "sonner"; type ToasterProps = React.ComponentProps<typeof Sonner>; const Toaster = ({ ...props }: ToasterProps) => { const { theme = "system" } = useTheme(); return ( <Sonner theme={theme as ToasterProps["theme"]} className="toaster group" toastOptions={{ classNames: { toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg", description: "group-[.toast]:text-muted-foreground", actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground", cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground", }, }} {...props} /> ); }; export { Toaster }; ``` -------------------------------------------------------------------------------- /packages/core/src/utils/error-codes.ts: -------------------------------------------------------------------------------- ```typescript type UpperLetter = | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"; type SpecialCharacter = "_"; type IsValidUpperSnakeCase<S extends string> = S extends `${infer F}${infer R}` ? F extends UpperLetter | SpecialCharacter ? IsValidUpperSnakeCase<R> : false : true; type InvalidKeyError<K extends string> = `Invalid error code key: "${K}" - must only contain uppercase letters (A-Z) and underscores (_)`; type ValidateErrorCodes<T> = { [K in keyof T]: K extends string ? IsValidUpperSnakeCase<K> extends false ? InvalidKeyError<K> : T[K] : T[K]; }; export function defineErrorCodes<const T extends Record<string, string>>( codes: ValidateErrorCodes<T>, ): T { return codes as T; } ``` -------------------------------------------------------------------------------- /packages/stripe/src/utils.ts: -------------------------------------------------------------------------------- ```typescript import type { StripeOptions } from "./types"; export async function getPlans(options: StripeOptions) { return typeof options?.subscription?.plans === "function" ? await options.subscription?.plans() : options.subscription?.plans; } export async function getPlanByPriceInfo( options: StripeOptions, priceId: string, priceLookupKey: string | null, ) { return await getPlans(options).then((res) => res?.find( (plan) => plan.priceId === priceId || plan.annualDiscountPriceId === priceId || (priceLookupKey && (plan.lookupKey === priceLookupKey || plan.annualDiscountLookupKey === priceLookupKey)), ), ); } export async function getPlanByName(options: StripeOptions, name: string) { return await getPlans(options).then((res) => res?.find((plan) => plan.name.toLowerCase() === name.toLowerCase()), ); } ``` -------------------------------------------------------------------------------- /packages/cli/CHANGELOG.md: -------------------------------------------------------------------------------- ```markdown # @better-auth/cli ## 1.3.4 ### Patch Changes - 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. Organization - Added listMembers API with pagination, sorting, and filtering. - Added membersLimit param to getFullOrganization. - Improved client inference for additional fields in organization schemas. - Bug Fixes - Fixed date handling by casting DB values to Date objects before using date methods. - Fixed Notion OAuth to extract user info correctly. - Ensured session is set in context when reading from cookie cach - Updated dependencies [2bd2fa9] - [email protected] ``` -------------------------------------------------------------------------------- /packages/expo/CHANGELOG.md: -------------------------------------------------------------------------------- ```markdown # @better-auth/expo ## 1.3.4 ### Patch Changes - 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. Organization - Added listMembers API with pagination, sorting, and filtering. - Added membersLimit param to getFullOrganization. - Improved client inference for additional fields in organization schemas. - Bug Fixes - Fixed date handling by casting DB values to Date objects before using date methods. - Fixed Notion OAuth to extract user info correctly. - Ensured session is set in context when reading from cookie cach - Updated dependencies [2bd2fa9] - [email protected] ``` -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- ``` { $schema: 'https://docs.renovatebot.com/renovate-schema.json', extends: [ 'config:recommended', 'schedule:weekly', 'group:allNonMajor', ':disablePeerDependencies', 'customManagers:biomeVersions', 'helpers:pinGitHubActionDigestsToSemver', ], labels: [ 'dependencies', ], rangeStrategy: 'bump', postUpdateOptions: [ 'pnpmDedupe', ], ignorePaths: [ '**/node_modules/**', ], packageRules: [ { groupName: 'github-actions', matchManagers: [ 'github-actions', ], }, { groupName: 'better-auth dependencies', matchManagers: [ 'npm', ], matchFileNames: [ 'packages/better-auth/**', ], }, ], ignoreDeps: [ '@biomejs/biome', '@types/node', 'drizzle-orm', 'node', 'npm', 'pnpm', ], } ``` -------------------------------------------------------------------------------- /packages/telemetry/src/types.ts: -------------------------------------------------------------------------------- ```typescript export interface DetectionInfo { name: string; version: string | null; } export interface SystemInfo { // Software information systemPlatform: string; systemRelease: string; systemArchitecture: string; // Machine information cpuCount: number; cpuModel: string | null; cpuSpeed: number | null; memory: number; // Environment information isDocker: boolean; isTTY: boolean; isWSL: boolean; isCI: boolean; } export interface AuthConfigInfo { options: any; plugins: string[]; } export interface ProjectInfo { isGit: boolean; packageManager: DetectionInfo | null; } export interface TelemetryEvent { type: string; anonymousId?: string; payload: Record<string, any>; } export interface TelemetryContext { customTrack?: (event: TelemetryEvent) => Promise<void>; database?: string; adapter?: string; skipTestCheck?: boolean; } ``` -------------------------------------------------------------------------------- /demo/nextjs/app/pricing/page.tsx: -------------------------------------------------------------------------------- ```typescript import { Pricing } from "@/components/blocks/pricing"; const demoPlans = [ { name: "Plus", price: "20", yearlyPrice: "16", period: "per month", features: [ "Up to 10 projects", "Basic analytics", "48-hour support response time", "Limited API access", ], description: "Perfect for individuals and small projects", buttonText: "Start Free Trial", href: "/sign-up", isPopular: false, }, { name: "Pro", price: "50", yearlyPrice: "40", period: "per month", features: [ "Unlimited projects", "Advanced analytics", "24-hour support response time", "Full API access", "Priority support", ], description: "Ideal for growing teams and businesses", buttonText: "Get Started", href: "/sign-up", isPopular: true, }, ]; export default function Page() { return <Pricing plans={demoPlans} />; } ``` -------------------------------------------------------------------------------- /packages/cli/src/utils/install-dependencies.ts: -------------------------------------------------------------------------------- ```typescript import { exec } from "child_process"; export function installDependencies({ dependencies, packageManager, cwd, }: { dependencies: string[]; packageManager: "npm" | "pnpm" | "bun" | "yarn"; cwd: string; }): Promise<boolean> { let installCommand: string; switch (packageManager) { case "npm": installCommand = "npm install --force"; break; case "pnpm": installCommand = "pnpm install"; break; case "bun": installCommand = "bun install"; break; case "yarn": installCommand = "yarn install"; break; default: throw new Error("Invalid package manager"); } const command = `${installCommand} ${dependencies.join(" ")}`; return new Promise((resolve, reject) => { exec(command, { cwd }, (error, stdout, stderr) => { if (error) { reject(new Error(stderr)); return; } resolve(true); }); }); } ``` -------------------------------------------------------------------------------- /docs/components/mdx/add-to-cursor.tsx: -------------------------------------------------------------------------------- ```typescript import Link from "next/link"; export const AddToCursor = () => { return ( <div className="w-max"> <Link href="cursor://anysphere.cursor-deeplink/mcp/install?name=Better%20Auth&config=eyJ1cmwiOiJodHRwczovL21jcC5jaG9ua2llLmFpL2JldHRlci1hdXRoL2JldHRlci1hdXRoLWJ1aWxkZXIvbWNwIn0%3D" className="dark:hidden" > <img src="https://cursor.com/deeplink/mcp-install-dark.svg" alt="Add Better Auth MCP to Cursor" height="32" /> </Link> <Link href="cursor://anysphere.cursor-deeplink/mcp/install?name=Better%20Auth&config=eyJ1cmwiOiJodHRwczovL21jcC5jaG9ua2llLmFpL2JldHRlci1hdXRoL2JldHRlci1hdXRoLWJ1aWxkZXIvbWNwIn0%3D" className="dark:block hidden" > <img src="https://cursor.com/deeplink/mcp-install-light.svg" alt="Add Better Auth MCP to Cursor" height="32" /> </Link> </div> ); }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/adapters/memory-adapter/adapter.memory.test.ts: -------------------------------------------------------------------------------- ```typescript import { getAuthTables } from "../../db"; import { testAdapter } from "../test-adapter"; import { memoryAdapter } from "./memory-adapter"; import { performanceTestSuite, normalTestSuite, transactionsTestSuite, authFlowTestSuite, numberIdTestSuite, } from "../tests"; let db: Record<string, any[]> = {}; const { execute } = await testAdapter({ adapter: () => { return memoryAdapter(db); }, runMigrations: (options) => { db = {}; const authTables = getAuthTables(options); const allModels = Object.keys(authTables); for (const model of allModels) { const modelName = authTables[model]?.modelName || model; db[modelName] = []; } }, tests: [ normalTestSuite(), transactionsTestSuite({ disableTests: { ALL: true } }), authFlowTestSuite(), numberIdTestSuite(), performanceTestSuite(), ], async onFinish() {}, }); execute(); ``` -------------------------------------------------------------------------------- /docs/app/api/analytics/event/route.ts: -------------------------------------------------------------------------------- ```typescript import { logEventToAnalytics } from "@/lib/inkeep-analytics"; import { NextRequest, NextResponse } from "next/server"; export const runtime = "edge"; export async function POST(req: NextRequest) { try { const { type, entityType, messageId, conversationId } = await req.json(); if (!type || !entityType) { return NextResponse.json( { error: "type and entityType are required" }, { status: 400 }, ); } if (entityType !== "message" && entityType !== "conversation") { return NextResponse.json( { error: "entityType must be 'message' or 'conversation'" }, { status: 400 }, ); } const result = await logEventToAnalytics({ type, entityType, messageId, conversationId, }); return NextResponse.json(result); } catch (error) { return NextResponse.json({ error: "Failed to log event" }, { status: 500 }); } } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/siwe/types.ts: -------------------------------------------------------------------------------- ```typescript /** * SIWE Plugin Type Definitions */ export interface WalletAddress { id: string; userId: string; address: string; chainId: number; isPrimary: boolean; createdAt: Date; } interface CacaoHeader { t: "caip122"; } // Signed Cacao (CAIP-74) interface CacaoPayload { domain: string; aud: string; nonce: string; iss: string; version?: string; iat?: string; nbf?: string; exp?: string; statement?: string; requestId?: string; resources?: string[]; type?: string; } interface Cacao { h: CacaoHeader; p: CacaoPayload; s: { t: "eip191" | "eip1271"; s: string; m?: string; }; } export interface SIWEVerifyMessageArgs { message: string; signature: string; address: string; chainId: number; cacao?: Cacao; } export interface ENSLookupArgs { walletAddress: string; } export interface ENSLookupResult { name: string; avatar: string; } ``` -------------------------------------------------------------------------------- /docs/components/landing/gradient-bg.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import React from "react"; import { cn } from "@/lib/utils"; export function GradientBG({ children, className, ...props }: React.PropsWithChildren< { className?: string; } & React.HTMLAttributes<HTMLElement> >) { return ( <div className={cn( "relative flex content-center transition duration-500 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-full", )} {...props} > <div className={cn("w-auto z-10 px-4 py-2 rounded-none", className)}> {children} </div> <div className={cn( "flex-none inset-0 overflow-hidden absolute z-0 rounded-none bg-gradient-to-tl dark:from-amber-100/30 dark:via-zinc-900 dark:to-black blur-md opacity-50", )} /> <div className="bg-zinc-100 dark:bg-zinc-950 absolute z-1 flex-none inset-[2px] " /> </div> ); } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/captcha/types.ts: -------------------------------------------------------------------------------- ```typescript import type { Providers } from "./constants"; export type Provider = (typeof Providers)[keyof typeof Providers]; export interface BaseCaptchaOptions { secretKey: string; endpoints?: string[]; siteVerifyURLOverride?: string; } export interface GoogleRecaptchaOptions extends BaseCaptchaOptions { provider: typeof Providers.GOOGLE_RECAPTCHA; minScore?: number; } export interface CloudflareTurnstileOptions extends BaseCaptchaOptions { provider: typeof Providers.CLOUDFLARE_TURNSTILE; } export interface HCaptchaOptions extends BaseCaptchaOptions { provider: typeof Providers.HCAPTCHA; siteKey?: string; } export interface CaptchaFoxOptions extends BaseCaptchaOptions { provider: typeof Providers.CAPTCHAFOX; siteKey?: string; } export type CaptchaOptions = | GoogleRecaptchaOptions | CloudflareTurnstileOptions | HCaptchaOptions | CaptchaFoxOptions; ``` -------------------------------------------------------------------------------- /docs/app/v1/_components/v1-text.tsx: -------------------------------------------------------------------------------- ```typescript export const ShipText = () => { const voxels = [ // V [0, 0], [0, 1], [0, 2], [1, 3], [2, 2], [2, 1], [2, 0], // 1 [4, 0], [4, 1], [4, 2], [4, 3], // . [6, 3], // 0 [8, 0], [8, 1], [8, 2], [8, 3], [9, 0], [9, 3], [10, 0], [10, 1], [10, 2], [10, 3], ]; return ( <div className="flex justify-center items-center mb-0 h-[80%]"> <div className="grid grid-cols-11 gap-2"> {Array.from({ length: 44 }).map((_, index) => { const row = Math.floor(index / 11); const col = index % 11; const isActive = voxels.some(([x, y]) => x === col && y === row); return ( <div key={index} className={`w-8 h-8 ${ isActive ? "bg-gradient-to-tr from-stone-100 via-white/90 to-zinc-900" : "bg-transparent" }`} /> ); })} </div> </div> ); }; ``` -------------------------------------------------------------------------------- /packages/telemetry/src/detectors/detect-runtime.ts: -------------------------------------------------------------------------------- ```typescript import { getEnvVar, isTest } from "@better-auth/core/env"; import { isCI } from "./detect-system-info"; export function detectRuntime() { // @ts-expect-error: TS doesn't know about Deno global if (typeof Deno !== "undefined") { // @ts-expect-error: TS doesn't know about Deno global const denoVersion = Deno?.version?.deno ?? null; return { name: "deno", version: denoVersion }; } if (typeof Bun !== "undefined") { const bunVersion = Bun?.version ?? null; return { name: "bun", version: bunVersion }; } if (typeof process !== "undefined" && process?.versions?.node) { return { name: "node", version: process.versions.node ?? null }; } return { name: "edge", version: null }; } export function detectEnvironment() { return getEnvVar("NODE_ENV") === "production" ? "production" : isCI() ? "ci" : isTest() ? "test" : "development"; } ``` -------------------------------------------------------------------------------- /docs/components/endpoint.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { cn } from "@/lib/utils"; import { useState } from "react"; function Method({ method }: { method: "POST" | "GET" | "DELETE" | "PUT" }) { return ( <div className="flex items-center justify-center h-6 px-2 text-sm font-semibold uppercase border rounded-lg select-none w-fit font-display bg-background"> {method} </div> ); } export function Endpoint({ path, method, isServerOnly, className, }: { path: string; method: "POST" | "GET" | "DELETE" | "PUT"; isServerOnly?: boolean; className?: string; }) { const [copying, setCopying] = useState(false); return ( <div className={cn( "relative flex items-center w-full gap-2 p-2 border-t border-x border-border bg-fd-secondary/50 group", className, )} > <Method method={method} /> <span className="font-mono text-sm text-muted-foreground">{path}</span> </div> ); } ``` -------------------------------------------------------------------------------- /docs/components/display-techstack.tsx: -------------------------------------------------------------------------------- ```typescript import { cn } from "@/lib/utils"; import { techStackIcons } from "./techstack-icons"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; export const TechStackDisplay = ({ skills, className, }: { skills: string[]; className?: string; }) => { return ( <div className={cn( "flex gap-7 flex-wrap mt-3 justify-center items-center max-w-4xl", className, )} > {skills.map((icon) => { return ( <TooltipProvider delayDuration={50} key={icon}> <Tooltip> <TooltipTrigger asChild> <span className="transform duration-300 hover:rotate-12 transition-transform"> {techStackIcons[icon].icon} </span> </TooltipTrigger> <TooltipContent>{techStackIcons[icon].name}</TooltipContent> </Tooltip> </TooltipProvider> ); })} </div> ); }; ``` -------------------------------------------------------------------------------- /docs/components/ui/use-copy-button.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { type MouseEventHandler, useEffect, useRef, useState } from "react"; import { useEffectEvent } from "fumadocs-core/utils/use-effect-event"; export function useCopyButton( onCopy: () => void | Promise<void>, ): [checked: boolean, onClick: MouseEventHandler] { const [checked, setChecked] = useState(false); const timeoutRef = useRef<number | null>(null); const onClick: MouseEventHandler = useEffectEvent(() => { if (timeoutRef.current) window.clearTimeout(timeoutRef.current); const res = Promise.resolve(onCopy()); void res.then(() => { setChecked(true); timeoutRef.current = window.setTimeout(() => { setChecked(false); }, 1500); }); }); // Avoid updates after being unmounted useEffect(() => { return () => { if (timeoutRef.current) window.clearTimeout(timeoutRef.current); }; }, []); return [checked, onClick]; } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/username/schema.ts: -------------------------------------------------------------------------------- ```typescript import type { BetterAuthPluginDBSchema } from "@better-auth/core/db"; export const getSchema = (normalizer: { username: (username: string) => string; displayUsername: (displayUsername: string) => string; }) => { return { user: { fields: { username: { type: "string", required: false, sortable: true, unique: true, returned: true, transform: { input(value) { return typeof value !== "string" ? value : normalizer.username(value as string); }, }, }, displayUsername: { type: "string", required: false, transform: { input(value) { return typeof value !== "string" ? value : normalizer.displayUsername(value as string); }, }, }, }, }, } satisfies BetterAuthPluginDBSchema; }; export type UsernameSchema = ReturnType<typeof getSchema>; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/client/solid/solid-store.ts: -------------------------------------------------------------------------------- ```typescript import type { Store, StoreValue } from "nanostores"; import { createStore, reconcile } from "solid-js/store"; import type { Accessor } from "solid-js"; import { onCleanup } from "solid-js"; /** * Subscribes to store changes and gets store’s value. * * @param store Store instance. * @returns Store value. */ export function useStore< SomeStore extends Store, Value extends StoreValue<SomeStore>, >(store: SomeStore): Accessor<Value> { // Activate the store explicitly: // https://github.com/nanostores/solid/issues/19 const unbindActivation = store.listen(() => {}); const [state, setState] = createStore({ value: store.get(), }); const unsubscribe = store.subscribe((newValue) => { setState("value", reconcile(newValue)); }); onCleanup(() => unsubscribe()); // Remove temporary listener now that there is already a proper subscriber. unbindActivation(); return () => state.value; } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/client/vue/vue-store.ts: -------------------------------------------------------------------------------- ```typescript import type { Store, StoreValue } from "nanostores"; import { getCurrentInstance, getCurrentScope, onScopeDispose, readonly, shallowRef, type DeepReadonly, type ShallowRef, type UnwrapNestedRefs, } from "vue"; export function registerStore(store: Store) { let instance = getCurrentInstance(); if (instance && instance.proxy) { let vm = instance.proxy as any; let cache = "_nanostores" in vm ? vm._nanostores : (vm._nanostores = []); cache.push(store); } } export function useStore< SomeStore extends Store, Value extends StoreValue<SomeStore>, >(store: SomeStore): DeepReadonly<UnwrapNestedRefs<ShallowRef<Value>>> { let state = shallowRef(); let unsubscribe = store.subscribe((value) => { state.value = value; }); getCurrentScope() && onScopeDispose(unsubscribe); if (process.env.NODE_ENV !== "production") { registerStore(store); return readonly(state); } return state; } ``` -------------------------------------------------------------------------------- /demo/expo-example/tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "esModuleInterop": true, "skipLibCheck": true, "target": "ES2022", "lib": ["ES2022"], "allowJs": true, "resolveJsonModule": true, "moduleDetection": "force", "isolatedModules": true, "incremental": true, "disableSourceOfProjectReferenceRedirect": true, "tsBuildInfoFile": "${configDir}/.cache/tsbuildinfo.json", "strict": true, "noUncheckedIndexedAccess": true, "checkJs": false, "types": ["nativewind"], "module": "es2022", "moduleResolution": "Bundler", "noEmit": true, "jsx": "react-native", "moduleSuffixes": [".ios", ".android", ".native", ""], "paths": { "@/*": ["./src/*"] } }, "include": [ "src", ".expo/types/**/*.ts", "expo-env.d.ts", "nativewind-env.d.ts" ], "exclude": ["node_modules", "build", "dist", ".next", ".expo"], "extends": "expo/tsconfig.base" } ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/input.tsx: -------------------------------------------------------------------------------- ```typescript import * as React from "react"; import { cn } from "@/lib/utils"; function Input({ className, type, ...props }: React.ComponentProps<"input">) { return ( <input type={type} data-slot="input" className={cn( "border-input file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className, )} {...props} /> ); } export { Input }; ``` -------------------------------------------------------------------------------- /docs/components/ui/input.tsx: -------------------------------------------------------------------------------- ```typescript import * as React from "react"; import { cn } from "@/lib/utils"; function Input({ className, type, ...props }: React.ComponentProps<"input">) { return ( <input type={type} data-slot="input" className={cn( "border-input file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className, )} {...props} /> ); } export { Input }; ``` -------------------------------------------------------------------------------- /packages/core/src/db/schema/account.ts: -------------------------------------------------------------------------------- ```typescript import * as z from "zod"; import { coreSchema } from "./shared"; export const accountSchema = coreSchema.extend({ providerId: z.string(), accountId: z.string(), userId: z.coerce.string(), accessToken: z.string().nullish(), refreshToken: z.string().nullish(), idToken: z.string().nullish(), /** * Access token expires at */ accessTokenExpiresAt: z.date().nullish(), /** * Refresh token expires at */ refreshTokenExpiresAt: z.date().nullish(), /** * The scopes that the user has authorized */ scope: z.string().nullish(), /** * Password is only stored in the credential provider */ password: z.string().nullish(), }); /** * Account schema type used by better-auth, note that it's possible that account could have additional fields * * todo: we should use generics to extend this type with additional fields from plugins and options in the future */ export type Account = z.infer<typeof accountSchema>; ``` -------------------------------------------------------------------------------- /packages/stripe/CHANGELOG.md: -------------------------------------------------------------------------------- ```markdown # @better-auth/stripe ## 1.3.4 ### Patch Changes - ac6baba: chore: fix typo on `freeTrial` - c2fb1aa: Fix duplicate trials when switching plans - 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. Organization - Added listMembers API with pagination, sorting, and filtering. - Added membersLimit param to getFullOrganization. - Improved client inference for additional fields in organization schemas. - Bug Fixes - Fixed date handling by casting DB values to Date objects before using date methods. - Fixed Notion OAuth to extract user info correctly. - Ensured session is set in context when reading from cookie cach - Updated dependencies [2bd2fa9] - [email protected] ``` -------------------------------------------------------------------------------- /packages/telemetry/package.json: -------------------------------------------------------------------------------- ```json { "name": "@better-auth/telemetry", "version": "1.4.0-beta.10", "description": "Telemetry package for Better Auth", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", "exports": { ".": { "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }, "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } }, "typesVersions": { "*": { "index": [ "dist/index.d.ts" ] } }, "scripts": { "build": "tsdown", "dev": "tsdown --watch", "typecheck": "tsc --project tsconfig.json" }, "devDependencies": { "@better-auth/core": "workspace:*", "tsdown": "^0.15.6", "type-fest": "^4.31.0" }, "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "catalog:" }, "peerDependencies": { "@better-auth/core": "workspace:*" } } ``` -------------------------------------------------------------------------------- /docs/components/landing/section.tsx: -------------------------------------------------------------------------------- ```typescript import type React from "react"; import SectionSvg from "./section-svg"; const Section = ({ className, id, crosses, crossesOffset, customPaddings, children, }: { className: string; id: string; crosses?: boolean; crossesOffset: string; customPaddings: boolean; children: React.ReactNode; }) => { return ( <div id={id} className={` relative ${customPaddings || `py-10 lg:py-16 ${crosses ? "" : ""}`} ${className || " "}`} > {children} <div className="hidden absolute top-0 left-5 w-[0.0625rem] h-[calc(100%_+_30px)] dark:bg-[#26242C] bg-stone-200 pointer-events-none lg:block lg:left-16 xl:left-16" /> <div className="hidden absolute top-0 right-5 w-[0.0625rem] h-[calc(100%_+_30px)] dark:bg-[#26242C] bg-stone-200 pointer-events-none lg:block lg:right-14 xl:right-14" /> {crosses && ( <> <SectionSvg crossesOffset={crossesOffset} /> </> )} </div> ); }; export default Section; ``` -------------------------------------------------------------------------------- /docs/content/docs/adapters/mongo.mdx: -------------------------------------------------------------------------------- ```markdown --- title: MongoDB Adapter description: Integrate Better Auth with MongoDB. --- MongoDB is a popular NoSQL database that is widely used for building scalable and flexible applications. It provides a flexible schema that allows for easy data modeling and querying. Read more here: [MongoDB](https://www.mongodb.com/). ## Example Usage Make sure you have MongoDB installed and configured. Then, you can use the mongodb adapter. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { MongoClient } from "mongodb"; import { mongodbAdapter } from "better-auth/adapters/mongodb"; const client = new MongoClient("mongodb://localhost:27017/database"); const db = client.db(); export const auth = betterAuth({ database: mongodbAdapter(db, { // Optional: if you don't provide a client, database transactions won't be enabled. client }), }); ``` ## Schema generation & migration For MongoDB, we don't need to generate or migrate the schema. ``` -------------------------------------------------------------------------------- /demo/expo-example/src/components/ui/input.tsx: -------------------------------------------------------------------------------- ```typescript import * as React from "react"; import { TextInput, type TextInputProps } from "react-native"; import { cn } from "@/lib/utils"; const Input = React.forwardRef< React.ElementRef<typeof TextInput>, TextInputProps >(({ className, placeholderClassName, ...props }, ref) => { return ( <TextInput ref={ref} className={cn( "web:flex h-10 native:h-12 web:w-full rounded-md border border-input bg-background px-3 web:py-2 text-base lg:text-sm native:text-lg native:leading-[1.25] text-foreground placeholder:text-muted-foreground web:ring-offset-background file:border-0 file:bg-transparent file:font-medium web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2", props.editable === false && "opacity-50 web:cursor-not-allowed", className, )} placeholderClassName={cn("text-muted-foreground", placeholderClassName)} {...props} /> ); }); Input.displayName = "Input"; export { Input }; ``` -------------------------------------------------------------------------------- /docs/components/techstack-icons.tsx: -------------------------------------------------------------------------------- ```typescript import { Icons } from "./icons"; type TechStackIconType = { [key: string]: { name: string; icon: any; }; }; export const techStackIcons: TechStackIconType = { nextJs: { name: "Next.js", icon: <Icons.nextJS className="w-10 h-10" />, }, nuxt: { name: "Nuxt", icon: <Icons.nuxt className="w-10 h-10" />, }, svelteKit: { name: "SvelteKit", icon: <Icons.svelteKit className="w-10 h-10" />, }, solidStart: { name: "SolidStart", icon: <Icons.solidStart className="w-10 h-10" />, }, react: { name: "React", icon: <Icons.react className="w-10 h-10" />, }, hono: { name: "Hono", icon: <Icons.hono className="w-10 h-10" />, }, astro: { name: "Astro", icon: <Icons.astro className="w-10 h-10" />, }, tanstack: { name: "TanStack Start", icon: <Icons.tanstack className="w-10 h-10" />, }, expo: { name: "Expo", icon: <Icons.expo className="w-10 h-10" />, }, nitro: { name: "Nitro", icon: <Icons.nitro className="w-10 h-10" />, }, }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/admin/has-permission.ts: -------------------------------------------------------------------------------- ```typescript import { defaultRoles } from "./access"; import type { AdminOptions } from "./types"; type PermissionExclusive = | { /** * @deprecated Use `permissions` instead */ permission: { [key: string]: string[] }; permissions?: never; } | { permissions: { [key: string]: string[] }; permission?: never; }; export const hasPermission = ( input: { userId?: string; role?: string; options?: AdminOptions; } & PermissionExclusive, ) => { if (input.userId && input.options?.adminUserIds?.includes(input.userId)) { return true; } if (!input.permissions && !input.permission) { return false; } const roles = (input.role || input.options?.defaultRole || "user").split(","); const acRoles = input.options?.roles || defaultRoles; for (const role of roles) { const _role = acRoles[role as keyof typeof acRoles]; const result = _role?.authorize(input.permission ?? input.permissions); if (result?.success) { return true; } } return false; }; ``` -------------------------------------------------------------------------------- /docs/components/ui/switch.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as React from "react"; import * as SwitchPrimitive from "@radix-ui/react-switch"; import { cn } from "@/lib/utils"; function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) { return ( <SwitchPrimitive.Root data-slot="switch" className={cn( "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 inline-flex h-5 w-9 shrink-0 items-center rounded-full border-2 border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", className, )} {...props} > <SwitchPrimitive.Thumb data-slot="switch-thumb" className={cn( "bg-background pointer-events-none block size-4 rounded-full ring-0 shadow-lg transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0", )} /> </SwitchPrimitive.Root> ); } export { Switch }; ``` -------------------------------------------------------------------------------- /docs/components/ui/aside-link.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import type { ClassValue } from "clsx"; import Link from "next/link"; import { useSelectedLayoutSegment } from "next/navigation"; import { cn } from "@/lib/utils"; type Props = { href: string; children: React.ReactNode; startWith: string; title?: string | null; className?: ClassValue; activeClassName?: ClassValue; } & React.AnchorHTMLAttributes<HTMLAnchorElement>; export const AsideLink = ({ href, children, startWith, title, className, activeClassName, ...props }: Props) => { const segment = useSelectedLayoutSegment(); const path = href; const isActive = path.replace("/docs/", "") === segment; return ( <Link href={href} className={cn( isActive ? cn("text-foreground bg-primary/10", activeClassName) : "text-muted-foreground hover:text-foreground hover:bg-primary/10", "w-full transition-colors flex items-center gap-x-2.5 hover:bg-primary/10 px-5 py-1", className, )} {...props} > {children} </Link> ); }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/adapters/mongodb-adapter/adapter.mongo-db.test.ts: -------------------------------------------------------------------------------- ```typescript import { MongoClient, ObjectId } from "mongodb"; import { testAdapter } from "../test-adapter"; import { mongodbAdapter } from "./mongodb-adapter"; import { normalTestSuite, performanceTestSuite, authFlowTestSuite, transactionsTestSuite, } from "../tests"; const dbClient = async (connectionString: string, dbName: string) => { const client = new MongoClient(connectionString); await client.connect(); const db = client.db(dbName); return { db, client }; }; const { db, client } = await dbClient( "mongodb://127.0.0.1:27017", "better-auth", ); const { execute } = await testAdapter({ adapter: (options) => { return mongodbAdapter(db, { transaction: false }); }, runMigrations: async (betterAuthOptions) => {}, tests: [ normalTestSuite(), authFlowTestSuite(), transactionsTestSuite(), // numberIdTestSuite(), // Mongo doesn't support number ids performanceTestSuite(), ], customIdGenerator: () => new ObjectId().toString(), defaultRetryCount: 20, }); execute(); ``` -------------------------------------------------------------------------------- /demo/nextjs/app/device/denied/page.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { X } from "lucide-react"; import Link from "next/link"; export default function DeviceDeniedPage() { return ( <div className="flex min-h-screen items-center justify-center p-4"> <Card className="w-full max-w-md p-6"> <div className="space-y-4 text-center"> <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100"> <X className="h-6 w-6 text-red-600" /> </div> <div> <h1 className="text-2xl font-bold">Device Denied</h1> <p className="text-muted-foreground mt-2"> The device authorization request has been denied. </p> </div> <p className="text-sm text-muted-foreground"> The device will not be able to access your account. </p> <Button asChild className="w-full"> <Link href="/">Return to Home</Link> </Button> </div> </Card> </div> ); } ``` -------------------------------------------------------------------------------- /docs/components/generate-secret.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { createRandomStringGenerator } from "@better-auth/utils/random"; import { useState } from "react"; import { Button } from "./ui/button"; export const GenerateSecret = () => { const [generated, setGenerated] = useState(false); const generateRandomString = createRandomStringGenerator("a-z", "0-9", "A-Z"); return ( <div className="my-2"> <Button variant="outline" size="sm" disabled={generated} onClick={() => { const elements = document.querySelectorAll("pre code span.line span"); for (let i = 0; i < elements.length; i++) { if (elements[i].textContent === "BETTER_AUTH_SECRET=") { elements[i].textContent = `BETTER_AUTH_SECRET=${generateRandomString(32)}`; setGenerated(true); setTimeout(() => { elements[i].textContent = "BETTER_AUTH_SECRET="; setGenerated(false); }, 5000); } } }} > {generated ? "Generated" : "Generate Secret"} </Button> </div> ); }; ``` -------------------------------------------------------------------------------- /e2e/integration/solid-vinxi/e2e/test.spec.ts: -------------------------------------------------------------------------------- ```typescript import { test, expect } from "@playwright/test"; import { runClient, setup } from "./utils"; const { ref, start, clean } = setup(); test.describe("solid-vinxi", async () => { test.beforeEach(async () => start()); test.afterEach(async () => clean()); test("signIn with existing email and password should work", async ({ page, }) => { await page.goto(`http://localhost:${ref.clientPort}`); await page.locator("text=Ready").waitFor(); await expect( runClient(page, ({ client }) => typeof client !== "undefined"), ).resolves.toBe(true); await expect( runClient(page, async ({ client }) => client.getSession()), ).resolves.toEqual({ data: null, error: null }); await runClient(page, ({ client }) => client.signIn.email({ email: "[email protected]", password: "password123", }), ); // Check that the session is now set const cookies = await page.context().cookies(); expect( cookies.find((c) => c.name === "better-auth.session_token"), ).toBeDefined(); }); }); ``` -------------------------------------------------------------------------------- /packages/better-auth/src/adapters/prisma-adapter/test/get-prisma-client.ts: -------------------------------------------------------------------------------- ```typescript import type { PrismaClient } from "@prisma/client"; type PC = InstanceType<typeof PrismaClient>; let migrationCount = 0; const clientMap = new Map<string, PC>(); export const getPrismaClient = async ( dialect: "sqlite" | "postgresql" | "mysql", ) => { if (clientMap.has(`${dialect}-${migrationCount}`)) { return clientMap.get(`${dialect}-${migrationCount}`) as PC; } const { PrismaClient } = await import( migrationCount === 0 ? "@prisma/client" : `./.tmp/prisma-client-${dialect}-${migrationCount}` ); const db = new PrismaClient(); clientMap.set(`${dialect}-${migrationCount}`, db); return db as PC; }; export const incrementMigrationCount = () => { migrationCount++; return migrationCount; }; export const destroyPrismaClient = ({ migrationCount, dialect, }: { migrationCount: number; dialect: "sqlite" | "postgresql" | "mysql"; }) => { const db = clientMap.get(`${dialect}-${migrationCount}`); if (db) { db.$disconnect(); } clientMap.delete(`${dialect}-${migrationCount}`); }; ``` -------------------------------------------------------------------------------- /docs/lib/chat/inkeep-qa-schema.ts: -------------------------------------------------------------------------------- ```typescript import { z } from "zod"; const InkeepRecordTypes = z.enum([ "documentation", "site", "discourse_post", "github_issue", "github_discussion", "stackoverflow_question", "discord_forum_post", "discord_message", "custom_question_answer", ]); const LinkType = z.union([InkeepRecordTypes, z.string()]); const LinkSchema = z.object({ label: z.string().nullish(), url: z.string(), title: z.string().nullish(), type: LinkType.nullish(), breadcrumbs: z.array(z.string()).nullish(), }); const LinksSchema = z.array(LinkSchema).nullish(); export const ProvideLinksToolSchema = z.object({ links: LinksSchema, }); const KnownAnswerConfidence = z.enum([ "very_confident", "somewhat_confident", "not_confident", "no_sources", "other", ]); const AnswerConfidence = z.union([KnownAnswerConfidence, z.string()]); // evolvable const AIAnnotationsToolSchema = z.object({ answerConfidence: AnswerConfidence, }); export const ProvideAIAnnotationsToolSchema = z.object({ aiAnnotations: AIAnnotationsToolSchema, }); ``` -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- ```json { "$schema": "https://turborepo.org/schema.json", "globalDependencies": [ "package.json", "tsconfig.json", "pnpm-lock.yaml", "pnpm-workspace.yaml" ], "tasks": { "dev": { "cache": false, "persistent": true }, "build": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", ".env*"], "outputs": [".next/**", "!.next/cache/**", "dist/**"] }, "clean": {}, "format": { "dependsOn": ["//#format"] }, "//#format": {}, "lint": {}, "knip": { "cache": true }, "test": { "dependsOn": ["build"], "outputs": [] }, "e2e:smoke": { "dependsOn": ["^build"], "outputs": [] }, "e2e:integration": { "dependsOn": ["^build"], "outputs": [] }, "typecheck": { "outputs": [".tsbuildinfo", "dist/**"], "cache": true }, "deploy": { "cache": false }, "migrate": { "cache": false }, "generate": { "cache": false } } } ``` -------------------------------------------------------------------------------- /demo/nextjs/app/device/success/page.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Check } from "lucide-react"; import Link from "next/link"; export default function DeviceSuccessPage() { return ( <div className="flex min-h-screen items-center justify-center p-4"> <Card className="w-full max-w-md p-6"> <div className="space-y-4 text-center"> <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100"> <Check className="h-6 w-6 text-green-600" /> </div> <div> <h1 className="text-2xl font-bold">Device Approved</h1> <p className="text-muted-foreground mt-2"> The device has been successfully authorized to access your account. </p> </div> <p className="text-sm text-muted-foreground"> You can now return to your device to continue. </p> <Button asChild className="w-full"> <Link href="/">Return to Home</Link> </Button> </div> </Card> </div> ); } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/two-factor/client.ts: -------------------------------------------------------------------------------- ```typescript import type { BetterAuthClientPlugin } from "@better-auth/core"; import type { twoFactor as twoFa } from "../../plugins/two-factor"; export const twoFactorClient = (options?: { /** * a redirect function to call if a user needs to verify * their two factor */ onTwoFactorRedirect?: () => void | Promise<void>; }) => { return { id: "two-factor", $InferServerPlugin: {} as ReturnType<typeof twoFa>, atomListeners: [ { matcher: (path) => path.startsWith("/two-factor/"), signal: "$sessionSignal", }, ], pathMethods: { "/two-factor/disable": "POST", "/two-factor/enable": "POST", "/two-factor/send-otp": "POST", "/two-factor/generate-backup-codes": "POST", }, fetchPlugins: [ { id: "two-factor", name: "two-factor", hooks: { async onSuccess(context) { if (context.data?.twoFactorRedirect) { if (options?.onTwoFactorRedirect) { await options.onTwoFactorRedirect(); } } }, }, }, ], } satisfies BetterAuthClientPlugin; }; ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/avatar.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as React from "react"; import * as AvatarPrimitive from "@radix-ui/react-avatar"; import { cn } from "@/lib/utils"; function Avatar({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root>) { return ( <AvatarPrimitive.Root data-slot="avatar" className={cn( "relative flex size-8 shrink-0 overflow-hidden rounded-full", className, )} {...props} /> ); } function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>) { return ( <AvatarPrimitive.Image data-slot="avatar-image" className={cn("aspect-square size-full", className)} {...props} /> ); } function AvatarFallback({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) { return ( <AvatarPrimitive.Fallback data-slot="avatar-fallback" className={cn( "bg-muted flex size-full items-center justify-center rounded-full", className, )} {...props} /> ); } export { Avatar, AvatarImage, AvatarFallback }; ``` -------------------------------------------------------------------------------- /docs/components/nav-link.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import { cn } from "@/lib/utils"; import Link from "next/link"; import { useSelectedLayoutSegment } from "next/navigation"; type Props = { href: string; children: React.ReactNode; className?: string; external?: boolean; }; export const NavLink = ({ href, children, className, external }: Props) => { const segment = useSelectedLayoutSegment(); const isActive = segment === href.slice(1) || (segment === null && href === "/"); return ( <li className={cn("relative group", className)}> <Link href={href} className={cn( "w-full h-full block py-4 px-5 transition-colors", "group-hover:text-foreground", isActive ? "text-foreground" : "text-muted-foreground", )} target={external ? "_blank" : "_parent"} > {children} </Link> <div className={cn( "absolute bottom-0 h-0.5 bg-muted-foreground opacity-0 transition-all duration-500", "group-hover:opacity-100 group-hover:w-full", isActive ? "opacity-100 w-full" : "w-0", )} /> </li> ); }; ``` -------------------------------------------------------------------------------- /docs/components/ui/avatar.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as React from "react"; import * as AvatarPrimitive from "@radix-ui/react-avatar"; import { cn } from "@/lib/utils"; function Avatar({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root>) { return ( <AvatarPrimitive.Root data-slot="avatar" className={cn( "relative flex size-8 shrink-0 overflow-hidden rounded-full", className, )} {...props} /> ); } function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>) { return ( <AvatarPrimitive.Image data-slot="avatar-image" className={cn("aspect-square size-full", className)} {...props} /> ); } function AvatarFallback({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) { return ( <AvatarPrimitive.Fallback data-slot="avatar-fallback" className={cn( "bg-muted flex size-full items-center justify-center rounded-full", className, )} {...props} /> ); } export { Avatar, AvatarImage, AvatarFallback }; ``` -------------------------------------------------------------------------------- /demo/nextjs/components/tier-labels.tsx: -------------------------------------------------------------------------------- ```typescript import type React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const tierVariants = cva( "inline-flex items-center rounded-full px-3 py-1 text-xs font-semibold ring-1 ring-inset transition-all duration-300 ease-in-out", { variants: { variant: { free: "bg-gray-500 text-white ring-gray-400 hover:bg-gray-600", plus: "bg-lime-700/40 text-white ring-lime-200/40 hover:bg-lime-600", pro: "bg-purple-800/80 ring-purple-400 hover:bg-purple-700", }, }, defaultVariants: { variant: "free", }, }, ); export interface SubscriptionTierLabelProps extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof tierVariants> { tier?: "free" | "plus" | "pro"; } export const SubscriptionTierLabel: React.FC<SubscriptionTierLabelProps> = ({ tier = "free", className, ...props }) => { return ( <span className={cn(tierVariants({ variant: tier }), className)} {...props}> {tier.charAt(0).toUpperCase() + tier.slice(1)} </span> ); }; ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/slider.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as React from "react"; import * as SliderPrimitive from "@radix-ui/react-slider"; import { cn } from "@/lib/utils"; const Slider = ({ ref, className, ...props }: React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> & { ref: React.RefObject<React.ElementRef<typeof SliderPrimitive.Root>>; }) => ( <SliderPrimitive.Root ref={ref} className={cn( "relative flex w-full touch-none select-none items-center", className, )} {...props} > <SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20"> <SliderPrimitive.Range className="absolute h-full bg-primary" /> </SliderPrimitive.Track> <SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" /> </SliderPrimitive.Root> ); Slider.displayName = SliderPrimitive.Root.displayName; export { Slider }; ``` -------------------------------------------------------------------------------- /e2e/integration/vanilla-node/e2e/test.spec.ts: -------------------------------------------------------------------------------- ```typescript import { test, expect } from "@playwright/test"; import { runClient, setup } from "./utils"; const { ref, start, clean } = setup(); test.describe("vanilla-node", async () => { test.beforeEach(async () => start()); test.afterEach(async () => clean()); test("signIn with existing email and password should work", async ({ page, }) => { await page.goto( `http://localhost:${ref.clientPort}/?port=${ref.serverPort}`, ); await page.locator("text=Ready").waitFor(); await expect( runClient(page, ({ client }) => typeof client !== "undefined"), ).resolves.toBe(true); await expect( runClient(page, async ({ client }) => client.getSession()), ).resolves.toEqual({ data: null, error: null }); await runClient(page, ({ client }) => client.signIn.email({ email: "[email protected]", password: "password123", }), ); // Check that the session is now set const cookies = await page.context().cookies(); expect( cookies.find((c) => c.name === "better-auth.session_token"), ).toBeDefined(); }); }); ``` -------------------------------------------------------------------------------- /packages/core/src/oauth2/utils.ts: -------------------------------------------------------------------------------- ```typescript import { base64Url } from "@better-auth/utils/base64"; import type { OAuth2Tokens } from "./oauth-provider"; export function getOAuth2Tokens(data: Record<string, any>): OAuth2Tokens { const getDate = (seconds: number) => { const now = new Date(); return new Date(now.getTime() + seconds * 1000); }; return { tokenType: data.token_type, accessToken: data.access_token, refreshToken: data.refresh_token, accessTokenExpiresAt: data.expires_in ? getDate(data.expires_in) : undefined, refreshTokenExpiresAt: data.refresh_token_expires_in ? getDate(data.refresh_token_expires_in) : undefined, scopes: data?.scope ? typeof data.scope === "string" ? data.scope.split(" ") : data.scope : [], idToken: data.id_token, }; } export async function generateCodeChallenge(codeVerifier: string) { const encoder = new TextEncoder(); const data = encoder.encode(codeVerifier); const hash = await crypto.subtle.digest("SHA-256", data); return base64Url.encode(new Uint8Array(hash), { padding: false, }); } ``` -------------------------------------------------------------------------------- /docs/scripts/endpoint-to-doc/output.mdx: -------------------------------------------------------------------------------- ```markdown {/* -------------------------------------------------------- */} {/* APIMethod component */} {/* -------------------------------------------------------- */} <APIMethod path="/subscription/restore" method="POST" requireSession > ```ts type restoreSubscription = { /** * Reference id of the subscription to restore. */ referenceId?: string = '123' /** * The id of the subscription to restore. */ subscriptionId: string = 'sub_123' } ``` </APIMethod> {/* -------------------------------------------------------- */} {/* JSDOC For the endpoint */} {/* -------------------------------------------------------- */} /** * ### Endpoint * * POST `/subscription/restore` * * ### API Methods * * **server:** * `auth.api.restoreSubscription` * * **client:** * `authClient.subscription.restore` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/subscription#api-method-subscription-restore) */ ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/index.ts: -------------------------------------------------------------------------------- ```typescript export * from "./organization"; export * from "./two-factor"; export * from "./username"; export * from "./bearer"; export * from "../types/plugins"; export * from "../utils/hide-metadata"; export * from "./magic-link"; export * from "./phone-number"; export * from "./anonymous"; export * from "./admin"; export * from "./generic-oauth"; export * from "./jwt"; export * from "./multi-session"; export * from "./email-otp"; export * from "./one-tap"; export * from "./oauth-proxy"; export * from "./custom-session"; export * from "./open-api"; export * from "./oidc-provider"; export * from "./captcha"; export * from "./api-key"; export * from "./haveibeenpwned"; export * from "./one-time-token"; export * from "./mcp"; export * from "./siwe"; export * from "./device-authorization"; export * from "./last-login-method"; /** * @deprecated Please import from `better-auth/api` directly. */ export { createAuthEndpoint, createAuthMiddleware, optionsMiddleware, type AuthEndpoint, type AuthMiddleware, } from "@better-auth/core/middleware"; ``` -------------------------------------------------------------------------------- /demo/expo-example/app.config.ts: -------------------------------------------------------------------------------- ```typescript import type { ConfigContext, ExpoConfig } from "expo/config"; export default ({ config }: ConfigContext): ExpoConfig => ({ ...config, name: "Better Auth", slug: "better-auth", scheme: "better-auth", version: "0.1.0", orientation: "portrait", icon: "./assets/icon.png", userInterfaceStyle: "automatic", splash: { image: "./assets/icon.png", resizeMode: "contain", backgroundColor: "#1F104A", }, web: { bundler: "metro", output: "server", }, updates: { fallbackToCacheTimeout: 0, }, assetBundlePatterns: ["**/*"], ios: { bundleIdentifier: "your.bundle.identifier", supportsTablet: true, }, android: { package: "your.bundle.identifier", adaptiveIcon: { foregroundImage: "./assets/icon.png", backgroundColor: "#1F104A", }, }, // extra: { // eas: { // projectId: "your-eas-project-id", // }, // }, experiments: { tsconfigPaths: true, typedRoutes: true, }, plugins: [ [ "expo-router", { origin: "http://localhost:8081", }, ], "expo-secure-store", "expo-font", ], }); ``` -------------------------------------------------------------------------------- /packages/cli/test/__snapshots__/migrations.sql: -------------------------------------------------------------------------------- ```sql create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "emailVerified" integer not null, "image" text, "createdAt" date not null, "updatedAt" date not null); create table "session" ("id" text not null primary key, "expiresAt" date not null, "token" text not null unique, "createdAt" date not null, "updatedAt" date not null, "ipAddress" text, "userAgent" text, "userId" text not null references "user" ("id") on delete cascade); create table "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id") on delete cascade, "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" date, "refreshTokenExpiresAt" date, "scope" text, "password" text, "createdAt" date not null, "updatedAt" date not null); create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" date not null, "createdAt" date not null, "updatedAt" date not null); ``` -------------------------------------------------------------------------------- /docs/components/docs/ui/collapsible.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; import { forwardRef, useEffect, useState } from "react"; import { cn } from "../../../lib/utils"; const Collapsible = CollapsiblePrimitive.Root; const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; const CollapsibleContent = forwardRef< HTMLDivElement, React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.CollapsibleContent> >(({ children, ...props }, ref) => { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); return ( <CollapsiblePrimitive.CollapsibleContent ref={ref} {...props} className={cn( "overflow-hidden", mounted && "data-[state=closed]:animate-fd-collapsible-up data-[state=open]:animate-fd-collapsible-down", props.className, )} > {children} </CollapsiblePrimitive.CollapsibleContent> ); }); CollapsibleContent.displayName = CollapsiblePrimitive.CollapsibleContent.displayName; export { Collapsible, CollapsibleTrigger, CollapsibleContent }; ``` -------------------------------------------------------------------------------- /docs/next.config.js: -------------------------------------------------------------------------------- ```javascript import { createMDX } from "fumadocs-mdx/next"; const withMDX = createMDX(); /** @type {import('next').NextConfig} */ const config = { async rewrites() { return [ { source: "/docs/:path*.mdx", destination: "/llms.txt/:path*", }, ]; }, redirects: async () => { return [ { source: "/docs", destination: "/docs/introduction", permanent: true, }, { source: "/docs/examples", destination: "/docs/examples/next-js", permanent: true, }, ]; }, serverExternalPackages: [ "ts-morph", "typescript", "oxc-transform", "@shikijs/twoslash", ], images: { remotePatterns: [ { hostname: "images.unsplash.com", }, { hostname: "assets.aceternity.com", }, { hostname: "pbs.twimg.com", }, { hostname: "github.com", }, { hostname: "hebbkx1anhila5yf.public.blob.vercel-storage.com", }, ], }, reactStrictMode: true, typescript: { ignoreBuildErrors: true, }, experimental: { turbopackFileSystemCacheForDev: true, }, }; export default withMDX(config); ``` -------------------------------------------------------------------------------- /packages/better-auth/src/index.ts: -------------------------------------------------------------------------------- ```typescript //#region Re-exports necessaries from core module export * from "@better-auth/core/env"; export * from "@better-auth/core"; export * from "@better-auth/core/oauth2"; export * from "@better-auth/core/error"; export * from "@better-auth/core/utils"; //#endregion export { getCurrentAdapter } from "@better-auth/core/context"; export * from "./auth"; export * from "./types"; export * from "./utils"; export type * from "better-call"; export type * from "zod/v4"; // @ts-expect-error we need to export core to make sure type annotations works with v4/core export type * from "zod/v4/core"; //@ts-expect-error: we need to export helper types even when they conflict with better-call types to avoid "The inferred type of 'auth' cannot be named without a reference to..." export type * from "./types/helper"; // export this as we are referencing OAuth2Tokens in the `refresh-token` api as return type // telemetry exports for CLI and consumers export { createTelemetry, getTelemetryAuthConfig, type TelemetryEvent, } from "@better-auth/telemetry"; export { APIError } from "./api"; ``` -------------------------------------------------------------------------------- /e2e/smoke/test/fixtures/cloudflare/src/index.ts: -------------------------------------------------------------------------------- ```typescript import { Hono } from "hono"; import { betterAuth } from "better-auth"; import { createDrizzle } from "./db"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; interface CloudflareBindings { DB: D1Database; } const createAuth = (env: CloudflareBindings) => betterAuth({ baseURL: "http://localhost:4000", database: drizzleAdapter(createDrizzle(env.DB), { provider: "sqlite" }), emailAndPassword: { enabled: true, }, logger: { level: "debug", }, }); type Auth = ReturnType<typeof createAuth>; const app = new Hono<{ Bindings: CloudflareBindings; Variables: { auth: Auth; }; }>(); app.use("*", async (c, next) => { const auth = createAuth(c.env); c.set("auth", auth); await next(); }); app.on(["POST", "GET"], "/api/auth/*", (c) => c.var.auth.handler(c.req.raw)); app.get("/", async (c) => { const session = await c.var.auth.api.getSession({ headers: c.req.raw.headers, }); if (session) return c.text("Hello " + session.user.name); return c.text("Not logged in"); }); export default app satisfies ExportedHandler<CloudflareBindings>; ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/badge.tsx: -------------------------------------------------------------------------------- ```typescript import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const badgeVariants = cva( "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", outline: "text-foreground", }, }, defaultVariants: { variant: "default", }, }, ); export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {} function Badge({ className, variant, ...props }: BadgeProps) { return ( <div className={cn(badgeVariants({ variant }), className)} {...props} /> ); } export { Badge, badgeVariants }; ``` -------------------------------------------------------------------------------- /docs/source.config.ts: -------------------------------------------------------------------------------- ```typescript import { defineDocs, defineConfig, defineCollections, } from "fumadocs-mdx/config"; import { z } from "zod"; import { remarkAutoTypeTable, createGenerator } from "fumadocs-typescript"; import { remarkNpm } from "fumadocs-core/mdx-plugins"; export const docs = defineDocs({ dir: "./content/docs", }); export const changelogCollection = defineCollections({ type: "doc", dir: "./content/changelogs", schema: z.object({ title: z.string(), description: z.string(), date: z.date(), }), }); export const blogCollection = defineCollections({ type: "doc", dir: "./content/blogs", schema: z.object({ title: z.string(), description: z.string(), date: z.date(), author: z.object({ name: z.string(), avatar: z.string(), twitter: z.string().optional(), }), image: z.string(), tags: z.array(z.string()), }), }); const generator = createGenerator(); export default defineConfig({ mdxOptions: { remarkPlugins: [ [ remarkNpm, { persist: { id: "persist-install", }, }, ], [remarkAutoTypeTable, { generator }], ], }, }); ``` -------------------------------------------------------------------------------- /packages/better-auth/src/utils/merger.ts: -------------------------------------------------------------------------------- ```typescript import { clone } from "./clone"; const mergeObjects = (target: any, source: any): any => { for (const key in source) { if (!source.hasOwnProperty(key)) continue; if (key === "constructor" || key === "prototype" || key === "__proto__") continue; const value = source[key]; if (isPrimitive(value)) { if (value !== undefined || !(key in target)) { target[key] = value; } } else if (!target[key] || isArray(value)) { target[key] = clone(value); } else { target[key] = mergeObjects(target[key], value); } } return target; }; const isArray = (value: unknown): value is unknown[] => { return Array.isArray(value); }; const isPrimitive = ( value: unknown, ): value is bigint | symbol | string | number | boolean | null | undefined => { if (value === null) return true; const type = typeof value; return type !== "object" && type !== "function"; }; export const merge = (objects: object[]): object => { const target = clone(objects[0]!); for (let i = 1, l = objects.length; i < l; i++) { mergeObjects(target, objects[i]!); } return target; }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/organization/access/statement.ts: -------------------------------------------------------------------------------- ```typescript import { createAccessControl } from "../../access"; export const defaultStatements = { organization: ["update", "delete"], member: ["create", "update", "delete"], invitation: ["create", "cancel"], team: ["create", "update", "delete"], ac: ["create", "read", "update", "delete"], } as const; export const defaultAc = createAccessControl(defaultStatements); export const adminAc = defaultAc.newRole({ organization: ["update"], invitation: ["create", "cancel"], member: ["create", "update", "delete"], team: ["create", "update", "delete"], ac: ["create", "read", "update", "delete"], }); export const ownerAc = defaultAc.newRole({ organization: ["update", "delete"], member: ["create", "update", "delete"], invitation: ["create", "cancel"], team: ["create", "update", "delete"], ac: ["create", "read", "update", "delete"], }); export const memberAc = defaultAc.newRole({ organization: [], member: [], invitation: [], team: [], ac: ["read"], // Allow members to see all roles for their org. }); export const defaultRoles = { admin: adminAc, owner: ownerAc, member: memberAc, }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/adapters/tests/number-id.ts: -------------------------------------------------------------------------------- ```typescript import { expect } from "vitest"; import { createTestSuite } from "../create-test-suite"; import type { User } from "better-auth/types"; import { getNormalTestSuiteTests } from "./normal"; export const numberIdTestSuite = createTestSuite( "number-id", { defaultBetterAuthOptions: { advanced: { database: { useNumberId: true, }, }, }, alwaysMigrate: true, prefixTests: "number-id", }, (helpers) => { const { "create - should use generateId if provided": _, ...normalTests } = getNormalTestSuiteTests({ ...helpers }); return { "init - tests": async () => { const opts = helpers.getBetterAuthOptions(); expect(opts.advanced?.database?.useNumberId).toBe(true); }, "create - should return a number id": async () => { const user = await helpers.generate("user"); const res = await helpers.adapter.create<User>({ model: "user", data: user, forceAllowId: true, }); expect(res).toHaveProperty("id"); expect(typeof res.id).toBe("string"); expect(parseInt(res.id)).toBeGreaterThan(0); }, ...normalTests, }; }, ); ``` -------------------------------------------------------------------------------- /docs/components/docs/ui/popover.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as PopoverPrimitive from "@radix-ui/react-popover"; import * as React from "react"; import { cn } from "../../../lib/utils"; const Popover = PopoverPrimitive.Root; const PopoverTrigger = PopoverPrimitive.Trigger; const PopoverContent = React.forwardRef< React.ComponentRef<typeof PopoverPrimitive.Content>, React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( <PopoverPrimitive.Portal> <PopoverPrimitive.Content ref={ref} align={align} sideOffset={sideOffset} side="bottom" className={cn( "z-50 min-w-[220px] max-w-[98vw] rounded-lg border bg-fd-popover p-2 text-sm text-fd-popover-foreground shadow-lg focus-visible:outline-none data-[state=closed]:animate-fd-popover-out data-[state=open]:animate-fd-popover-in", className, )} {...props} /> </PopoverPrimitive.Portal> )); PopoverContent.displayName = PopoverPrimitive.Content.displayName; const PopoverClose = PopoverPrimitive.PopoverClose; export { Popover, PopoverTrigger, PopoverContent, PopoverClose }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/crypto/password.ts: -------------------------------------------------------------------------------- ```typescript import { constantTimeEqual } from "./buffer"; import { scryptAsync } from "@noble/hashes/scrypt.js"; import { hex } from "@better-auth/utils/hex"; import { hexToBytes } from "@noble/hashes/utils.js"; import { BetterAuthError } from "@better-auth/core/error"; const config = { N: 16384, r: 16, p: 1, dkLen: 64, }; async function generateKey(password: string, salt: string) { return await scryptAsync(password.normalize("NFKC"), salt, { N: config.N, p: config.p, r: config.r, dkLen: config.dkLen, maxmem: 128 * config.N * config.r * 2, }); } export const hashPassword = async (password: string) => { const salt = hex.encode(crypto.getRandomValues(new Uint8Array(16))); const key = await generateKey(password, salt); return `${salt}:${hex.encode(key)}`; }; export const verifyPassword = async ({ hash, password, }: { hash: string; password: string; }) => { const [salt, key] = hash.split(":"); if (!salt || !key) { throw new BetterAuthError("Invalid password hash"); } const targetKey = await generateKey(password, salt!); return constantTimeEqual(targetKey, hexToBytes(key)); }; ``` -------------------------------------------------------------------------------- /.github/workflows/preview.yml: -------------------------------------------------------------------------------- ```yaml name: Publish Any Commit on: [pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 - name: Cache turbo build setup uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: .turbo key: ${{ runner.os }}-turbo-${{ github.sha }} restore-keys: | ${{ runner.os }}-turbo- - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 22.x registry-url: 'https://registry.npmjs.org' cache: pnpm - name: Install run: pnpm install - name: Build env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM || github.repository_owner }} TURBO_CACHE: remote:rw run: pnpm build - run: pnpm dlx pkg-pr-new publish --pnpm ./packages/* ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/toggle.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as TogglePrimitive from "@radix-ui/react-toggle"; import { cva } from "class-variance-authority"; import { cn } from "@/lib/utils"; const toggleVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground", { variants: { variant: { default: "bg-transparent", outline: "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground", }, size: { default: "h-9 px-3", sm: "h-8 px-2", lg: "h-10 px-3", }, }, defaultVariants: { variant: "default", size: "default", }, }, ); const Toggle = ({ ref, className, variant, size, ...props }) => ( <TogglePrimitive.Root ref={ref} className={cn(toggleVariants({ variant, size, className }))} {...props} /> ); Toggle.displayName = TogglePrimitive.Root.displayName; export { Toggle, toggleVariants }; ``` -------------------------------------------------------------------------------- /demo/nextjs/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as React from "react"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import { CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils"; function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) { return ( <CheckboxPrimitive.Root data-slot="checkbox" className={cn( "peer border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", className, )} {...props} > <CheckboxPrimitive.Indicator data-slot="checkbox-indicator" className="flex items-center justify-center text-current transition-none" > <CheckIcon className="size-3.5" /> </CheckboxPrimitive.Indicator> </CheckboxPrimitive.Root> ); } export { Checkbox }; ``` -------------------------------------------------------------------------------- /docs/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- ```typescript "use client"; import * as React from "react"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import { CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils"; function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) { return ( <CheckboxPrimitive.Root data-slot="checkbox" className={cn( "peer border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", className, )} {...props} > <CheckboxPrimitive.Indicator data-slot="checkbox-indicator" className="flex items-center justify-center text-current transition-none" > <CheckIcon className="size-3.5" /> </CheckboxPrimitive.Indicator> </CheckboxPrimitive.Root> ); } export { Checkbox }; ``` -------------------------------------------------------------------------------- /packages/better-auth/src/plugins/device-authorization/schema.ts: -------------------------------------------------------------------------------- ```typescript import type { BetterAuthPluginDBSchema } from "@better-auth/core/db"; import * as z from "zod"; export const schema = { deviceCode: { fields: { deviceCode: { type: "string", required: true, }, userCode: { type: "string", required: true, }, userId: { type: "string", required: false, }, expiresAt: { type: "date", required: true, }, status: { type: "string", required: true, }, lastPolledAt: { type: "date", required: false, }, pollingInterval: { type: "number", required: false, }, clientId: { type: "string", required: false, }, scope: { type: "string", required: false, }, }, }, } satisfies BetterAuthPluginDBSchema; export const deviceCode = z.object({ id: z.string(), deviceCode: z.string(), userCode: z.string(), userId: z.string().optional(), expiresAt: z.date(), status: z.string(), lastPolledAt: z.date().optional(), pollingInterval: z.number().optional(), clientId: z.string().optional(), scope: z.string().optional(), }); export type DeviceCode = z.infer<typeof deviceCode>; ``` -------------------------------------------------------------------------------- /demo/nextjs/app/layout.tsx: -------------------------------------------------------------------------------- ```typescript import "./globals.css"; import { Toaster } from "@/components/ui/sonner"; import { ThemeProvider } from "@/components/theme-provider"; import { GeistMono } from "geist/font/mono"; import { GeistSans } from "geist/font/sans"; import { Wrapper, WrapperWithQuery } from "@/components/wrapper"; import { createMetadata } from "@/lib/metadata"; export const metadata = createMetadata({ title: { template: "%s | Better Auth", default: "Better Auth", }, description: "The most comprehensive authentication library for typescript", metadataBase: new URL("https://demo.better-auth.com"), }); export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en" suppressHydrationWarning> <head> <link rel="icon" href="/favicon/favicon.ico" sizes="any" /> </head> <body className={`${GeistSans.variable} ${GeistMono.variable} font-sans`}> <ThemeProvider attribute="class" defaultTheme="dark"> <Wrapper> <WrapperWithQuery>{children}</WrapperWithQuery> </Wrapper> <Toaster richColors closeButton /> </ThemeProvider> </body> </html> ); } ``` -------------------------------------------------------------------------------- /packages/better-auth/src/crypto/index.ts: -------------------------------------------------------------------------------- ```typescript import { createHash } from "@better-auth/utils/hash"; import { xchacha20poly1305 } from "@noble/ciphers/chacha.js"; import { bytesToHex, hexToBytes, utf8ToBytes, managedNonce, } from "@noble/ciphers/utils.js"; export type SymmetricEncryptOptions = { key: string; data: string; }; export const symmetricEncrypt = async ({ key, data, }: SymmetricEncryptOptions) => { const keyAsBytes = await createHash("SHA-256").digest(key); const dataAsBytes = utf8ToBytes(data); const chacha = managedNonce(xchacha20poly1305)(new Uint8Array(keyAsBytes)); return bytesToHex(chacha.encrypt(dataAsBytes)); }; export type SymmetricDecryptOptions = { key: string; data: string; }; export const symmetricDecrypt = async ({ key, data, }: SymmetricDecryptOptions) => { const keyAsBytes = await createHash("SHA-256").digest(key); const dataAsBytes = hexToBytes(data); const chacha = managedNonce(xchacha20poly1305)(new Uint8Array(keyAsBytes)); return new TextDecoder().decode(chacha.decrypt(dataAsBytes)); }; export * from "./buffer"; export * from "./hash"; export * from "./jwt"; export * from "./password"; export * from "./random"; ```