#
tokens: 49447/50000 20/1099 files (page 14/69)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 14 of 69. Use http://codebase.md/better-auth/better-auth?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitattributes
├── .github
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   ├── renovate.json5
│   └── workflows
│       ├── ci.yml
│       ├── e2e.yml
│       ├── preview.yml
│       └── release.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .vscode
│   └── settings.json
├── banner-dark.png
├── banner.png
├── biome.json
├── bump.config.ts
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── demo
│   ├── expo-example
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── app.config.ts
│   │   ├── assets
│   │   │   ├── bg-image.jpeg
│   │   │   ├── fonts
│   │   │   │   └── SpaceMono-Regular.ttf
│   │   │   ├── icon.png
│   │   │   └── images
│   │   │       ├── adaptive-icon.png
│   │   │       ├── favicon.png
│   │   │       ├── logo.png
│   │   │       ├── partial-react-logo.png
│   │   │       ├── react-logo.png
│   │   │       ├── [email protected]
│   │   │       ├── [email protected]
│   │   │       └── splash.png
│   │   ├── babel.config.js
│   │   ├── components.json
│   │   ├── expo-env.d.ts
│   │   ├── index.ts
│   │   ├── metro.config.js
│   │   ├── nativewind-env.d.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── app
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── api
│   │   │   │   │   └── auth
│   │   │   │   │       └── [...route]+api.ts
│   │   │   │   ├── dashboard.tsx
│   │   │   │   ├── forget-password.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   └── sign-up.tsx
│   │   │   ├── components
│   │   │   │   ├── icons
│   │   │   │   │   └── google.tsx
│   │   │   │   └── ui
│   │   │   │       ├── avatar.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       └── text.tsx
│   │   │   ├── global.css
│   │   │   └── lib
│   │   │       ├── auth-client.ts
│   │   │       ├── auth.ts
│   │   │       ├── icons
│   │   │       │   ├── iconWithClassName.ts
│   │   │       │   └── X.tsx
│   │   │       └── utils.ts
│   │   ├── tailwind.config.js
│   │   └── tsconfig.json
│   └── nextjs
│       ├── .env.example
│       ├── .gitignore
│       ├── app
│       │   ├── (auth)
│       │   │   ├── forget-password
│       │   │   │   └── page.tsx
│       │   │   ├── reset-password
│       │   │   │   └── page.tsx
│       │   │   ├── sign-in
│       │   │   │   ├── loading.tsx
│       │   │   │   └── page.tsx
│       │   │   └── two-factor
│       │   │       ├── otp
│       │   │       │   └── page.tsx
│       │   │       └── page.tsx
│       │   ├── accept-invitation
│       │   │   └── [id]
│       │   │       ├── invitation-error.tsx
│       │   │       └── page.tsx
│       │   ├── admin
│       │   │   └── page.tsx
│       │   ├── api
│       │   │   └── auth
│       │   │       └── [...all]
│       │   │           └── route.ts
│       │   ├── apps
│       │   │   └── register
│       │   │       └── page.tsx
│       │   ├── client-test
│       │   │   └── page.tsx
│       │   ├── dashboard
│       │   │   ├── change-plan.tsx
│       │   │   ├── client.tsx
│       │   │   ├── organization-card.tsx
│       │   │   ├── page.tsx
│       │   │   ├── upgrade-button.tsx
│       │   │   └── user-card.tsx
│       │   ├── device
│       │   │   ├── approve
│       │   │   │   └── page.tsx
│       │   │   ├── denied
│       │   │   │   └── page.tsx
│       │   │   ├── layout.tsx
│       │   │   ├── page.tsx
│       │   │   └── success
│       │   │       └── page.tsx
│       │   ├── favicon.ico
│       │   ├── features.tsx
│       │   ├── fonts
│       │   │   ├── GeistMonoVF.woff
│       │   │   └── GeistVF.woff
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   ├── oauth
│       │   │   └── authorize
│       │   │       ├── concet-buttons.tsx
│       │   │       └── page.tsx
│       │   ├── page.tsx
│       │   └── pricing
│       │       └── page.tsx
│       ├── components
│       │   ├── account-switch.tsx
│       │   ├── blocks
│       │   │   └── pricing.tsx
│       │   ├── logo.tsx
│       │   ├── one-tap.tsx
│       │   ├── sign-in-btn.tsx
│       │   ├── sign-in.tsx
│       │   ├── sign-up.tsx
│       │   ├── theme-provider.tsx
│       │   ├── theme-toggle.tsx
│       │   ├── tier-labels.tsx
│       │   ├── ui
│       │   │   ├── accordion.tsx
│       │   │   ├── alert-dialog.tsx
│       │   │   ├── alert.tsx
│       │   │   ├── aspect-ratio.tsx
│       │   │   ├── avatar.tsx
│       │   │   ├── badge.tsx
│       │   │   ├── breadcrumb.tsx
│       │   │   ├── button.tsx
│       │   │   ├── calendar.tsx
│       │   │   ├── card.tsx
│       │   │   ├── carousel.tsx
│       │   │   ├── chart.tsx
│       │   │   ├── checkbox.tsx
│       │   │   ├── collapsible.tsx
│       │   │   ├── command.tsx
│       │   │   ├── context-menu.tsx
│       │   │   ├── copy-button.tsx
│       │   │   ├── dialog.tsx
│       │   │   ├── drawer.tsx
│       │   │   ├── dropdown-menu.tsx
│       │   │   ├── form.tsx
│       │   │   ├── hover-card.tsx
│       │   │   ├── input-otp.tsx
│       │   │   ├── input.tsx
│       │   │   ├── label.tsx
│       │   │   ├── menubar.tsx
│       │   │   ├── navigation-menu.tsx
│       │   │   ├── pagination.tsx
│       │   │   ├── password-input.tsx
│       │   │   ├── popover.tsx
│       │   │   ├── progress.tsx
│       │   │   ├── radio-group.tsx
│       │   │   ├── resizable.tsx
│       │   │   ├── scroll-area.tsx
│       │   │   ├── select.tsx
│       │   │   ├── separator.tsx
│       │   │   ├── sheet.tsx
│       │   │   ├── skeleton.tsx
│       │   │   ├── slider.tsx
│       │   │   ├── sonner.tsx
│       │   │   ├── switch.tsx
│       │   │   ├── table.tsx
│       │   │   ├── tabs.tsx
│       │   │   ├── tabs2.tsx
│       │   │   ├── textarea.tsx
│       │   │   ├── toast.tsx
│       │   │   ├── toaster.tsx
│       │   │   ├── toggle-group.tsx
│       │   │   ├── toggle.tsx
│       │   │   └── tooltip.tsx
│       │   └── wrapper.tsx
│       ├── components.json
│       ├── hooks
│       │   └── use-toast.ts
│       ├── lib
│       │   ├── auth-client.ts
│       │   ├── auth-types.ts
│       │   ├── auth.ts
│       │   ├── email
│       │   │   ├── invitation.tsx
│       │   │   ├── resend.ts
│       │   │   └── reset-password.tsx
│       │   ├── metadata.ts
│       │   ├── shared.ts
│       │   └── utils.ts
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.mjs
│       ├── proxy.ts
│       ├── 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-decelration
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── demo.ts
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-exact-optional-property-types
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── index.ts
│       │   │   │   │   ├── organization.ts
│       │   │   │   │   ├── user-additional-fields.ts
│       │   │   │   │   └── username.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-isolated-module-bundler
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── index.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
│   │   │   │   ├── 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
│   │   └── vitest.setup.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
│   │   │   ├── api
│   │   │   │   └── index.ts
│   │   │   ├── async_hooks
│   │   │   │   └── index.ts
│   │   │   ├── context
│   │   │   │   ├── endpoint-context.ts
│   │   │   │   ├── 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
│   │   │   ├── oauth2
│   │   │   │   ├── client-credentials-token.ts
│   │   │   │   ├── create-authorization-url.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── oauth-provider.ts
│   │   │   │   ├── refresh-access-token.ts
│   │   │   │   ├── utils.ts
│   │   │   │   └── validate-authorization-code.ts
│   │   │   ├── social-providers
│   │   │   │   ├── apple.ts
│   │   │   │   ├── atlassian.ts
│   │   │   │   ├── cognito.ts
│   │   │   │   ├── discord.ts
│   │   │   │   ├── dropbox.ts
│   │   │   │   ├── facebook.ts
│   │   │   │   ├── figma.ts
│   │   │   │   ├── github.ts
│   │   │   │   ├── gitlab.ts
│   │   │   │   ├── google.ts
│   │   │   │   ├── huggingface.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kakao.ts
│   │   │   │   ├── kick.ts
│   │   │   │   ├── line.ts
│   │   │   │   ├── linear.ts
│   │   │   │   ├── linkedin.ts
│   │   │   │   ├── microsoft-entra-id.ts
│   │   │   │   ├── naver.ts
│   │   │   │   ├── notion.ts
│   │   │   │   ├── paypal.ts
│   │   │   │   ├── reddit.ts
│   │   │   │   ├── roblox.ts
│   │   │   │   ├── salesforce.ts
│   │   │   │   ├── slack.ts
│   │   │   │   ├── spotify.ts
│   │   │   │   ├── tiktok.ts
│   │   │   │   ├── twitch.ts
│   │   │   │   ├── twitter.ts
│   │   │   │   ├── vk.ts
│   │   │   │   └── zoom.ts
│   │   │   ├── types
│   │   │   │   ├── context.ts
│   │   │   │   ├── cookie.ts
│   │   │   │   ├── helper.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── init-options.ts
│   │   │   │   ├── plugin-client.ts
│   │   │   │   └── plugin.ts
│   │   │   └── utils
│   │   │       ├── error-codes.ts
│   │   │       └── index.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── expo
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── expo.test.ts
│   │   │   └── index.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── sso
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── index.ts
│   │   │   ├── oidc.test.ts
│   │   │   └── saml.test.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── stripe
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── hooks.ts
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   ├── stripe.test.ts
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   └── vitest.config.ts
│   └── telemetry
│       ├── package.json
│       ├── src
│       │   ├── detectors
│       │   │   ├── detect-auth-config.ts
│       │   │   ├── detect-database.ts
│       │   │   ├── detect-framework.ts
│       │   │   ├── detect-project-info.ts
│       │   │   ├── detect-runtime.ts
│       │   │   └── detect-system-info.ts
│       │   ├── index.ts
│       │   ├── project-id.ts
│       │   ├── telemetry.test.ts
│       │   ├── types.ts
│       │   └── utils
│       │       ├── hash.ts
│       │       ├── id.ts
│       │       ├── import-util.ts
│       │       └── package-json.ts
│       ├── tsconfig.json
│       └── tsdown.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── SECURITY.md
├── tsconfig.json
└── turbo.json
```

# Files

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/api-key/schema.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { BetterAuthPluginDBSchema } from "@better-auth/core/db";
  2 | import parseJSON from "../../client/parser";
  3 | 
  4 | export const apiKeySchema = ({
  5 | 	timeWindow,
  6 | 	rateLimitMax,
  7 | }: {
  8 | 	timeWindow: number;
  9 | 	rateLimitMax: number;
 10 | }) =>
 11 | 	({
 12 | 		apikey: {
 13 | 			fields: {
 14 | 				/**
 15 | 				 * The name of the key.
 16 | 				 */
 17 | 				name: {
 18 | 					type: "string",
 19 | 					required: false,
 20 | 					input: false,
 21 | 				},
 22 | 				/**
 23 | 				 * Shows the first few characters of the API key
 24 | 				 * This allows you to show those few characters in the UI to make it easier for users to identify the API key.
 25 | 				 */
 26 | 				start: {
 27 | 					type: "string",
 28 | 					required: false,
 29 | 					input: false,
 30 | 				},
 31 | 				/**
 32 | 				 * The prefix of the key.
 33 | 				 */
 34 | 				prefix: {
 35 | 					type: "string",
 36 | 					required: false,
 37 | 					input: false,
 38 | 				},
 39 | 				/**
 40 | 				 * The hashed key value.
 41 | 				 */
 42 | 				key: {
 43 | 					type: "string",
 44 | 					required: true,
 45 | 					input: false,
 46 | 				},
 47 | 				/**
 48 | 				 * The user id of the user who created the key.
 49 | 				 */
 50 | 				userId: {
 51 | 					type: "string",
 52 | 					references: { model: "user", field: "id", onDelete: "cascade" },
 53 | 					required: true,
 54 | 					input: false,
 55 | 				},
 56 | 				/**
 57 | 				 * The interval to refill the key in milliseconds.
 58 | 				 */
 59 | 				refillInterval: {
 60 | 					type: "number",
 61 | 					required: false,
 62 | 					input: false,
 63 | 				},
 64 | 				/**
 65 | 				 * The amount to refill the remaining count of the key.
 66 | 				 */
 67 | 				refillAmount: {
 68 | 					type: "number",
 69 | 					required: false,
 70 | 					input: false,
 71 | 				},
 72 | 				/**
 73 | 				 * The date and time when the key was last refilled.
 74 | 				 */
 75 | 				lastRefillAt: {
 76 | 					type: "date",
 77 | 					required: false,
 78 | 					input: false,
 79 | 				},
 80 | 				/**
 81 | 				 * Whether the key is enabled.
 82 | 				 */
 83 | 				enabled: {
 84 | 					type: "boolean",
 85 | 					required: false,
 86 | 					input: false,
 87 | 					defaultValue: true,
 88 | 				},
 89 | 				/**
 90 | 				 * Whether the key has rate limiting enabled.
 91 | 				 */
 92 | 				rateLimitEnabled: {
 93 | 					type: "boolean",
 94 | 					required: false,
 95 | 					input: false,
 96 | 					defaultValue: true,
 97 | 				},
 98 | 				/**
 99 | 				 * The time window in milliseconds for the rate limit.
100 | 				 */
101 | 				rateLimitTimeWindow: {
102 | 					type: "number",
103 | 					required: false,
104 | 					input: false,
105 | 					defaultValue: timeWindow,
106 | 				},
107 | 				/**
108 | 				 * The maximum number of requests allowed within the `rateLimitTimeWindow`.
109 | 				 */
110 | 				rateLimitMax: {
111 | 					type: "number",
112 | 					required: false,
113 | 					input: false,
114 | 					defaultValue: rateLimitMax,
115 | 				},
116 | 				/**
117 | 				 * The number of requests made within the rate limit time window
118 | 				 */
119 | 				requestCount: {
120 | 					type: "number",
121 | 					required: false,
122 | 					input: false,
123 | 					defaultValue: 0,
124 | 				},
125 | 				/**
126 | 				 * The remaining number of requests before the key is revoked.
127 | 				 *
128 | 				 * If this is null, then the key is not revoked.
129 | 				 *
130 | 				 * If `refillInterval` & `refillAmount` are provided, than this will refill accordingly.
131 | 				 */
132 | 				remaining: {
133 | 					type: "number",
134 | 					required: false,
135 | 					input: false,
136 | 				},
137 | 				/**
138 | 				 * The date and time of the last request made to the key.
139 | 				 */
140 | 				lastRequest: {
141 | 					type: "date",
142 | 					required: false,
143 | 					input: false,
144 | 				},
145 | 				/**
146 | 				 * The date and time when the key will expire.
147 | 				 */
148 | 				expiresAt: {
149 | 					type: "date",
150 | 					required: false,
151 | 					input: false,
152 | 				},
153 | 				/**
154 | 				 * The date and time when the key was created.
155 | 				 */
156 | 				createdAt: {
157 | 					type: "date",
158 | 					required: true,
159 | 					input: false,
160 | 				},
161 | 				/**
162 | 				 * The date and time when the key was last updated.
163 | 				 */
164 | 				updatedAt: {
165 | 					type: "date",
166 | 					required: true,
167 | 					input: false,
168 | 				},
169 | 				/**
170 | 				 * The permissions of the key.
171 | 				 */
172 | 				permissions: {
173 | 					type: "string",
174 | 					required: false,
175 | 					input: false,
176 | 				},
177 | 				/**
178 | 				 * Any additional metadata you want to store with the key.
179 | 				 */
180 | 				metadata: {
181 | 					type: "string",
182 | 					required: false,
183 | 					input: true,
184 | 					transform: {
185 | 						input(value) {
186 | 							return JSON.stringify(value);
187 | 						},
188 | 						output(value) {
189 | 							if (!value) return null;
190 | 							return parseJSON<any>(value as string);
191 | 						},
192 | 					},
193 | 				},
194 | 			},
195 | 		},
196 | 	}) satisfies BetterAuthPluginDBSchema;
197 | 
```

--------------------------------------------------------------------------------
/docs/app/changelogs/_components/stat-field.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import { useEffect, useId, useRef } from "react";
  4 | import clsx from "clsx";
  5 | import { animate, Segment } from "motion/react";
  6 | 
  7 | type Star = [x: number, y: number, dim?: boolean, blur?: boolean];
  8 | 
  9 | const stars: Array<Star> = [
 10 | 	[4, 4, true, true],
 11 | 	[4, 44, true],
 12 | 	[36, 22],
 13 | 	[50, 146, true, true],
 14 | 	[64, 43, true, true],
 15 | 	[76, 30, true],
 16 | 	[101, 116],
 17 | 	[140, 36, true],
 18 | 	[149, 134],
 19 | 	[162, 74, true],
 20 | 	[171, 96, true, true],
 21 | 	[210, 56, true, true],
 22 | 	[235, 90],
 23 | 	[275, 82, true, true],
 24 | 	[306, 6],
 25 | 	[307, 64, true, true],
 26 | 	[380, 68, true],
 27 | 	[380, 108, true, true],
 28 | 	[391, 148, true, true],
 29 | 	[405, 18, true],
 30 | 	[412, 86, true, true],
 31 | 	[426, 210, true, true],
 32 | 	[427, 56, true, true],
 33 | 	[538, 138],
 34 | 	[563, 88, true, true],
 35 | 	[611, 154, true, true],
 36 | 	[637, 150],
 37 | 	[651, 146, true],
 38 | 	[682, 70, true, true],
 39 | 	[683, 128],
 40 | 	[781, 82, true, true],
 41 | 	[785, 158, true],
 42 | 	[832, 146, true, true],
 43 | 	[852, 89],
 44 | ];
 45 | 
 46 | const constellations: Array<Array<Star>> = [
 47 | 	[
 48 | 		[247, 103],
 49 | 		[261, 86],
 50 | 		[307, 104],
 51 | 		[357, 36],
 52 | 	],
 53 | 	[
 54 | 		[586, 120],
 55 | 		[516, 100],
 56 | 		[491, 62],
 57 | 		[440, 107],
 58 | 		[477, 180],
 59 | 		[516, 100],
 60 | 	],
 61 | 	[
 62 | 		[733, 100],
 63 | 		[803, 120],
 64 | 		[879, 113],
 65 | 		[823, 164],
 66 | 		[803, 120],
 67 | 	],
 68 | ];
 69 | 
 70 | function Star({
 71 | 	blurId,
 72 | 	point: [cx, cy, dim, blur],
 73 | }: {
 74 | 	blurId: string;
 75 | 	point: Star;
 76 | }) {
 77 | 	let groupRef = useRef<React.ElementRef<"g">>(null);
 78 | 	let ref = useRef<React.ElementRef<"circle">>(null);
 79 | 
 80 | 	useEffect(() => {
 81 | 		if (!groupRef.current || !ref.current) {
 82 | 			return;
 83 | 		}
 84 | 
 85 | 		let delay = Math.random() * 2;
 86 | 
 87 | 		let animations = [
 88 | 			animate(groupRef.current, { opacity: 1 }, { duration: 4, delay }),
 89 | 			animate(
 90 | 				ref.current,
 91 | 				{
 92 | 					opacity: dim ? [0.2, 0.5] : [1, 0.6],
 93 | 					scale: dim ? [1, 1.2] : [1.2, 1],
 94 | 				},
 95 | 				{
 96 | 					duration: 10,
 97 | 					delay,
 98 | 				},
 99 | 			),
100 | 		];
101 | 
102 | 		return () => {
103 | 			for (let animation of animations) {
104 | 				animation.cancel();
105 | 			}
106 | 		};
107 | 	}, [dim]);
108 | 
109 | 	return (
110 | 		<g ref={groupRef} className="opacity-0">
111 | 			<circle
112 | 				ref={ref}
113 | 				cx={cx}
114 | 				cy={cy}
115 | 				r={1}
116 | 				style={{
117 | 					transformOrigin: `${cx / 16}rem ${cy / 16}rem`,
118 | 					opacity: dim ? 0.2 : 1,
119 | 					transform: `scale(${dim ? 1 : 1.2})`,
120 | 				}}
121 | 				filter={blur ? `url(#${blurId})` : undefined}
122 | 			/>
123 | 		</g>
124 | 	);
125 | }
126 | 
127 | function Constellation({
128 | 	points,
129 | 	blurId,
130 | }: {
131 | 	points: Array<Star>;
132 | 	blurId: string;
133 | }) {
134 | 	let ref = useRef<React.ElementRef<"path">>(null);
135 | 	let uniquePoints = points.filter(
136 | 		(point, pointIndex) =>
137 | 			points.findIndex((p) => String(p) === String(point)) === pointIndex,
138 | 	);
139 | 	let isFilled = uniquePoints.length !== points.length;
140 | 
141 | 	useEffect(() => {
142 | 		if (!ref.current) {
143 | 			return;
144 | 		}
145 | 
146 | 		let sequence: Array<Segment> = [
147 | 			[
148 | 				ref.current,
149 | 				{ strokeDashoffset: 0, visibility: "visible" },
150 | 				{ duration: 5, delay: Math.random() * 3 + 2 },
151 | 			],
152 | 		];
153 | 
154 | 		if (isFilled) {
155 | 			sequence.push([
156 | 				ref.current,
157 | 				{ fill: "rgb(255 255 255 / 0.02)" },
158 | 				{ duration: 1 },
159 | 			]);
160 | 		}
161 | 
162 | 		let animation = animate(sequence);
163 | 
164 | 		return () => {
165 | 			animation.cancel();
166 | 		};
167 | 	}, [isFilled]);
168 | 
169 | 	return (
170 | 		<>
171 | 			<path
172 | 				ref={ref}
173 | 				stroke="white"
174 | 				strokeOpacity="0.2"
175 | 				strokeDasharray={1}
176 | 				strokeDashoffset={1}
177 | 				pathLength={1}
178 | 				fill="transparent"
179 | 				d={`M ${points.join("L")}`}
180 | 				className="invisible"
181 | 			/>
182 | 			{uniquePoints.map((point, pointIndex) => (
183 | 				<Star key={pointIndex} point={point} blurId={blurId} />
184 | 			))}
185 | 		</>
186 | 	);
187 | }
188 | 
189 | export function StarField({ className }: { className?: string }) {
190 | 	let blurId = useId();
191 | 
192 | 	return (
193 | 		<svg
194 | 			viewBox="0 0 881 211"
195 | 			fill="white"
196 | 			aria-hidden="true"
197 | 			className={clsx(
198 | 				"pointer-events-none absolute w-[55.0625rem] origin-top-right rotate-[30deg] overflow-visible opacity-70",
199 | 				className,
200 | 			)}
201 | 		>
202 | 			<defs>
203 | 				<filter id={blurId}>
204 | 					<feGaussianBlur in="SourceGraphic" stdDeviation=".5" />
205 | 				</filter>
206 | 			</defs>
207 | 			{constellations.map((points, constellationIndex) => (
208 | 				<Constellation
209 | 					key={constellationIndex}
210 | 					points={points}
211 | 					blurId={blurId}
212 | 				/>
213 | 			))}
214 | 			{stars.map((point, pointIndex) => (
215 | 				<Star key={pointIndex} point={point} blurId={blurId} />
216 | 			))}
217 | 		</svg>
218 | 	);
219 | }
220 | 
```

--------------------------------------------------------------------------------
/docs/app/blog/_components/stat-field.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import { useEffect, useId, useRef } from "react";
  4 | import clsx from "clsx";
  5 | import { animate, Segment } from "motion/react";
  6 | 
  7 | type Star = [x: number, y: number, dim?: boolean, blur?: boolean];
  8 | 
  9 | const stars: Array<Star> = [
 10 | 	[4, 4, true, true],
 11 | 	[4, 44, true],
 12 | 	[36, 22],
 13 | 	[50, 146, true, true],
 14 | 	[64, 43, true, true],
 15 | 	[76, 30, true],
 16 | 	[101, 116],
 17 | 	[140, 36, true],
 18 | 	[149, 134],
 19 | 	[162, 74, true],
 20 | 	[171, 96, true, true],
 21 | 	[210, 56, true, true],
 22 | 	[235, 90],
 23 | 	[275, 82, true, true],
 24 | 	[306, 6],
 25 | 	[307, 64, true, true],
 26 | 	[380, 68, true],
 27 | 	[380, 108, true, true],
 28 | 	[391, 148, true, true],
 29 | 	[405, 18, true],
 30 | 	[412, 86, true, true],
 31 | 	[426, 210, true, true],
 32 | 	[427, 56, true, true],
 33 | 	[538, 138],
 34 | 	[563, 88, true, true],
 35 | 	[611, 154, true, true],
 36 | 	[637, 150],
 37 | 	[651, 146, true],
 38 | 	[682, 70, true, true],
 39 | 	[683, 128],
 40 | 	[781, 82, true, true],
 41 | 	[785, 158, true],
 42 | 	[832, 146, true, true],
 43 | 	[852, 89],
 44 | ];
 45 | 
 46 | const constellations: Array<Array<Star>> = [
 47 | 	[
 48 | 		[247, 103],
 49 | 		[261, 86],
 50 | 		[307, 104],
 51 | 		[357, 36],
 52 | 	],
 53 | 	[
 54 | 		[586, 120],
 55 | 		[516, 100],
 56 | 		[491, 62],
 57 | 		[440, 107],
 58 | 		[477, 180],
 59 | 		[516, 100],
 60 | 	],
 61 | 	[
 62 | 		[733, 100],
 63 | 		[803, 120],
 64 | 		[879, 113],
 65 | 		[823, 164],
 66 | 		[803, 120],
 67 | 	],
 68 | ];
 69 | 
 70 | function Star({
 71 | 	blurId,
 72 | 	point: [cx, cy, dim, blur],
 73 | }: {
 74 | 	blurId: string;
 75 | 	point: Star;
 76 | }) {
 77 | 	let groupRef = useRef<React.ElementRef<"g">>(null);
 78 | 	let ref = useRef<React.ElementRef<"circle">>(null);
 79 | 
 80 | 	useEffect(() => {
 81 | 		if (!groupRef.current || !ref.current) {
 82 | 			return;
 83 | 		}
 84 | 
 85 | 		let delay = Math.random() * 2;
 86 | 
 87 | 		let animations = [
 88 | 			animate(groupRef.current, { opacity: 1 }, { duration: 4, delay }),
 89 | 			animate(
 90 | 				ref.current,
 91 | 				{
 92 | 					opacity: dim ? [0.2, 0.5] : [1, 0.6],
 93 | 					scale: dim ? [1, 1.2] : [1.2, 1],
 94 | 				},
 95 | 				{
 96 | 					duration: 10,
 97 | 					delay,
 98 | 				},
 99 | 			),
100 | 		];
101 | 
102 | 		return () => {
103 | 			for (let animation of animations) {
104 | 				animation.cancel();
105 | 			}
106 | 		};
107 | 	}, [dim]);
108 | 
109 | 	return (
110 | 		<g ref={groupRef} className="opacity-0">
111 | 			<circle
112 | 				ref={ref}
113 | 				cx={cx}
114 | 				cy={cy}
115 | 				r={1}
116 | 				style={{
117 | 					transformOrigin: `${cx / 16}rem ${cy / 16}rem`,
118 | 					opacity: dim ? 0.2 : 1,
119 | 					transform: `scale(${dim ? 1 : 1.2})`,
120 | 				}}
121 | 				filter={blur ? `url(#${blurId})` : undefined}
122 | 			/>
123 | 		</g>
124 | 	);
125 | }
126 | 
127 | function Constellation({
128 | 	points,
129 | 	blurId,
130 | }: {
131 | 	points: Array<Star>;
132 | 	blurId: string;
133 | }) {
134 | 	let ref = useRef<React.ElementRef<"path">>(null);
135 | 	let uniquePoints = points.filter(
136 | 		(point, pointIndex) =>
137 | 			points.findIndex((p) => String(p) === String(point)) === pointIndex,
138 | 	);
139 | 	let isFilled = uniquePoints.length !== points.length;
140 | 
141 | 	useEffect(() => {
142 | 		if (!ref.current) {
143 | 			return;
144 | 		}
145 | 
146 | 		let sequence: Array<Segment> = [
147 | 			[
148 | 				ref.current,
149 | 				{ strokeDashoffset: 0, visibility: "visible" },
150 | 				{ duration: 5, delay: Math.random() * 3 + 2 },
151 | 			],
152 | 		];
153 | 
154 | 		if (isFilled) {
155 | 			sequence.push([
156 | 				ref.current,
157 | 				{ fill: "rgb(255 255 255 / 0.02)" },
158 | 				{ duration: 1 },
159 | 			]);
160 | 		}
161 | 
162 | 		let animation = animate(sequence);
163 | 
164 | 		return () => {
165 | 			animation.cancel();
166 | 		};
167 | 	}, [isFilled]);
168 | 
169 | 	return (
170 | 		<>
171 | 			<path
172 | 				ref={ref}
173 | 				stroke="white"
174 | 				strokeOpacity="0.2"
175 | 				strokeDasharray={1}
176 | 				strokeDashoffset={1}
177 | 				pathLength={1}
178 | 				fill="transparent"
179 | 				d={`M ${points.join("L")}`}
180 | 				className="invisible"
181 | 			/>
182 | 			{uniquePoints.map((point, pointIndex) => (
183 | 				<Star key={pointIndex} point={point} blurId={blurId} />
184 | 			))}
185 | 		</>
186 | 	);
187 | }
188 | 
189 | export function StarField({ className }: { className?: string }) {
190 | 	let blurId = useId();
191 | 
192 | 	return (
193 | 		<svg
194 | 			viewBox="0 0 881 211"
195 | 			fill="white"
196 | 			aria-hidden="true"
197 | 			className={clsx(
198 | 				"pointer-events-none absolute w-[55.0625rem] max-w-[100vw] origin-top-right rotate-[30deg] overflow-visible opacity-70",
199 | 				className,
200 | 			)}
201 | 		>
202 | 			<defs>
203 | 				<filter id={blurId}>
204 | 					<feGaussianBlur in="SourceGraphic" stdDeviation=".5" />
205 | 				</filter>
206 | 			</defs>
207 | 			{constellations.map((points, constellationIndex) => (
208 | 				<Constellation
209 | 					key={constellationIndex}
210 | 					points={points}
211 | 					blurId={blurId}
212 | 				/>
213 | 			))}
214 | 			{stars.map((point, pointIndex) => (
215 | 				<Star key={pointIndex} point={point} blurId={blurId} />
216 | 			))}
217 | 		</svg>
218 | 	);
219 | }
220 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/client/parser.ts:
--------------------------------------------------------------------------------

```typescript
  1 | const PROTO_POLLUTION_PATTERNS = {
  2 | 	proto:
  3 | 		/"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/,
  4 | 	constructor:
  5 | 		/"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/,
  6 | 	protoShort: /"__proto__"\s*:/,
  7 | 	constructorShort: /"constructor"\s*:/,
  8 | } as const;
  9 | 
 10 | const JSON_SIGNATURE =
 11 | 	/^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?([Ee][+-]?\d+)?\s*$/;
 12 | 
 13 | const SPECIAL_VALUES = {
 14 | 	true: true,
 15 | 	false: false,
 16 | 	null: null,
 17 | 	undefined: undefined,
 18 | 	nan: Number.NaN,
 19 | 	infinity: Number.POSITIVE_INFINITY,
 20 | 	"-infinity": Number.NEGATIVE_INFINITY,
 21 | } as const;
 22 | 
 23 | const ISO_DATE_REGEX =
 24 | 	/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,7}))?(?:Z|([+-])(\d{2}):(\d{2}))$/;
 25 | 
 26 | type ParseOptions = {
 27 | 	/** Throw errors instead of returning the original value */
 28 | 	strict?: boolean;
 29 | 	/** Log warnings when suspicious patterns are detected */
 30 | 	warnings?: boolean;
 31 | 	/** Custom reviver function */
 32 | 	reviver?: (key: string, value: any) => any;
 33 | 	/** Automatically convert ISO date strings to Date objects */
 34 | 	parseDates?: boolean;
 35 | };
 36 | 
 37 | function isValidDate(date: Date): boolean {
 38 | 	return date instanceof Date && !isNaN(date.getTime());
 39 | }
 40 | 
 41 | function parseISODate(value: string): Date | null {
 42 | 	const match = ISO_DATE_REGEX.exec(value);
 43 | 	if (!match) return null;
 44 | 
 45 | 	const [
 46 | 		,
 47 | 		year,
 48 | 		month,
 49 | 		day,
 50 | 		hour,
 51 | 		minute,
 52 | 		second,
 53 | 		ms,
 54 | 		offsetSign,
 55 | 		offsetHour,
 56 | 		offsetMinute,
 57 | 	] = match;
 58 | 
 59 | 	let date = new Date(
 60 | 		Date.UTC(
 61 | 			parseInt(year!, 10),
 62 | 			parseInt(month!, 10) - 1,
 63 | 			parseInt(day!, 10),
 64 | 			parseInt(hour!, 10),
 65 | 			parseInt(minute!, 10),
 66 | 			parseInt(second!, 10),
 67 | 			ms ? parseInt(ms.padEnd(3, "0"), 10) : 0,
 68 | 		),
 69 | 	);
 70 | 
 71 | 	if (offsetSign) {
 72 | 		const offset =
 73 | 			(parseInt(offsetHour!, 10) * 60 + parseInt(offsetMinute!, 10)) *
 74 | 			(offsetSign === "+" ? -1 : 1);
 75 | 		date.setUTCMinutes(date.getUTCMinutes() + offset);
 76 | 	}
 77 | 
 78 | 	return isValidDate(date) ? date : null;
 79 | }
 80 | 
 81 | function betterJSONParse<T = unknown>(
 82 | 	value: unknown,
 83 | 	options: ParseOptions = {},
 84 | ): T {
 85 | 	const {
 86 | 		strict = false,
 87 | 		warnings = false,
 88 | 		reviver,
 89 | 		parseDates = true,
 90 | 	} = options;
 91 | 
 92 | 	if (typeof value !== "string") {
 93 | 		return value as T;
 94 | 	}
 95 | 
 96 | 	const trimmed = value.trim();
 97 | 
 98 | 	if (
 99 | 		trimmed.length > 0 &&
100 | 		trimmed[0] === '"' &&
101 | 		trimmed.endsWith('"') &&
102 | 		!trimmed.slice(1, -1).includes('"')
103 | 	) {
104 | 		return trimmed.slice(1, -1) as T;
105 | 	}
106 | 
107 | 	const lowerValue = trimmed.toLowerCase();
108 | 	if (lowerValue.length <= 9 && lowerValue in SPECIAL_VALUES) {
109 | 		return SPECIAL_VALUES[lowerValue as keyof typeof SPECIAL_VALUES] as T;
110 | 	}
111 | 
112 | 	if (!JSON_SIGNATURE.test(trimmed)) {
113 | 		if (strict) {
114 | 			throw new SyntaxError("[better-json] Invalid JSON");
115 | 		}
116 | 		return value as T;
117 | 	}
118 | 
119 | 	const hasProtoPattern = Object.entries(PROTO_POLLUTION_PATTERNS).some(
120 | 		([key, pattern]) => {
121 | 			const matches = pattern.test(trimmed);
122 | 			if (matches && warnings) {
123 | 				console.warn(
124 | 					`[better-json] Detected potential prototype pollution attempt using ${key} pattern`,
125 | 				);
126 | 			}
127 | 			return matches;
128 | 		},
129 | 	);
130 | 
131 | 	if (hasProtoPattern && strict) {
132 | 		throw new Error(
133 | 			"[better-json] Potential prototype pollution attempt detected",
134 | 		);
135 | 	}
136 | 
137 | 	try {
138 | 		const secureReviver = (key: string, value: any) => {
139 | 			if (
140 | 				key === "__proto__" ||
141 | 				(key === "constructor" &&
142 | 					value &&
143 | 					typeof value === "object" &&
144 | 					"prototype" in value)
145 | 			) {
146 | 				if (warnings) {
147 | 					console.warn(
148 | 						`[better-json] Dropping "${key}" key to prevent prototype pollution`,
149 | 					);
150 | 				}
151 | 				return undefined;
152 | 			}
153 | 
154 | 			if (parseDates && typeof value === "string") {
155 | 				const date = parseISODate(value);
156 | 				if (date) {
157 | 					return date;
158 | 				}
159 | 			}
160 | 
161 | 			return reviver ? reviver(key, value) : value;
162 | 		};
163 | 
164 | 		return JSON.parse(trimmed, secureReviver);
165 | 	} catch (error) {
166 | 		if (strict) {
167 | 			throw error;
168 | 		}
169 | 		return value as T;
170 | 	}
171 | }
172 | 
173 | export function parseJSON<T = unknown>(
174 | 	value: unknown,
175 | 	options: ParseOptions = { strict: true },
176 | ): T {
177 | 	return betterJSONParse<T>(value, options);
178 | }
179 | 
180 | export default parseJSON;
181 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/adapters/tests/performance.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { assert, expect } from "vitest";
  2 | import { createTestSuite } from "../create-test-suite";
  3 | 
  4 | /**
  5 |  * This test suite tests the performance of the adapter and logs the results.
  6 |  */
  7 | export const performanceTestSuite = createTestSuite(
  8 | 	"performance",
  9 | 	{},
 10 | 	(
 11 | 		{ adapter, generate, cleanup },
 12 | 		config?: { iterations?: number; userSeedCount?: number; dialect?: string },
 13 | 	) => {
 14 | 		const tests = {
 15 | 			create: [] as number[],
 16 | 			update: [] as number[],
 17 | 			delete: [] as number[],
 18 | 			count: [] as number[],
 19 | 			findOne: [] as number[],
 20 | 			findMany: [] as number[],
 21 | 		};
 22 | 
 23 | 		const iterations = config?.iterations ?? 10;
 24 | 		const userSeedCount = config?.userSeedCount ?? 15;
 25 | 
 26 | 		assert(
 27 | 			userSeedCount >= iterations,
 28 | 			"userSeedCount must be greater than iterations",
 29 | 		);
 30 | 
 31 | 		const seedUser = async () => {
 32 | 			const user = await generate("user");
 33 | 			return await adapter.create({
 34 | 				model: "user",
 35 | 				data: user,
 36 | 				forceAllowId: true,
 37 | 			});
 38 | 		};
 39 | 		const seedManyUsers = async () => {
 40 | 			const users = [];
 41 | 			for (let i = 0; i < userSeedCount; i++) {
 42 | 				users.push(await seedUser());
 43 | 			}
 44 | 			return users;
 45 | 		};
 46 | 
 47 | 		const performanceTests = {
 48 | 			create: async () => {
 49 | 				for (let i = 0; i < iterations; i++) {
 50 | 					const start = performance.now();
 51 | 					await seedUser();
 52 | 					const end = performance.now();
 53 | 					tests.create.push(end - start);
 54 | 				}
 55 | 			},
 56 | 			update: async () => {
 57 | 				const users = await seedManyUsers();
 58 | 				for (let i = 0; i < iterations; i++) {
 59 | 					const start = performance.now();
 60 | 					await adapter.update({
 61 | 						model: "user",
 62 | 						where: [{ field: "id", value: users[i]!.id }],
 63 | 						update: {
 64 | 							name: `user-${i}`,
 65 | 						},
 66 | 					});
 67 | 					const end = performance.now();
 68 | 					tests.update.push(end - start);
 69 | 				}
 70 | 			},
 71 | 			delete: async () => {
 72 | 				const users = await seedManyUsers();
 73 | 				for (let i = 0; i < iterations; i++) {
 74 | 					const start = performance.now();
 75 | 					await adapter.delete({
 76 | 						model: "user",
 77 | 						where: [{ field: "id", value: users[i]!.id }],
 78 | 					});
 79 | 					const end = performance.now();
 80 | 					tests.delete.push(end - start);
 81 | 				}
 82 | 			},
 83 | 			count: async () => {
 84 | 				const users = await seedManyUsers();
 85 | 				for (let i = 0; i < iterations; i++) {
 86 | 					const start = performance.now();
 87 | 					const c = await adapter.count({
 88 | 						model: "user",
 89 | 					});
 90 | 					const end = performance.now();
 91 | 					tests.count.push(end - start);
 92 | 					expect(c).toEqual(users.length);
 93 | 				}
 94 | 			},
 95 | 			findOne: async () => {
 96 | 				const users = await seedManyUsers();
 97 | 				for (let i = 0; i < iterations; i++) {
 98 | 					const start = performance.now();
 99 | 					await adapter.findOne({
100 | 						model: "user",
101 | 						where: [{ field: "id", value: users[i]!.id }],
102 | 					});
103 | 					const end = performance.now();
104 | 					tests.findOne.push(end - start);
105 | 				}
106 | 			},
107 | 			findMany: async () => {
108 | 				const users = await seedManyUsers();
109 | 				for (let i = 0; i < iterations; i++) {
110 | 					const start = performance.now();
111 | 					const result = await adapter.findMany({
112 | 						model: "user",
113 | 						where: [{ field: "name", value: "user", operator: "starts_with" }],
114 | 						limit: users.length,
115 | 					});
116 | 					const end = performance.now();
117 | 					tests.findMany.push(end - start);
118 | 					expect(result.length).toBe(users.length);
119 | 				}
120 | 			},
121 | 		};
122 | 
123 | 		return {
124 | 			"run performance test": async () => {
125 | 				for (const test of Object.keys(performanceTests)) {
126 | 					await performanceTests[test as keyof typeof performanceTests]();
127 | 					await cleanup();
128 | 				}
129 | 
130 | 				// Calculate averages for each test
131 | 				const averages = Object.entries(tests).reduce(
132 | 					(acc, [key, values]) => {
133 | 						const average =
134 | 							values.length > 0
135 | 								? values.reduce((sum, val) => sum + val, 0) / values.length
136 | 								: 0;
137 | 						acc[key] = `${average.toFixed(3)}ms`;
138 | 						return acc;
139 | 					},
140 | 					{} as Record<string, string>,
141 | 				);
142 | 
143 | 				console.log(`Performance tests results, counting averages:`);
144 | 				console.table(averages);
145 | 				console.log({
146 | 					iterations,
147 | 					userSeedCount,
148 | 					adapter: adapter.options?.adapterConfig.adapterId,
149 | 					...(config?.dialect ? { dialect: config.dialect } : {}),
150 | 				});
151 | 				expect(1).toBe(1);
152 | 			},
153 | 		};
154 | 	},
155 | );
156 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/one-time-token/one-time-token.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it, vi } from "vitest";
  2 | import { getTestInstance } from "../../test-utils/test-instance";
  3 | import { oneTimeToken } from ".";
  4 | import { APIError } from "better-call";
  5 | import { oneTimeTokenClient } from "./client";
  6 | import { defaultKeyHasher } from "./utils";
  7 | 
  8 | describe("One-time token", async () => {
  9 | 	const { auth, signInWithTestUser, client } = await getTestInstance(
 10 | 		{
 11 | 			plugins: [oneTimeToken()],
 12 | 		},
 13 | 		{
 14 | 			clientOptions: {
 15 | 				plugins: [oneTimeTokenClient()],
 16 | 			},
 17 | 		},
 18 | 	);
 19 | 	it("should work", async () => {
 20 | 		const { headers } = await signInWithTestUser();
 21 | 		const response = await auth.api.generateOneTimeToken({
 22 | 			headers,
 23 | 		});
 24 | 		expect(response.token).toBeDefined();
 25 | 		const session = await auth.api.verifyOneTimeToken({
 26 | 			body: {
 27 | 				token: response.token,
 28 | 			},
 29 | 		});
 30 | 		expect(session).toBeDefined();
 31 | 		const shouldFail = await auth.api
 32 | 			.verifyOneTimeToken({
 33 | 				body: {
 34 | 					token: response.token,
 35 | 				},
 36 | 			})
 37 | 			.catch((e) => e);
 38 | 		expect(shouldFail).toBeInstanceOf(APIError);
 39 | 	});
 40 | 
 41 | 	it("should expire", async () => {
 42 | 		const { headers } = await signInWithTestUser();
 43 | 		const response = await auth.api.generateOneTimeToken({
 44 | 			headers,
 45 | 		});
 46 | 		vi.useFakeTimers();
 47 | 		await vi.advanceTimersByTimeAsync(5 * 60 * 1000);
 48 | 		const shouldFail = await auth.api
 49 | 			.verifyOneTimeToken({
 50 | 				body: {
 51 | 					token: response.token,
 52 | 				},
 53 | 			})
 54 | 			.catch((e) => e);
 55 | 		expect(shouldFail).toBeInstanceOf(APIError);
 56 | 		vi.useRealTimers();
 57 | 	});
 58 | 
 59 | 	it("should work with client", async () => {
 60 | 		const { headers } = await signInWithTestUser();
 61 | 		const response = await client.oneTimeToken.generate({
 62 | 			fetchOptions: {
 63 | 				headers,
 64 | 				throw: true,
 65 | 			},
 66 | 		});
 67 | 		expect(response.token).toBeDefined();
 68 | 		const session = await client.oneTimeToken.verify({
 69 | 			token: response.token,
 70 | 		});
 71 | 		expect(session.data?.session).toBeDefined();
 72 | 	});
 73 | 
 74 | 	describe("should work with different storeToken options", () => {
 75 | 		describe("hashed", async () => {
 76 | 			const { auth, signInWithTestUser, client } = await getTestInstance(
 77 | 				{
 78 | 					plugins: [
 79 | 						oneTimeToken({
 80 | 							storeToken: "hashed",
 81 | 							async generateToken(session, ctx) {
 82 | 								return "123456";
 83 | 							},
 84 | 						}),
 85 | 					],
 86 | 				},
 87 | 				{
 88 | 					clientOptions: {
 89 | 						plugins: [oneTimeTokenClient()],
 90 | 					},
 91 | 				},
 92 | 			);
 93 | 			const { internalAdapter } = await auth.$context;
 94 | 
 95 | 			it("should work with hashed", async () => {
 96 | 				const { headers } = await signInWithTestUser();
 97 | 				const response = await auth.api.generateOneTimeToken({
 98 | 					headers,
 99 | 				});
100 | 				expect(response.token).toBeDefined();
101 | 				expect(response.token).toBe("123456");
102 | 
103 | 				const hashedToken = await defaultKeyHasher(response.token);
104 | 				const storedToken = await internalAdapter.findVerificationValue(
105 | 					`one-time-token:${hashedToken}`,
106 | 				);
107 | 				expect(storedToken).toBeDefined();
108 | 
109 | 				const session = await auth.api.verifyOneTimeToken({
110 | 					body: {
111 | 						token: response.token,
112 | 					},
113 | 				});
114 | 				expect(session).toBeDefined();
115 | 				expect(session.user.email).toBeDefined();
116 | 			});
117 | 		});
118 | 
119 | 		describe("custom hasher", async () => {
120 | 			const { auth, signInWithTestUser, client } = await getTestInstance({
121 | 				plugins: [
122 | 					oneTimeToken({
123 | 						storeToken: {
124 | 							type: "custom-hasher",
125 | 							hash: async (token) => {
126 | 								return token + "hashed";
127 | 							},
128 | 						},
129 | 						async generateToken(session, ctx) {
130 | 							return "123456";
131 | 						},
132 | 					}),
133 | 				],
134 | 			});
135 | 			const { internalAdapter } = await auth.$context;
136 | 			it("should work with custom hasher", async () => {
137 | 				const { headers } = await signInWithTestUser();
138 | 				const response = await auth.api.generateOneTimeToken({
139 | 					headers,
140 | 				});
141 | 				expect(response.token).toBeDefined();
142 | 				expect(response.token).toBe("123456");
143 | 
144 | 				const hashedToken = response.token + "hashed";
145 | 				const storedToken = await internalAdapter.findVerificationValue(
146 | 					`one-time-token:${hashedToken}`,
147 | 				);
148 | 				expect(storedToken).toBeDefined();
149 | 
150 | 				const session = await auth.api.verifyOneTimeToken({
151 | 					body: {
152 | 						token: response.token,
153 | 					},
154 | 				});
155 | 				expect(session).toBeDefined();
156 | 			});
157 | 		});
158 | 	});
159 | });
160 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/integrations/nuxt.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Nuxt Integration
  3 | description: Integrate Better Auth with Nuxt.
  4 | ---
  5 | 
  6 | Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
  7 | 
  8 | ### Create API Route
  9 | 
 10 | We need to mount the handler to an API route. Create a file inside `/server/api/auth` called `[...all].ts` and add the following code:
 11 | 
 12 | ```ts title="server/api/auth/[...all].ts"
 13 | import { auth } from "~/lib/auth"; // import your auth config
 14 | 
 15 | export default defineEventHandler((event) => {
 16 | 	return auth.handler(toWebRequest(event));
 17 | });
 18 | ```
 19 | <Callout type="info">
 20 |  You can change the path on your better-auth configuration but it's recommended to keep it as `/api/auth/[...all]`
 21 | </Callout>
 22 | 
 23 | ### Migrate the database
 24 | 
 25 | Run the following command to create the necessary tables in your database:
 26 | 
 27 | ```bash
 28 | npx @better-auth/cli migrate
 29 | ```
 30 | 
 31 | ## Create a client
 32 | 
 33 | Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
 34 | 
 35 | ```ts title="auth-client.ts"
 36 | import { createAuthClient } from "better-auth/vue" // make sure to import from better-auth/vue
 37 | 
 38 | export const authClient = createAuthClient({
 39 |     //you can pass client configuration here
 40 | })
 41 | ```
 42 | 
 43 | Once you have created the client, you can use it to sign up, sign in, and perform other actions.
 44 | Some of the actions are reactive.
 45 | 
 46 | ### Example usage
 47 | 
 48 | ```vue title="index.vue"
 49 | <script setup lang="ts">
 50 | import { authClient } from "~/lib/client"
 51 | const session = authClient.useSession()
 52 | </script>
 53 | 
 54 | <template>
 55 |     <div>
 56 |         <button v-if="!session?.data" @click="() => authClient.signIn.social({
 57 |             provider: 'github'
 58 |         })">
 59 |             Continue with GitHub
 60 |         </button>
 61 |         <div>
 62 |             <pre>{{ session.data }}</pre>
 63 |             <button v-if="session.data" @click="authClient.signOut()">
 64 |                 Sign out
 65 |             </button>
 66 |         </div>
 67 |     </div>
 68 | </template>
 69 | ```
 70 | 
 71 | ### Server Usage
 72 | 
 73 | The `api` object exported from the auth instance contains all the actions that you can perform on the server. Every endpoint made inside Better Auth is a invocable as a function. Including plugins endpoints.
 74 | 
 75 | **Example: Getting Session on a server API route**
 76 | 
 77 | ```tsx title="server/api/example.ts"
 78 | import { auth } from "~/lib/auth";
 79 | 
 80 | export default defineEventHandler((event) => {
 81 |     const session = await auth.api.getSession({
 82 |       headers: event.headers
 83 |     });
 84 | 
 85 |    if(session) {
 86 |      // access the session.session && session.user
 87 |    }
 88 | });
 89 | ```
 90 | 
 91 | 
 92 | ### SSR Usage
 93 | 
 94 | If you are using Nuxt with SSR, you can use the `useSession` function in the `setup` function of your page component and pass `useFetch` to make it work with SSR.
 95 | 
 96 | ```vue title="index.vue"
 97 | <script setup lang="ts">
 98 | import { authClient } from "~/lib/auth-client";
 99 | 
100 | const { data: session } = await authClient.useSession(useFetch);
101 | </script>
102 | 
103 | <template>
104 |     <p>
105 |         {{ session }}
106 |     </p>
107 | </template>
108 | ```
109 | 
110 | 
111 | ### Middleware
112 | 
113 | To add middleware to your Nuxt project, you can use the `useSession` method from the client.
114 | 
115 | ```ts title="middleware/auth.global.ts"
116 | import { authClient } from "~/lib/auth-client";
117 | export default defineNuxtRouteMiddleware(async (to, from) => {
118 | 	const { data: session } = await authClient.useSession(useFetch); 
119 | 	if (!session.value) {
120 | 		if (to.path === "/dashboard") {
121 | 			return navigateTo("/");
122 | 		}
123 | 	}
124 | });
125 | ```
126 | 
127 | ### Resources & Examples
128 | 
129 | - [Nuxt and Nuxt Hub example](https://github.com/atinux/nuxthub-better-auth) on GitHub.
130 | - [NuxtZzle is Nuxt,Drizzle ORM example](https://github.com/leamsigc/nuxt-better-auth-drizzle) on GitHub [preview](https://nuxt-better-auth.giessen.dev/)
131 | - [Nuxt example](https://stackblitz.com/github/better-auth/examples/tree/main/nuxt-example) on StackBlitz.
132 | - [NuxSaaS (Github)](https://github.com/NuxSaaS/NuxSaaS) is a full-stack SaaS Starter Kit that leverages Better Auth for secure and efficient user authentication. [Demo](https://nuxsaas.com/)
133 | - [NuxtOne (Github)](https://github.com/nuxtone/nuxt-one) is a Nuxt-based starter template for building AIaaS (AI-as-a-Service) applications [preview](https://www.one.devv.zone)
134 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/concepts/cli.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: CLI
  3 | description: Built-in CLI for managing your project.
  4 | ---
  5 | 
  6 | Better Auth comes with a built-in CLI to help you manage the database schemas, initialize your project, generate a secret key for your application, and gather diagnostic information about your setup.
  7 | 
  8 | ## Generate
  9 | 
 10 | The `generate` command creates the schema required by Better Auth. If you're using a database adapter like Prisma or Drizzle, this command will generate the right schema for your ORM. If you're using the built-in Kysely adapter, it will generate an SQL file you can run directly on your database.
 11 | 
 12 | ```bash title="Terminal"
 13 | npx @better-auth/cli@latest generate
 14 | ```
 15 | 
 16 | ### Options
 17 | 
 18 | - `--output` - Where to save the generated schema. For Prisma, it will be saved in prisma/schema.prisma. For Drizzle, it goes to schema.ts in your project root. For Kysely, it's an SQL file saved as schema.sql in your project root.
 19 | - `--config` - The path to your Better Auth config file. By default, the CLI will search for an auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under the `src` directory.
 20 | - `--yes` - Skip the confirmation prompt and generate the schema directly.
 21 | 
 22 | 
 23 | ## Migrate
 24 | 
 25 | The migrate command applies the Better Auth schema directly to your database. This is available if you're using the built-in Kysely adapter. For other adapters, you'll need to apply the schema using your ORM's migration tool.
 26 | 
 27 | ```bash title="Terminal"
 28 | npx @better-auth/cli@latest migrate
 29 | ```
 30 | 
 31 | ### Options
 32 | 
 33 | - `--config` - The path to your Better Auth config file. By default, the CLI will search for an auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under the `src` directory.
 34 | - `--yes` - Skip the confirmation prompt and apply the schema directly.
 35 | 
 36 | ## Init
 37 | 
 38 | The `init` command allows you to initialize Better Auth in your project.
 39 | 
 40 | ```bash title="Terminal"
 41 | npx @better-auth/cli@latest init
 42 | ```
 43 | 
 44 | ### Options
 45 | 
 46 | - `--name` - The name of your application. (defaults to the `name` property in your `package.json`).
 47 | - `--framework` - The framework your codebase is using. Currently, the only supported framework is `Next.js`.
 48 | - `--plugins` - The plugins you want to use. You can specify multiple plugins by separating them with a comma.
 49 | - `--database` - The database you want to use. Currently, the only supported database is `SQLite`.
 50 | - `--package-manager` - The package manager you want to use. Currently, the only supported package managers are `npm`, `pnpm`, `yarn`, `bun` (defaults to the manager you used to initialize the CLI).
 51 | 
 52 | ## Info
 53 | 
 54 | The `info` command provides diagnostic information about your Better Auth setup and environment. Useful for debugging and sharing when seeking support.
 55 | 
 56 | ```bash title="Terminal"
 57 | npx @better-auth/cli@latest info
 58 | ```
 59 | 
 60 | ### Output
 61 | 
 62 | The command displays:
 63 | - **System**: OS, CPU, memory, Node.js version
 64 | - **Package Manager**: Detected manager and version
 65 | - **Better Auth**: Version and configuration (sensitive data auto-redacted)
 66 | - **Frameworks**: Detected frameworks (Next.js, React, Vue, etc.)
 67 | - **Databases**: Database clients and ORMs (Prisma, Drizzle, etc.)
 68 | 
 69 | ### Options
 70 | 
 71 | - `--config` - Path to your Better Auth config file
 72 | - `--json` - Output as JSON for sharing or programmatic use
 73 | 
 74 | ### Examples
 75 | 
 76 | ```bash
 77 | # Basic usage
 78 | npx @better-auth/cli@latest info
 79 | 
 80 | # Custom config path
 81 | npx @better-auth/cli@latest info --config ./config/auth.ts
 82 | 
 83 | # JSON output
 84 | npx @better-auth/cli@latest info --json > auth-info.json
 85 | ```
 86 | 
 87 | Sensitive data like secrets, API keys, and database URLs are automatically replaced with `[REDACTED]` for safe sharing.
 88 | 
 89 | ## Secret
 90 | 
 91 | The CLI also provides a way to generate a secret key for your Better Auth instance.
 92 | 
 93 | ```bash title="Terminal"
 94 | npx @better-auth/cli@latest secret
 95 | ```
 96 | 
 97 | ## Common Issues
 98 | 
 99 | **Error: Cannot find module X**
100 | 
101 | If you see this error, it means the CLI can't resolve imported modules in your Better Auth config file. We are working on a fix for many of these issues, but in the meantime, you can try the following:
102 | 
103 | - Remove any import aliases in your config file and use relative paths instead. After running the CLI, you can revert to using aliases.
```

--------------------------------------------------------------------------------
/docs/components/github-stat.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import { kFormatter } from "@/lib/utils";
 2 | 
 3 | export function GithubStat({ stars }: { stars: string | null }) {
 4 | 	let result = 0;
 5 | 	if (stars) {
 6 | 		result = parseInt(stars?.replace(/,/g, ""), 10);
 7 | 	} else {
 8 | 		return <></>;
 9 | 	}
10 | 
11 | 	return (
12 | 		<a
13 | 			href="https://github.com/better-auth/better-auth"
14 | 			className="flex mt-4 border border-input shadow-sm hover:bg-accent hover:text-accent-foreground rounded-none h-10 p-5 ml-auto z-50 overflow-hidden items-center text-sm font-medium focus-visible:outline-none  disabled:pointer-events-none disabled:opacity-50 bg-transparent dark:text-white text-black px-4 py-2 max-w-[14.8rem] whitespace-pre md:flex group relative w-full justify-center gap-2 transition-all duration-300 ease-out  hover:ring-black"
15 | 		>
16 | 			<span className="absolute right-0 -mt-12 h-32 w-8 translate-x-12 rotate-12 dark:bg-white/60 bg-black/60 opacity-10 transition-all duration-1000 ease-out group-hover:-translate-x-40"></span>
17 | 			<div className="flex items-center ml-2">
18 | 				<svg className="w-4 h-4 fill-current" viewBox="0 0 438.549 438.549">
19 | 					<path d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"></path>
20 | 				</svg>
21 | 				<span className="ml-2 text-black dark:text-white">Star on GitHub</span>
22 | 			</div>
23 | 			<div className="ml-2 flex items-center gap-2 text-sm md:flex">
24 | 				<svg
25 | 					className="w-4 h-4 text-gray-500 transition-all duration-300 group-hover:text-yellow-300"
26 | 					data-slot="icon"
27 | 					aria-hidden="true"
28 | 					fill="currentColor"
29 | 					viewBox="0 0 24 24"
30 | 					xmlns="http://www.w3.org/2000/svg"
31 | 				>
32 | 					<path
33 | 						clipRule="evenodd"
34 | 						d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.006 5.404.434c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.434 2.082-5.005Z"
35 | 						fillRule="evenodd"
36 | 					></path>
37 | 				</svg>
38 | 				<span className="inline-block tabular-nums tracking-wider font-mono font-medium text-black dark:text-white">
39 | 					{kFormatter(result)}
40 | 				</span>
41 | 			</div>
42 | 		</a>
43 | 	);
44 | }
45 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/jwt/utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { getWebcryptoSubtle } from "@better-auth/utils";
  2 | import { base64 } from "@better-auth/utils/base64";
  3 | import { joseSecs } from "../../utils/time";
  4 | import type { JwtOptions, Jwk } from "./types";
  5 | import { generateKeyPair, exportJWK } from "jose";
  6 | import { symmetricEncrypt } from "../../crypto";
  7 | import { getJwksAdapter } from "./adapter";
  8 | import type { GenericEndpointContext } from "@better-auth/core";
  9 | 
 10 | /**
 11 |  * Converts an expirationTime to ISO seconds expiration time (the format of JWT exp)
 12 |  *
 13 |  * See https://github.com/panva/jose/blob/main/src/lib/jwt_claims_set.ts#L245
 14 |  *
 15 |  * @param expirationTime - see options.jwt.expirationTime
 16 |  * @param iat - the iat time to consolidate on
 17 |  * @returns
 18 |  */
 19 | export function toExpJWT(
 20 | 	expirationTime: number | Date | string,
 21 | 	iat: number,
 22 | ): number {
 23 | 	if (typeof expirationTime === "number") {
 24 | 		return expirationTime;
 25 | 	} else if (expirationTime instanceof Date) {
 26 | 		return Math.floor(expirationTime.getTime() / 1000);
 27 | 	} else {
 28 | 		return iat + joseSecs(expirationTime);
 29 | 	}
 30 | }
 31 | 
 32 | async function deriveKey(secretKey: string): Promise<CryptoKey> {
 33 | 	const enc = new TextEncoder();
 34 | 	const subtle = getWebcryptoSubtle();
 35 | 	const keyMaterial = await subtle.importKey(
 36 | 		"raw",
 37 | 		enc.encode(secretKey),
 38 | 		{ name: "PBKDF2" },
 39 | 		false,
 40 | 		["deriveKey"],
 41 | 	);
 42 | 
 43 | 	return subtle.deriveKey(
 44 | 		{
 45 | 			name: "PBKDF2",
 46 | 			salt: enc.encode("encryption_salt"),
 47 | 			iterations: 100000,
 48 | 			hash: "SHA-256",
 49 | 		},
 50 | 		keyMaterial,
 51 | 		{ name: "AES-GCM", length: 256 },
 52 | 		false,
 53 | 		["encrypt", "decrypt"],
 54 | 	);
 55 | }
 56 | 
 57 | export async function encryptPrivateKey(
 58 | 	privateKey: string,
 59 | 	secretKey: string,
 60 | ): Promise<{ encryptedPrivateKey: string; iv: string; authTag: string }> {
 61 | 	const key = await deriveKey(secretKey); // Derive a 32-byte key from the provided secret
 62 | 	const iv = crypto.getRandomValues(new Uint8Array(12)); // 12-byte IV for AES-GCM
 63 | 
 64 | 	const enc = new TextEncoder();
 65 | 	const ciphertext = await getWebcryptoSubtle().encrypt(
 66 | 		{
 67 | 			name: "AES-GCM",
 68 | 			iv: iv,
 69 | 		},
 70 | 		key,
 71 | 		enc.encode(privateKey),
 72 | 	);
 73 | 
 74 | 	const encryptedPrivateKey = base64.encode(ciphertext);
 75 | 	const ivBase64 = base64.encode(iv);
 76 | 
 77 | 	return {
 78 | 		encryptedPrivateKey,
 79 | 		iv: ivBase64,
 80 | 		authTag: encryptedPrivateKey.slice(-16),
 81 | 	};
 82 | }
 83 | 
 84 | export async function decryptPrivateKey(
 85 | 	encryptedPrivate: {
 86 | 		encryptedPrivateKey: string;
 87 | 		iv: string;
 88 | 		authTag: string;
 89 | 	},
 90 | 	secretKey: string,
 91 | ): Promise<string> {
 92 | 	const key = await deriveKey(secretKey);
 93 | 	const { encryptedPrivateKey, iv } = encryptedPrivate;
 94 | 
 95 | 	const ivBuffer = base64.decode(iv);
 96 | 	const ciphertext = base64.decode(encryptedPrivateKey);
 97 | 
 98 | 	const decrypted = await getWebcryptoSubtle().decrypt(
 99 | 		{
100 | 			name: "AES-GCM",
101 | 			iv: ivBuffer as BufferSource,
102 | 		},
103 | 		key,
104 | 		ciphertext as BufferSource,
105 | 	);
106 | 
107 | 	const dec = new TextDecoder();
108 | 	return dec.decode(decrypted);
109 | }
110 | 
111 | export async function generateExportedKeyPair(options?: JwtOptions) {
112 | 	const { alg, ...cfg } = options?.jwks?.keyPairConfig ?? {
113 | 		alg: "EdDSA",
114 | 		crv: "Ed25519",
115 | 	};
116 | 	const { publicKey, privateKey } = await generateKeyPair(alg, {
117 | 		...cfg,
118 | 		extractable: true,
119 | 	});
120 | 
121 | 	const publicWebKey = await exportJWK(publicKey);
122 | 	const privateWebKey = await exportJWK(privateKey);
123 | 
124 | 	return { publicWebKey, privateWebKey, alg, cfg };
125 | }
126 | 
127 | /**
128 |  * Creates a Jwk on the database
129 |  *
130 |  * @param ctx
131 |  * @param options
132 |  * @returns
133 |  */
134 | export async function createJwk(
135 | 	ctx: GenericEndpointContext,
136 | 	options?: JwtOptions,
137 | ) {
138 | 	const { publicWebKey, privateWebKey, alg, cfg } =
139 | 		await generateExportedKeyPair(options);
140 | 
141 | 	const stringifiedPrivateWebKey = JSON.stringify(privateWebKey);
142 | 	const privateKeyEncryptionEnabled =
143 | 		!options?.jwks?.disablePrivateKeyEncryption;
144 | 	let jwk: Omit<Jwk, "id"> = {
145 | 		alg,
146 | 		...(cfg && "crv" in cfg
147 | 			? {
148 | 					crv: (cfg as { crv: (typeof jwk)["crv"] }).crv,
149 | 				}
150 | 			: {}),
151 | 		publicKey: JSON.stringify(publicWebKey),
152 | 		privateKey: privateKeyEncryptionEnabled
153 | 			? JSON.stringify(
154 | 					await symmetricEncrypt({
155 | 						key: ctx.context.secret,
156 | 						data: stringifiedPrivateWebKey,
157 | 					}),
158 | 				)
159 | 			: stringifiedPrivateWebKey,
160 | 		createdAt: new Date(),
161 | 	};
162 | 
163 | 	const adapter = getJwksAdapter(ctx.context.adapter);
164 | 	const key = await adapter.createJwk(jwk as Jwk);
165 | 
166 | 	return key;
167 | }
168 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/components/ui/sheet.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as SheetPrimitive from "@radix-ui/react-dialog";
  5 | import { Cross2Icon } from "@radix-ui/react-icons";
  6 | import { cva, type VariantProps } from "class-variance-authority";
  7 | 
  8 | import { cn } from "@/lib/utils";
  9 | 
 10 | const Sheet = SheetPrimitive.Root;
 11 | 
 12 | const SheetTrigger = SheetPrimitive.Trigger;
 13 | 
 14 | const SheetClose = SheetPrimitive.Close;
 15 | 
 16 | const SheetPortal = SheetPrimitive.Portal;
 17 | 
 18 | const SheetOverlay = ({
 19 | 	ref,
 20 | 	className,
 21 | 	...props
 22 | }: React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> & {
 23 | 	ref: React.RefObject<React.ElementRef<typeof SheetPrimitive.Overlay>>;
 24 | }) => (
 25 | 	<SheetPrimitive.Overlay
 26 | 		className={cn(
 27 | 			"fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
 28 | 			className,
 29 | 		)}
 30 | 		{...props}
 31 | 		ref={ref}
 32 | 	/>
 33 | );
 34 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
 35 | 
 36 | const sheetVariants = cva(
 37 | 	"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
 38 | 	{
 39 | 		variants: {
 40 | 			side: {
 41 | 				top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
 42 | 				bottom:
 43 | 					"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
 44 | 				left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
 45 | 				right:
 46 | 					"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
 47 | 			},
 48 | 		},
 49 | 		defaultVariants: {
 50 | 			side: "right",
 51 | 		},
 52 | 	},
 53 | );
 54 | 
 55 | interface SheetContentProps
 56 | 	extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
 57 | 		VariantProps<typeof sheetVariants> {}
 58 | 
 59 | const SheetContent = ({
 60 | 	ref,
 61 | 	side = "right",
 62 | 	className,
 63 | 	children,
 64 | 	...props
 65 | }: SheetContentProps & {
 66 | 	ref: React.RefObject<React.ElementRef<typeof SheetPrimitive.Content>>;
 67 | }) => (
 68 | 	<SheetPortal>
 69 | 		<SheetOverlay />
 70 | 		<SheetPrimitive.Content
 71 | 			ref={ref}
 72 | 			className={cn(sheetVariants({ side }), className)}
 73 | 			{...props}
 74 | 		>
 75 | 			<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
 76 | 				<Cross2Icon className="h-4 w-4" />
 77 | 				<span className="sr-only">Close</span>
 78 | 			</SheetPrimitive.Close>
 79 | 			{children}
 80 | 		</SheetPrimitive.Content>
 81 | 	</SheetPortal>
 82 | );
 83 | SheetContent.displayName = SheetPrimitive.Content.displayName;
 84 | 
 85 | const SheetHeader = ({
 86 | 	className,
 87 | 	...props
 88 | }: React.HTMLAttributes<HTMLDivElement>) => (
 89 | 	<div
 90 | 		className={cn(
 91 | 			"flex flex-col space-y-2 text-center sm:text-left",
 92 | 			className,
 93 | 		)}
 94 | 		{...props}
 95 | 	/>
 96 | );
 97 | SheetHeader.displayName = "SheetHeader";
 98 | 
 99 | const SheetFooter = ({
100 | 	className,
101 | 	...props
102 | }: React.HTMLAttributes<HTMLDivElement>) => (
103 | 	<div
104 | 		className={cn(
105 | 			"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
106 | 			className,
107 | 		)}
108 | 		{...props}
109 | 	/>
110 | );
111 | SheetFooter.displayName = "SheetFooter";
112 | 
113 | const SheetTitle = ({
114 | 	ref,
115 | 	className,
116 | 	...props
117 | }: React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> & {
118 | 	ref: React.RefObject<React.ElementRef<typeof SheetPrimitive.Title>>;
119 | }) => (
120 | 	<SheetPrimitive.Title
121 | 		ref={ref}
122 | 		className={cn("text-lg font-semibold text-foreground", className)}
123 | 		{...props}
124 | 	/>
125 | );
126 | SheetTitle.displayName = SheetPrimitive.Title.displayName;
127 | 
128 | const SheetDescription = ({
129 | 	ref,
130 | 	className,
131 | 	...props
132 | }: React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> & {
133 | 	ref: React.RefObject<React.ElementRef<typeof SheetPrimitive.Description>>;
134 | }) => (
135 | 	<SheetPrimitive.Description
136 | 		ref={ref}
137 | 		className={cn("text-sm text-muted-foreground", className)}
138 | 		{...props}
139 | 	/>
140 | );
141 | SheetDescription.displayName = SheetPrimitive.Description.displayName;
142 | 
143 | export {
144 | 	Sheet,
145 | 	SheetPortal,
146 | 	SheetOverlay,
147 | 	SheetTrigger,
148 | 	SheetClose,
149 | 	SheetContent,
150 | 	SheetHeader,
151 | 	SheetFooter,
152 | 	SheetTitle,
153 | 	SheetDescription,
154 | };
155 | 
```

--------------------------------------------------------------------------------
/packages/core/src/social-providers/salesforce.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import { BetterAuthError } from "../error";
  3 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  4 | import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
  5 | import { logger } from "../env";
  6 | import { refreshAccessToken } from "../oauth2";
  7 | 
  8 | export interface SalesforceProfile {
  9 | 	sub: string;
 10 | 	user_id: string;
 11 | 	organization_id: string;
 12 | 	preferred_username?: string;
 13 | 	email: string;
 14 | 	email_verified?: boolean;
 15 | 	name: string;
 16 | 	given_name?: string;
 17 | 	family_name?: string;
 18 | 	zoneinfo?: string;
 19 | 	photos?: {
 20 | 		picture?: string;
 21 | 		thumbnail?: string;
 22 | 	};
 23 | }
 24 | 
 25 | export interface SalesforceOptions extends ProviderOptions<SalesforceProfile> {
 26 | 	clientId: string;
 27 | 	environment?: "sandbox" | "production";
 28 | 	loginUrl?: string;
 29 | 	/**
 30 | 	 * Override the redirect URI if auto-detection fails.
 31 | 	 * Should match the Callback URL configured in your Salesforce Connected App.
 32 | 	 * @example "http://localhost:3000/api/auth/callback/salesforce"
 33 | 	 */
 34 | 	redirectURI?: string;
 35 | }
 36 | 
 37 | export const salesforce = (options: SalesforceOptions) => {
 38 | 	const environment = options.environment ?? "production";
 39 | 	const isSandbox = environment === "sandbox";
 40 | 	const authorizationEndpoint = options.loginUrl
 41 | 		? `https://${options.loginUrl}/services/oauth2/authorize`
 42 | 		: isSandbox
 43 | 			? "https://test.salesforce.com/services/oauth2/authorize"
 44 | 			: "https://login.salesforce.com/services/oauth2/authorize";
 45 | 
 46 | 	const tokenEndpoint = options.loginUrl
 47 | 		? `https://${options.loginUrl}/services/oauth2/token`
 48 | 		: isSandbox
 49 | 			? "https://test.salesforce.com/services/oauth2/token"
 50 | 			: "https://login.salesforce.com/services/oauth2/token";
 51 | 
 52 | 	const userInfoEndpoint = options.loginUrl
 53 | 		? `https://${options.loginUrl}/services/oauth2/userinfo`
 54 | 		: isSandbox
 55 | 			? "https://test.salesforce.com/services/oauth2/userinfo"
 56 | 			: "https://login.salesforce.com/services/oauth2/userinfo";
 57 | 
 58 | 	return {
 59 | 		id: "salesforce",
 60 | 		name: "Salesforce",
 61 | 
 62 | 		async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
 63 | 			if (!options.clientId || !options.clientSecret) {
 64 | 				logger.error(
 65 | 					"Client Id and Client Secret are required for Salesforce. Make sure to provide them in the options.",
 66 | 				);
 67 | 				throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
 68 | 			}
 69 | 			if (!codeVerifier) {
 70 | 				throw new BetterAuthError("codeVerifier is required for Salesforce");
 71 | 			}
 72 | 
 73 | 			const _scopes = options.disableDefaultScope
 74 | 				? []
 75 | 				: ["openid", "email", "profile"];
 76 | 			options.scope && _scopes.push(...options.scope);
 77 | 			scopes && _scopes.push(...scopes);
 78 | 
 79 | 			return createAuthorizationURL({
 80 | 				id: "salesforce",
 81 | 				options,
 82 | 				authorizationEndpoint,
 83 | 				scopes: _scopes,
 84 | 				state,
 85 | 				codeVerifier,
 86 | 				redirectURI: options.redirectURI || redirectURI,
 87 | 			});
 88 | 		},
 89 | 
 90 | 		validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
 91 | 			return validateAuthorizationCode({
 92 | 				code,
 93 | 				codeVerifier,
 94 | 				redirectURI: options.redirectURI || redirectURI,
 95 | 				options,
 96 | 				tokenEndpoint,
 97 | 			});
 98 | 		},
 99 | 
100 | 		refreshAccessToken: options.refreshAccessToken
101 | 			? options.refreshAccessToken
102 | 			: async (refreshToken) => {
103 | 					return refreshAccessToken({
104 | 						refreshToken,
105 | 						options: {
106 | 							clientId: options.clientId,
107 | 							clientSecret: options.clientSecret,
108 | 						},
109 | 						tokenEndpoint,
110 | 					});
111 | 				},
112 | 
113 | 		async getUserInfo(token) {
114 | 			if (options.getUserInfo) {
115 | 				return options.getUserInfo(token);
116 | 			}
117 | 
118 | 			try {
119 | 				const { data: user } = await betterFetch<SalesforceProfile>(
120 | 					userInfoEndpoint,
121 | 					{
122 | 						headers: {
123 | 							Authorization: `Bearer ${token.accessToken}`,
124 | 						},
125 | 					},
126 | 				);
127 | 
128 | 				if (!user) {
129 | 					logger.error("Failed to fetch user info from Salesforce");
130 | 					return null;
131 | 				}
132 | 
133 | 				const userMap = await options.mapProfileToUser?.(user);
134 | 
135 | 				return {
136 | 					user: {
137 | 						id: user.user_id,
138 | 						name: user.name,
139 | 						email: user.email,
140 | 						image: user.photos?.picture || user.photos?.thumbnail,
141 | 						emailVerified: user.email_verified ?? false,
142 | 						...userMap,
143 | 					},
144 | 					data: user,
145 | 				};
146 | 			} catch (error) {
147 | 				logger.error("Failed to fetch user info from Salesforce:", error);
148 | 				return null;
149 | 			}
150 | 		},
151 | 
152 | 		options,
153 | 	} satisfies OAuthProvider<SalesforceProfile>;
154 | };
155 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/plugins/one-tap.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: One Tap
  3 | description: One Tap plugin for Better Auth
  4 | ---
  5 | 
  6 | The One Tap plugin allows users to log in with a single tap using Google's One Tap API. The plugin
  7 | provides a simple way to integrate One Tap into your application, handling the client-side and server-side logic for you.
  8 | 
  9 | ## Installation
 10 | 
 11 | ### Add the Server Plugin
 12 | 
 13 | Add the One Tap plugin to your auth configuration:
 14 | 
 15 | ```ts title="auth.ts"
 16 | import { betterAuth } from "better-auth";
 17 | import { oneTap } from "better-auth/plugins"; // [!code highlight]
 18 | 
 19 | export const auth = betterAuth({
 20 |     plugins: [ // [!code highlight]
 21 |         oneTap(), // Add the One Tap server plugin  // [!code highlight]
 22 |     ] // [!code highlight]
 23 | });
 24 | ```
 25 | 
 26 | ### Add the Client Plugin
 27 | 
 28 | Add the client plugin and specify where the user should be redirected after sign-in or if additional verification (like 2FA) is needed.
 29 | 
 30 | 
 31 | ```ts
 32 | import { createAuthClient } from "better-auth/client";
 33 | import { oneTapClient } from "better-auth/client/plugins";
 34 | 
 35 | export const authClient = createAuthClient({
 36 |   plugins: [
 37 |     oneTapClient({
 38 |       clientId: "YOUR_CLIENT_ID",
 39 |       // Optional client configuration:
 40 |       autoSelect: false,
 41 |       cancelOnTapOutside: true,
 42 |       context: "signin",
 43 |       additionalOptions: {
 44 |         // Any extra options for the Google initialize method
 45 |       },
 46 |       // Configure prompt behavior and exponential backoff:
 47 |       promptOptions: {
 48 |         baseDelay: 1000,   // Base delay in ms (default: 1000)
 49 |         maxAttempts: 5     // Maximum number of attempts before triggering onPromptNotification (default: 5)
 50 |       }
 51 |     })
 52 |   ]
 53 | });
 54 | ```
 55 | 
 56 | ### Usage
 57 | 
 58 | To display the One Tap popup, simply call the oneTap method on your auth client:
 59 | 
 60 | ```ts
 61 | await authClient.oneTap();
 62 | ```
 63 | 
 64 | ### Customizing Redirect Behavior
 65 | 
 66 | By default, after a successful login the plugin will hard redirect the user to `/`. You can customize this behavior as follows:
 67 | 
 68 | #### Avoiding a Hard Redirect
 69 | 
 70 | Pass fetchOptions with an onSuccess callback to handle the login response without a page reload:
 71 | 
 72 | ```ts
 73 | await authClient.oneTap({
 74 |   fetchOptions: {
 75 |     onSuccess: () => {
 76 |       // For example, use a router to navigate without a full reload:
 77 |       router.push("/dashboard");
 78 |     }
 79 |   }
 80 | });
 81 | ```
 82 | 
 83 | #### Specifying a Custom Callback URL
 84 | 
 85 | To perform a hard redirect to a different page after login, use the callbackURL option:
 86 | 
 87 | ```ts
 88 | await authClient.oneTap({
 89 |   callbackURL: "/dashboard"
 90 | });
 91 | ```
 92 | 
 93 | #### Handling Prompt Dismissals with Exponential Backoff
 94 | 
 95 | If the user dismisses or skips the prompt, the plugin will retry showing the One Tap prompt using exponential backoff based on your configured promptOptions.
 96 | 
 97 | If the maximum number of attempts is reached without a successful sign-in, you can use the onPromptNotification callback to be notified—allowing you to render an alternative UI (e.g., a traditional Google Sign-In button) so users can restart the process manually:
 98 | 
 99 | ```ts
100 | await authClient.oneTap({
101 |   onPromptNotification: (notification) => {
102 |     console.warn("Prompt was dismissed or skipped. Consider displaying an alternative sign-in option.", notification);
103 |     // Render your alternative UI here
104 |   }
105 | });
106 | ```
107 | 
108 | ### Client Options
109 | 
110 | - **clientId**: The client ID for your Google One Tap API.
111 | - **autoSelect**: Automatically select the account if the user is already signed in. Default is false.
112 | - **context**: The context in which the One Tap API should be used (e.g., "signin"). Default is "signin".
113 | - **cancelOnTapOutside**: Cancel the One Tap popup when the user taps outside it. Default is true.
114 | - additionalOptions: Extra options to pass to Google's initialize method as per the [Google Identity Services docs](https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt).
115 | - **promptOptions**: Configuration for the prompt behavior and exponential backoff:
116 | - **baseDelay**: Base delay in milliseconds for retries. Default is 1000.
117 | - **maxAttempts**: Maximum number of prompt attempts before invoking the onPromptNotification callback. Default is 5.
118 | 
119 | ### Server Options
120 | 
121 | - **disableSignUp**:  Disable the sign-up option, allowing only existing users to sign in. Default is `false`.
122 | - **ClientId**: Optionally, pass a client ID here if it is not provided in your social provider configuration.
123 | 
124 | 
```

--------------------------------------------------------------------------------
/packages/core/src/social-providers/google.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import { decodeJwt } from "jose";
  3 | import { BetterAuthError } from "../error";
  4 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  5 | import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
  6 | import { logger } from "../env";
  7 | import { refreshAccessToken } from "../oauth2";
  8 | 
  9 | export interface GoogleProfile {
 10 | 	aud: string;
 11 | 	azp: string;
 12 | 	email: string;
 13 | 	email_verified: boolean;
 14 | 	exp: number;
 15 | 	/**
 16 | 	 * The family name of the user, or last name in most
 17 | 	 * Western languages.
 18 | 	 */
 19 | 	family_name: string;
 20 | 	/**
 21 | 	 * The given name of the user, or first name in most
 22 | 	 * Western languages.
 23 | 	 */
 24 | 	given_name: string;
 25 | 	hd?: string;
 26 | 	iat: number;
 27 | 	iss: string;
 28 | 	jti?: string;
 29 | 	locale?: string;
 30 | 	name: string;
 31 | 	nbf?: number;
 32 | 	picture: string;
 33 | 	sub: string;
 34 | }
 35 | 
 36 | export interface GoogleOptions extends ProviderOptions<GoogleProfile> {
 37 | 	clientId: string;
 38 | 	/**
 39 | 	 * The access type to use for the authorization code request
 40 | 	 */
 41 | 	accessType?: "offline" | "online";
 42 | 	/**
 43 | 	 * The display mode to use for the authorization code request
 44 | 	 */
 45 | 	display?: "page" | "popup" | "touch" | "wap";
 46 | 	/**
 47 | 	 * The hosted domain of the user
 48 | 	 */
 49 | 	hd?: string;
 50 | }
 51 | 
 52 | export const google = (options: GoogleOptions) => {
 53 | 	return {
 54 | 		id: "google",
 55 | 		name: "Google",
 56 | 		async createAuthorizationURL({
 57 | 			state,
 58 | 			scopes,
 59 | 			codeVerifier,
 60 | 			redirectURI,
 61 | 			loginHint,
 62 | 			display,
 63 | 		}) {
 64 | 			if (!options.clientId || !options.clientSecret) {
 65 | 				logger.error(
 66 | 					"Client Id and Client Secret is required for Google. Make sure to provide them in the options.",
 67 | 				);
 68 | 				throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
 69 | 			}
 70 | 			if (!codeVerifier) {
 71 | 				throw new BetterAuthError("codeVerifier is required for Google");
 72 | 			}
 73 | 			const _scopes = options.disableDefaultScope
 74 | 				? []
 75 | 				: ["email", "profile", "openid"];
 76 | 			options.scope && _scopes.push(...options.scope);
 77 | 			scopes && _scopes.push(...scopes);
 78 | 			const url = await createAuthorizationURL({
 79 | 				id: "google",
 80 | 				options,
 81 | 				authorizationEndpoint: "https://accounts.google.com/o/oauth2/auth",
 82 | 				scopes: _scopes,
 83 | 				state,
 84 | 				codeVerifier,
 85 | 				redirectURI,
 86 | 				prompt: options.prompt,
 87 | 				accessType: options.accessType,
 88 | 				display: display || options.display,
 89 | 				loginHint,
 90 | 				hd: options.hd,
 91 | 				additionalParams: {
 92 | 					include_granted_scopes: "true",
 93 | 				},
 94 | 			});
 95 | 			return url;
 96 | 		},
 97 | 		validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
 98 | 			return validateAuthorizationCode({
 99 | 				code,
100 | 				codeVerifier,
101 | 				redirectURI,
102 | 				options,
103 | 				tokenEndpoint: "https://oauth2.googleapis.com/token",
104 | 			});
105 | 		},
106 | 		refreshAccessToken: options.refreshAccessToken
107 | 			? options.refreshAccessToken
108 | 			: async (refreshToken) => {
109 | 					return refreshAccessToken({
110 | 						refreshToken,
111 | 						options: {
112 | 							clientId: options.clientId,
113 | 							clientKey: options.clientKey,
114 | 							clientSecret: options.clientSecret,
115 | 						},
116 | 						tokenEndpoint: "https://www.googleapis.com/oauth2/v4/token",
117 | 					});
118 | 				},
119 | 		async verifyIdToken(token, nonce) {
120 | 			if (options.disableIdTokenSignIn) {
121 | 				return false;
122 | 			}
123 | 			if (options.verifyIdToken) {
124 | 				return options.verifyIdToken(token, nonce);
125 | 			}
126 | 			const googlePublicKeyUrl = `https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=${token}`;
127 | 			const { data: tokenInfo } = await betterFetch<{
128 | 				aud: string;
129 | 				iss: string;
130 | 				email: string;
131 | 				email_verified: boolean;
132 | 				name: string;
133 | 				picture: string;
134 | 				sub: string;
135 | 			}>(googlePublicKeyUrl);
136 | 			if (!tokenInfo) {
137 | 				return false;
138 | 			}
139 | 			const isValid =
140 | 				tokenInfo.aud === options.clientId &&
141 | 				(tokenInfo.iss === "https://accounts.google.com" ||
142 | 					tokenInfo.iss === "accounts.google.com");
143 | 			return isValid;
144 | 		},
145 | 		async getUserInfo(token) {
146 | 			if (options.getUserInfo) {
147 | 				return options.getUserInfo(token);
148 | 			}
149 | 			if (!token.idToken) {
150 | 				return null;
151 | 			}
152 | 			const user = decodeJwt(token.idToken) as GoogleProfile;
153 | 			const userMap = await options.mapProfileToUser?.(user);
154 | 			return {
155 | 				user: {
156 | 					id: user.sub,
157 | 					name: user.name,
158 | 					email: user.email,
159 | 					image: user.picture,
160 | 					emailVerified: user.email_verified,
161 | 					...userMap,
162 | 				},
163 | 				data: user,
164 | 			};
165 | 		},
166 | 		options,
167 | 	} satisfies OAuthProvider<GoogleProfile>;
168 | };
169 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/reference/telemetry.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: Telemetry
 3 | description: Better Auth now collects anonymous telemetry data about general usage.
 4 | ---
 5 | 
 6 | Better Auth collects anonymous usage data to help us improve the project. This is optional, transparent, and disabled by default.
 7 | 
 8 | ## Why is telemetry collected?
 9 | 
10 | Since v1.3.5, Better Auth collects anonymous telemetry data about general usage if enabled.
11 | 
12 | Telemetry data helps us understand how Better Auth is being used across different environments so we can improve performance, prioritize features, and fix issues more effectively. It guides our decisions on performance optimizations, feature development, and bug fixes. All data is collected completely anonymously and with privacy in mind, and users can opt out at any time. We strive to keep what we collect as transparent as possible.
13 | 
14 | ## What is being collected?
15 | 
16 | The following data points may be reported. Everything is anonymous and intended for aggregate insights only.
17 | 
18 | - **Anonymous identifier**: A non-reversible hash derived from your project (`package.json` name and optionally `baseURL`). This lets us de‑duplicate events per project without knowing who you are.
19 | - **Runtime**: `{ name: "node" | "bun" | "deno", version }`.
20 | - **Environment**: one of `development`, `production`, `test`, or `ci`.
21 | - **Framework (if detected)**: `{ name, version }` for frameworks like Next.js, Nuxt, Remix, Astro, SvelteKit, etc.
22 | - **Database (if detected)**: `{ name, version }` for integrations like PostgreSQL, MySQL, SQLite, Prisma, Drizzle, MongoDB, etc.
23 | - **System info**: platform, OS release, architecture, CPU count/model/speed, total memory, and flags like `isDocker`, `isWSL`, `isTTY`.
24 | - **Package manager**: `{ name, version }` derived from the npm user agent.
25 | - **Redacted auth config snapshot**: A minimized, privacy‑preserving view of your `betterAuth` options produced by `getTelemetryAuthConfig`.
26 | 
27 | We also collect anonymous telemetry from the CLI:
28 | 
29 | - **CLI generate (`cli_generate`)**: outcome `generated | overwritten | appended | no_changes | aborted` plus redacted config.
30 | - **CLI migrate (`cli_migrate`)**: outcome `migrated | no_changes | aborted | unsupported_adapter` plus adapter id (when relevant) and redacted config.
31 | 
32 | 
33 | <Callout type="info">
34 |   You can audit telemetry locally by setting the `BETTER_AUTH_TELEMETRY_DEBUG=1` environment variable when running your project or by setting `telemetry: { debug: true }` in your auth config. In this debug mode, telemetry events are logged only to the console.
35 | 
36 |   ```ts title="auth.ts"
37 |   export const auth = betterAuth({
38 |     // [!code highlight]
39 |     telemetry: { // [!code highlight]
40 |       debug: true // [!code highlight]
41 |     } // [!code highlight]
42 |   });
43 |   ```
44 | </Callout>
45 | 
46 | ## How is my data protected?
47 | 
48 | All collected data is fully anonymous and only useful in aggregate. It cannot be traced back to any individual source and is accessible only to a small group of core Better Auth maintainers to guide roadmap decisions.
49 | 
50 | - **No PII or secrets**: We do not collect emails, usernames, tokens, secrets, client IDs, client secrets, or database URLs.
51 | - **No full config**: We never send your full `betterAuth` configuration. Instead we send a reduced, redacted snapshot of non‑sensitive toggles and counts.
52 | - **Redaction by design**: See [detect-auth-config.ts](https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/telemetry/detectors/detect-auth-config.ts) in the Better Auth source for the exact shape of what is included. It purposely converts sensitive values to booleans, counts, or generic identifiers.
53 | 
54 | ## How can I enable it?
55 | 
56 | You can enable telemetry collection in your auth config or by setting an environment variable.
57 | 
58 | - Via your auth config.
59 | 
60 |   ```ts title="auth.ts"
61 |   export const auth = betterAuth({
62 |     // [!code highlight]
63 |     telemetry: { // [!code highlight]
64 |       enabled: true// [!code highlight]
65 |     } // [!code highlight]
66 |   });
67 |   ```
68 | 
69 | - Via an environment variable.
70 | 
71 |   ```txt title=".env"
72 |   # Enable telemetry
73 |   BETTER_AUTH_TELEMETRY=1
74 | 
75 |   # Disable telemetry
76 |   BETTER_AUTH_TELEMETRY=0
77 |   ```
78 | 
79 | 
80 | ### When is telemetry sent?
81 | 
82 | - On `betterAuth` initialization (`type: "init"`).
83 | - On CLI actions: `generate` and `migrate` as described above.
84 | 
85 | Telemetry is disabled automatically in tests (`NODE_ENV=test`) unless explicitly overridden by internal tooling.
86 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/client/path-to-object.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type {
  2 | 	BetterFetchOption,
  3 | 	BetterFetchResponse,
  4 | } from "@better-fetch/fetch";
  5 | import type { InputContext, Endpoint, StandardSchemaV1 } from "better-call";
  6 | import type {
  7 | 	HasRequiredKeys,
  8 | 	Prettify,
  9 | 	UnionToIntersection,
 10 | } from "../types/helper";
 11 | import type {
 12 | 	InferAdditionalFromClient,
 13 | 	InferSessionFromClient,
 14 | 	InferUserFromClient,
 15 | } from "./types";
 16 | import type { BetterAuthClientOptions } from "@better-auth/core";
 17 | 
 18 | export type CamelCase<S extends string> =
 19 | 	S extends `${infer P1}-${infer P2}${infer P3}`
 20 | 		? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
 21 | 		: Lowercase<S>;
 22 | 
 23 | export type PathToObject<
 24 | 	T extends string,
 25 | 	Fn extends (...args: any[]) => any,
 26 | > = T extends `/${infer Segment}/${infer Rest}`
 27 | 	? { [K in CamelCase<Segment>]: PathToObject<`/${Rest}`, Fn> }
 28 | 	: T extends `/${infer Segment}`
 29 | 		? { [K in CamelCase<Segment>]: Fn }
 30 | 		: never;
 31 | 
 32 | export type InferSignUpEmailCtx<
 33 | 	ClientOpts extends BetterAuthClientOptions,
 34 | 	FetchOptions extends BetterFetchOption,
 35 | > = {
 36 | 	email: string;
 37 | 	name: string;
 38 | 	password: string;
 39 | 	image?: string;
 40 | 	callbackURL?: string;
 41 | 	fetchOptions?: FetchOptions;
 42 | } & UnionToIntersection<InferAdditionalFromClient<ClientOpts, "user", "input">>;
 43 | 
 44 | export type InferUserUpdateCtx<
 45 | 	ClientOpts extends BetterAuthClientOptions,
 46 | 	FetchOptions extends BetterFetchOption,
 47 | > = {
 48 | 	image?: string | null;
 49 | 	name?: string;
 50 | 	fetchOptions?: FetchOptions;
 51 | } & Partial<
 52 | 	UnionToIntersection<InferAdditionalFromClient<ClientOpts, "user", "input">>
 53 | >;
 54 | 
 55 | export type InferCtx<
 56 | 	C extends InputContext<any, any>,
 57 | 	FetchOptions extends BetterFetchOption,
 58 | > = C["body"] extends Record<string, any>
 59 | 	? C["body"] & {
 60 | 			fetchOptions?: FetchOptions;
 61 | 		}
 62 | 	: C["query"] extends Record<string, any>
 63 | 		? {
 64 | 				query: C["query"];
 65 | 				fetchOptions?: FetchOptions;
 66 | 			}
 67 | 		: C["query"] extends Record<string, any> | undefined
 68 | 			? {
 69 | 					query?: C["query"];
 70 | 					fetchOptions?: FetchOptions;
 71 | 				}
 72 | 			: {
 73 | 					fetchOptions?: FetchOptions;
 74 | 				};
 75 | 
 76 | export type MergeRoutes<T> = UnionToIntersection<T>;
 77 | 
 78 | export type InferRoute<
 79 | 	API,
 80 | 	COpts extends BetterAuthClientOptions,
 81 | > = API extends Record<string, infer T>
 82 | 	? T extends Endpoint
 83 | 		? T["options"]["metadata"] extends
 84 | 				| {
 85 | 						isAction: false;
 86 | 				  }
 87 | 				| {
 88 | 						SERVER_ONLY: true;
 89 | 				  }
 90 | 			? {}
 91 | 			: PathToObject<
 92 | 					T["path"],
 93 | 					T extends (ctx: infer C) => infer R
 94 | 						? C extends InputContext<any, any>
 95 | 							? <
 96 | 									FetchOptions extends BetterFetchOption<
 97 | 										Partial<C["body"]> & Record<string, any>,
 98 | 										Partial<C["query"]> & Record<string, any>,
 99 | 										C["params"]
100 | 									>,
101 | 								>(
102 | 									...data: HasRequiredKeys<
103 | 										InferCtx<C, FetchOptions>
104 | 									> extends true
105 | 										? [
106 | 												Prettify<
107 | 													T["path"] extends `/sign-up/email`
108 | 														? InferSignUpEmailCtx<COpts, FetchOptions>
109 | 														: InferCtx<C, FetchOptions>
110 | 												>,
111 | 												FetchOptions?,
112 | 											]
113 | 										: [
114 | 												Prettify<
115 | 													T["path"] extends `/update-user`
116 | 														? InferUserUpdateCtx<COpts, FetchOptions>
117 | 														: InferCtx<C, FetchOptions>
118 | 												>?,
119 | 												FetchOptions?,
120 | 											]
121 | 								) => Promise<
122 | 									BetterFetchResponse<
123 | 										T["options"]["metadata"] extends {
124 | 											CUSTOM_SESSION: boolean;
125 | 										}
126 | 											? NonNullable<Awaited<R>>
127 | 											: T["path"] extends "/get-session"
128 | 												? {
129 | 														user: InferUserFromClient<COpts>;
130 | 														session: InferSessionFromClient<COpts>;
131 | 													} | null
132 | 												: NonNullable<Awaited<R>>,
133 | 										T["options"]["error"] extends StandardSchemaV1
134 | 											? // InferOutput
135 | 												NonNullable<
136 | 													T["options"]["error"]["~standard"]["types"]
137 | 												>["output"]
138 | 											: {
139 | 													code?: string;
140 | 													message?: string;
141 | 												},
142 | 										FetchOptions["throw"] extends true
143 | 											? true
144 | 											: COpts["fetchOptions"] extends { throw: true }
145 | 												? true
146 | 												: false
147 | 									>
148 | 								>
149 | 							: never
150 | 						: never
151 | 				>
152 | 		: {}
153 | 	: never;
154 | 
155 | export type InferRoutes<
156 | 	API extends Record<string, Endpoint>,
157 | 	ClientOpts extends BetterAuthClientOptions,
158 | > = MergeRoutes<InferRoute<API, ClientOpts>>;
159 | 
160 | export type ProxyRequest = {
161 | 	options?: BetterFetchOption<any, any>;
162 | 	query?: any;
163 | 	[key: string]: any;
164 | };
165 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/plugins/community-plugins.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: Community Plugins
 3 | description: A list of recommended community plugins.
 4 | ---
 5 | 
 6 | This page showcases a list of recommended community made plugins.
 7 | 
 8 | We encourage you to create custom plugins and maybe get added to the list!
 9 | 
10 | To create your own custom plugin, get started by reading our [plugins documentation](/docs/concepts/plugins). And if you want to share your plugin with the community, please open a pull request to add it to this list.
11 | 
12 | | <div className="w-[200px]">Plugin</div>                                             | Description                                                                                                                  | <div className="w-[150px]">Author</div>                                                                                                                              |
13 | | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
14 | | [@dymo-api/better-auth](https://github.com/TPEOficial/dymo-api-better-auth)         | Sign Up Protection and validation of disposable emails (the world's largest database with nearly 14 million entries).        | <img src="https://github.com/TPEOficial.png" className="rounded-full w-6 h-6 border opacity-70 m-0 inline mr-1" /> [TPEOficial](https://github.com/TPEOficial) |
15 | | [better-auth-harmony](https://github.com/gekorm/better-auth-harmony/)               | Email & phone normalization and additional validation, blocking over 55,000 temporary email domains.                         | <img src="https://github.com/GeKorm.png" className="rounded-full w-6 h-6 border opacity-70 m-0 inline mr-1" /> [GeKorm](https://github.com/GeKorm) |
16 | | [validation-better-auth](https://github.com/Daanish2003/validation-better-auth)     | Validate API request using any validation library (e.g., Zod, Yup)                                                           | <img src="https://github.com/Daanish2003.png" className="rounded-full w-6 h-6 border opacity-70 m-0 inline mr-1" /> [Daanish2003](https://github.com/Daanish2003) |
17 | | [better-auth-localization](https://github.com/marcellosso/better-auth-localization) | Localize and customize better-auth messages with easy translation and message override support.                              | <img src="https://github.com/marcellosso.png" className="rounded-full w-6 h-6 border opacity-70 m-0 inline mr-1" /> [marcellosso](https://github.com/marcellosso) |
18 | | [better-auth-attio-plugin](https://github.com/tobimori/better-auth-attio-plugin)    | Sync your products Better Auth users & workspaces with Attio                              | <img src="https://github.com/tobimori.png" className="rounded-full w-6 h-6 border opacity-70 m-0 inline mr-1" /> [tobimori](https://github.com/tobimori) |
19 | | [better-auth-cloudflare](https://github.com/zpg6/better-auth-cloudflare)            | Seamlessly integrate with Cloudflare Workers, D1, Hyperdrive, KV, R2, and geolocation services. Includes CLI for project generation, automated resource provisioning on Cloudflare, and database migrations. Supports Next.js, Hono, and more! | <img src="https://github.com/zpg6.png" className="rounded-full w-6 h-6 border opacity-70 m-0 inline mr-1" /> [zpg6](https://github.com/zpg6) |
20 | | [expo-better-auth-passkey](https://github.com/kevcube/expo-better-auth-passkey)     | Better-auth client plugin for using passkeys on mobile platforms in expo apps. Supports iOS, macOS, Android (and web!) by wrapping the existing better-auth passkey client plugin.                                                                                                                  | <img src="https://github.com/kevcube.png" className="rounded-full w-6 h-6 border opacity-70 m-0 inline mr-1" /> [kevcube](https://github.com/kevcube) |
21 | | [better-auth-credentials-plugin](https://github.com/erickweil/better-auth-credentials-plugin) | LDAP authentication plugin for Better Auth. | <img src="https://github.com/erickweil.png" className="rounded-full w-6 h-6 border opacity-70 m-0 inline mr-1" /> [erickweil](https://github.com/erickweil) |
22 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
  5 | 
  6 | import { cn } from "@/lib/utils";
  7 | import { buttonVariants } from "@/components/ui/button";
  8 | 
  9 | const AlertDialog = AlertDialogPrimitive.Root;
 10 | 
 11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
 12 | 
 13 | const AlertDialogPortal = AlertDialogPrimitive.Portal;
 14 | 
 15 | const AlertDialogOverlay = ({
 16 | 	ref,
 17 | 	className,
 18 | 	...props
 19 | }: React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> & {
 20 | 	ref: React.RefObject<React.ElementRef<typeof AlertDialogPrimitive.Overlay>>;
 21 | }) => (
 22 | 	<AlertDialogPrimitive.Overlay
 23 | 		className={cn(
 24 | 			"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
 25 | 			className,
 26 | 		)}
 27 | 		{...props}
 28 | 		ref={ref}
 29 | 	/>
 30 | );
 31 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
 32 | 
 33 | const AlertDialogContent = ({
 34 | 	ref,
 35 | 	className,
 36 | 	...props
 37 | }: React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> & {
 38 | 	ref: React.RefObject<React.ElementRef<typeof AlertDialogPrimitive.Content>>;
 39 | }) => (
 40 | 	<AlertDialogPortal>
 41 | 		<AlertDialogOverlay />
 42 | 		<AlertDialogPrimitive.Content
 43 | 			ref={ref}
 44 | 			className={cn(
 45 | 				"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
 46 | 				className,
 47 | 			)}
 48 | 			{...props}
 49 | 		/>
 50 | 	</AlertDialogPortal>
 51 | );
 52 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
 53 | 
 54 | const AlertDialogHeader = ({
 55 | 	className,
 56 | 	...props
 57 | }: React.HTMLAttributes<HTMLDivElement>) => (
 58 | 	<div
 59 | 		className={cn(
 60 | 			"flex flex-col space-y-2 text-center sm:text-left",
 61 | 			className,
 62 | 		)}
 63 | 		{...props}
 64 | 	/>
 65 | );
 66 | AlertDialogHeader.displayName = "AlertDialogHeader";
 67 | 
 68 | const AlertDialogFooter = ({
 69 | 	className,
 70 | 	...props
 71 | }: React.HTMLAttributes<HTMLDivElement>) => (
 72 | 	<div
 73 | 		className={cn(
 74 | 			"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
 75 | 			className,
 76 | 		)}
 77 | 		{...props}
 78 | 	/>
 79 | );
 80 | AlertDialogFooter.displayName = "AlertDialogFooter";
 81 | 
 82 | const AlertDialogTitle = ({
 83 | 	ref,
 84 | 	className,
 85 | 	...props
 86 | }: React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> & {
 87 | 	ref: React.RefObject<React.ElementRef<typeof AlertDialogPrimitive.Title>>;
 88 | }) => (
 89 | 	<AlertDialogPrimitive.Title
 90 | 		ref={ref}
 91 | 		className={cn("text-lg font-semibold", className)}
 92 | 		{...props}
 93 | 	/>
 94 | );
 95 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
 96 | 
 97 | const AlertDialogDescription = ({
 98 | 	ref,
 99 | 	className,
100 | 	...props
101 | }: React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description> & {
102 | 	ref: React.RefObject<
103 | 		React.ElementRef<typeof AlertDialogPrimitive.Description>
104 | 	>;
105 | }) => (
106 | 	<AlertDialogPrimitive.Description
107 | 		ref={ref}
108 | 		className={cn("text-sm text-muted-foreground", className)}
109 | 		{...props}
110 | 	/>
111 | );
112 | AlertDialogDescription.displayName =
113 | 	AlertDialogPrimitive.Description.displayName;
114 | 
115 | const AlertDialogAction = ({
116 | 	ref,
117 | 	className,
118 | 	...props
119 | }: React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action> & {
120 | 	ref: React.RefObject<React.ElementRef<typeof AlertDialogPrimitive.Action>>;
121 | }) => (
122 | 	<AlertDialogPrimitive.Action
123 | 		ref={ref}
124 | 		className={cn(buttonVariants(), className)}
125 | 		{...props}
126 | 	/>
127 | );
128 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
129 | 
130 | const AlertDialogCancel = ({
131 | 	ref,
132 | 	className,
133 | 	...props
134 | }: React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel> & {
135 | 	ref: React.RefObject<React.ElementRef<typeof AlertDialogPrimitive.Cancel>>;
136 | }) => (
137 | 	<AlertDialogPrimitive.Cancel
138 | 		ref={ref}
139 | 		className={cn(
140 | 			buttonVariants({ variant: "outline" }),
141 | 			"mt-2 sm:mt-0",
142 | 			className,
143 | 		)}
144 | 		{...props}
145 | 	/>
146 | );
147 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
148 | 
149 | export {
150 | 	AlertDialog,
151 | 	AlertDialogPortal,
152 | 	AlertDialogOverlay,
153 | 	AlertDialogTrigger,
154 | 	AlertDialogContent,
155 | 	AlertDialogHeader,
156 | 	AlertDialogFooter,
157 | 	AlertDialogTitle,
158 | 	AlertDialogDescription,
159 | 	AlertDialogAction,
160 | 	AlertDialogCancel,
161 | };
162 | 
```

--------------------------------------------------------------------------------
/demo/expo-example/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import * as DialogPrimitive from "@rn-primitives/dialog";
  2 | import * as React from "react";
  3 | import { Platform, StyleSheet, View, type ViewProps } from "react-native";
  4 | import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
  5 | import { X } from "@/lib/icons/X";
  6 | import { cn } from "@/lib/utils";
  7 | 
  8 | const Dialog = DialogPrimitive.Root;
  9 | 
 10 | const DialogTrigger = DialogPrimitive.Trigger;
 11 | 
 12 | const DialogPortal = DialogPrimitive.Portal;
 13 | 
 14 | const DialogClose = DialogPrimitive.Close;
 15 | 
 16 | const DialogOverlayWeb = React.forwardRef<
 17 | 	DialogPrimitive.OverlayRef,
 18 | 	DialogPrimitive.OverlayProps
 19 | >(({ className, ...props }, ref) => {
 20 | 	const { open } = DialogPrimitive.useRootContext();
 21 | 	return (
 22 | 		<DialogPrimitive.Overlay
 23 | 			className={cn(
 24 | 				"bg-black/80 flex justify-center items-center p-2 absolute top-0 right-0 bottom-0 left-0",
 25 | 				open
 26 | 					? "web:animate-in web:fade-in-0"
 27 | 					: "web:animate-out web:fade-out-0",
 28 | 				className,
 29 | 			)}
 30 | 			{...props}
 31 | 			ref={ref}
 32 | 		/>
 33 | 	);
 34 | });
 35 | 
 36 | DialogOverlayWeb.displayName = "DialogOverlayWeb";
 37 | 
 38 | const DialogOverlayNative = React.forwardRef<
 39 | 	DialogPrimitive.OverlayRef,
 40 | 	DialogPrimitive.OverlayProps
 41 | >(({ className, children, ...props }, ref) => {
 42 | 	return (
 43 | 		<DialogPrimitive.Overlay
 44 | 			style={StyleSheet.absoluteFill}
 45 | 			className={cn(
 46 | 				"flex bg-black/80 justify-center items-center p-2",
 47 | 				className,
 48 | 			)}
 49 | 			{...props}
 50 | 			ref={ref}
 51 | 		>
 52 | 			<Animated.View
 53 | 				entering={FadeIn.duration(150)}
 54 | 				exiting={FadeOut.duration(150)}
 55 | 			>
 56 | 				<>{children}</>
 57 | 			</Animated.View>
 58 | 		</DialogPrimitive.Overlay>
 59 | 	);
 60 | });
 61 | 
 62 | DialogOverlayNative.displayName = "DialogOverlayNative";
 63 | 
 64 | const DialogOverlay = Platform.select({
 65 | 	web: DialogOverlayWeb,
 66 | 	default: DialogOverlayNative,
 67 | });
 68 | 
 69 | const DialogContent = React.forwardRef<
 70 | 	DialogPrimitive.ContentRef,
 71 | 	DialogPrimitive.ContentProps & { portalHost?: string }
 72 | >(({ className, children, portalHost, ...props }, ref) => {
 73 | 	const { open } = DialogPrimitive.useRootContext();
 74 | 	return (
 75 | 		<DialogPortal hostName={portalHost}>
 76 | 			<DialogOverlay>
 77 | 				<DialogPrimitive.Content
 78 | 					ref={ref}
 79 | 					className={cn(
 80 | 						"max-w-lg gap-4 border border-border web:cursor-default bg-background p-6 shadow-lg web:duration-200 rounded-lg",
 81 | 						open
 82 | 							? "web:animate-in web:fade-in-0 web:zoom-in-95"
 83 | 							: "web:animate-out web:fade-out-0 web:zoom-out-95",
 84 | 						className,
 85 | 					)}
 86 | 					{...props}
 87 | 				>
 88 | 					{children}
 89 | 					<DialogPrimitive.Close
 90 | 						className={
 91 | 							"absolute right-4 top-4 p-0.5 web:group rounded-sm opacity-70 web:ring-offset-background web:transition-opacity web:hover:opacity-100 web:focus:outline-none web:focus:ring-2 web:focus:ring-ring web:focus:ring-offset-2 web:disabled:pointer-events-none"
 92 | 						}
 93 | 					>
 94 | 						<X
 95 | 							size={Platform.OS === "web" ? 16 : 18}
 96 | 							className={cn(
 97 | 								"text-muted-foreground",
 98 | 								open && "text-accent-foreground",
 99 | 							)}
100 | 						/>
101 | 					</DialogPrimitive.Close>
102 | 				</DialogPrimitive.Content>
103 | 			</DialogOverlay>
104 | 		</DialogPortal>
105 | 	);
106 | });
107 | DialogContent.displayName = DialogPrimitive.Content.displayName;
108 | 
109 | const DialogHeader = ({ className, ...props }: ViewProps) => (
110 | 	<View
111 | 		className={cn("flex flex-col gap-1.5 text-center sm:text-left", className)}
112 | 		{...props}
113 | 	/>
114 | );
115 | DialogHeader.displayName = "DialogHeader";
116 | 
117 | const DialogFooter = ({ className, ...props }: ViewProps) => (
118 | 	<View
119 | 		className={cn(
120 | 			"flex flex-col-reverse sm:flex-row sm:justify-end gap-2",
121 | 			className,
122 | 		)}
123 | 		{...props}
124 | 	/>
125 | );
126 | DialogFooter.displayName = "DialogFooter";
127 | 
128 | const DialogTitle = React.forwardRef<
129 | 	DialogPrimitive.TitleRef,
130 | 	DialogPrimitive.TitleProps
131 | >(({ className, ...props }, ref) => (
132 | 	<DialogPrimitive.Title
133 | 		ref={ref}
134 | 		className={cn(
135 | 			"text-lg native:text-xl text-foreground font-semibold leading-none tracking-tight",
136 | 			className,
137 | 		)}
138 | 		{...props}
139 | 	/>
140 | ));
141 | DialogTitle.displayName = DialogPrimitive.Title.displayName;
142 | 
143 | const DialogDescription = React.forwardRef<
144 | 	DialogPrimitive.DescriptionRef,
145 | 	DialogPrimitive.DescriptionProps
146 | >(({ className, ...props }, ref) => (
147 | 	<DialogPrimitive.Description
148 | 		ref={ref}
149 | 		className={cn("text-sm native:text-base text-muted-foreground", className)}
150 | 		{...props}
151 | 	/>
152 | ));
153 | DialogDescription.displayName = DialogPrimitive.Description.displayName;
154 | 
155 | export {
156 | 	Dialog,
157 | 	DialogClose,
158 | 	DialogContent,
159 | 	DialogDescription,
160 | 	DialogFooter,
161 | 	DialogHeader,
162 | 	DialogOverlay,
163 | 	DialogPortal,
164 | 	DialogTitle,
165 | 	DialogTrigger,
166 | };
167 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/oidc-provider/ui.ts:
--------------------------------------------------------------------------------

```typescript
  1 | export const authorizeHTML = ({
  2 | 	scopes,
  3 | 	clientIcon,
  4 | 	clientName,
  5 | 	redirectURI,
  6 | 	cancelURI,
  7 | }: {
  8 | 	scopes: string[];
  9 | 	clientIcon?: string;
 10 | 	clientName: string;
 11 | 	redirectURI: string;
 12 | 	cancelURI: string;
 13 | 	clientMetadata?: Record<string, any>;
 14 | }) => `<!DOCTYPE html>
 15 |   <html lang="en">
 16 |   <head>
 17 |       <meta charset="UTF-8">
 18 |       <meta clientName="viewport" content="width=device-width, initial-scale=1.0">
 19 |       <title>Authorize Application</title>
 20 |       <style>
 21 |           :root {
 22 |               --bg-color: #000000;
 23 |               --card-color: #1a1a1a;
 24 |               --text-primary: #ffffff;
 25 |               --text-secondary: #b0b0b0;
 26 |               --border-color: #333333;
 27 |               --button-color: #ffffff;
 28 |               --button-text: #000000;
 29 |           }
 30 |           body {
 31 |               font-family: 'Inter', 'Helvetica', 'Arial', sans-serif;
 32 |               background-color: var(--bg-color);
 33 |               color: var(--text-primary);
 34 |               display: flex;
 35 |               justify-content: center;
 36 |               align-items: center;
 37 |               min-height: 100vh;
 38 |               margin: 0;
 39 |               padding: 20px;
 40 |               box-sizing: border-box;
 41 |           }
 42 |           .authorize-container {
 43 |               background-color: var(--card-color);
 44 |               border: 1px solid var(--border-color);
 45 |               padding: 32px;
 46 |               width: 100%;
 47 |               max-width: 420px;
 48 |               box-shadow: 0 8px 24px rgba(255,255,255,0.1);
 49 |           }
 50 |           .app-info {
 51 |               display: flex;
 52 |               align-items: center;
 53 |               margin-bottom: 24px;
 54 |           }
 55 |           .app-clientIcon {
 56 |               width: 64px;
 57 |               height: 64px;
 58 |               margin-right: 16px;
 59 |               object-fit: cover;
 60 |           }
 61 |           .app-clientName {
 62 |               font-size: 24px;
 63 |               font-weight: 700;
 64 |           }
 65 |           .permissions-list {
 66 |               background-color: rgba(255, 255, 255, 0.05);
 67 |               border: 1px solid var(--border-color);
 68 |               padding: 16px;
 69 |               margin-bottom: 24px;
 70 |           }
 71 |           .permissions-list h3 {
 72 |               margin-top: 0;
 73 |               font-size: 16px;
 74 |               color: var(--text-secondary);
 75 |               margin-bottom: 12px;
 76 |           }
 77 |           .permissions-list ul {
 78 |               list-style-type: none;
 79 |               padding: 0;
 80 |               margin: 0;
 81 |           }
 82 |           .permissions-list li {
 83 |               margin-bottom: 8px;
 84 |               display: flex;
 85 |               align-items: center;
 86 |           }
 87 |           .permissions-list li::before {
 88 |               content: "•";
 89 |               color: var(--text-primary);
 90 |               font-size: 18px;
 91 |               margin-right: 8px;
 92 |           }
 93 |           .buttons {
 94 |               display: flex;
 95 |               justify-content: flex-end;
 96 |               gap: 12px;
 97 |           }
 98 |           .button {
 99 |               padding: 10px 20px;
100 |               border: none;
101 |               font-size: 14px;
102 |               font-weight: 600;
103 |               cursor: pointer;
104 |               transition: all 0.2s ease;
105 |           }
106 |           .authorize {
107 |               background-color: var(--button-color);
108 |               color: var(--button-text);
109 |           }
110 |           .authorize:hover {
111 |               opacity: 0.9;
112 |           }
113 |           .cancel {
114 |               background-color: transparent;
115 |               color: var(--text-secondary);
116 |               border: 1px solid var(--text-secondary);
117 |           }
118 |           .cancel:hover {
119 |               background-color: rgba(255, 255, 255, 0.1);
120 |           }
121 |       </style>
122 |   </head>
123 |   <body>
124 |       <div class="authorize-container">
125 |           <div class="app-info">
126 |               <img src="${clientIcon || ""}" alt="${clientName} clientIcon" class="app-clientIcon">
127 |               <span class="app-clientName">${clientName}</span>
128 |           </div>
129 |           <p>${clientName} would like permission to access your account</p>
130 |           <div class="permissions-list">
131 |               <h3>This will allow ${clientName} to:</h3>
132 |               <ul>
133 |                   ${scopes.map((scope) => `<li>${scope}</li>`).join("")}
134 |               </ul>
135 |           </div>
136 |           <div class="buttons">
137 |                 <a href="${cancelURI}" class="button cancel">Cancel</a>
138 |                <a href="${redirectURI}" class="button authorize">Authorize</a>
139 |           </div>
140 |       </div>
141 |   </body>
142 |   </html>`;
143 | 
```
Page 14/69FirstPrevNextLast