#
tokens: 49701/50000 19/1099 files (page 15/69)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 15 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

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

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import { Command as CommandPrimitive } from "cmdk";
  5 | import { SearchIcon } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | import {
  9 | 	Dialog,
 10 | 	DialogContent,
 11 | 	DialogDescription,
 12 | 	DialogHeader,
 13 | 	DialogTitle,
 14 | } from "@/components/ui/dialog";
 15 | 
 16 | function Command({
 17 | 	className,
 18 | 	...props
 19 | }: React.ComponentProps<typeof CommandPrimitive>) {
 20 | 	return (
 21 | 		<CommandPrimitive
 22 | 			data-slot="command"
 23 | 			className={cn(
 24 | 				"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
 25 | 				className,
 26 | 			)}
 27 | 			{...props}
 28 | 		/>
 29 | 	);
 30 | }
 31 | 
 32 | function CommandDialog({
 33 | 	title = "Command Palette",
 34 | 	description = "Search for a command to run...",
 35 | 	children,
 36 | 	...props
 37 | }: React.ComponentProps<typeof Dialog> & {
 38 | 	title?: string;
 39 | 	description?: string;
 40 | }) {
 41 | 	return (
 42 | 		<Dialog {...props}>
 43 | 			<DialogHeader className="sr-only">
 44 | 				<DialogTitle>{title}</DialogTitle>
 45 | 				<DialogDescription>{description}</DialogDescription>
 46 | 			</DialogHeader>
 47 | 			<DialogContent className="overflow-hidden p-0">
 48 | 				<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
 49 | 					{children}
 50 | 				</Command>
 51 | 			</DialogContent>
 52 | 		</Dialog>
 53 | 	);
 54 | }
 55 | 
 56 | function CommandInput({
 57 | 	className,
 58 | 	...props
 59 | }: React.ComponentProps<typeof CommandPrimitive.Input>) {
 60 | 	return (
 61 | 		<div
 62 | 			data-slot="command-input-wrapper"
 63 | 			className="flex h-9 items-center gap-2 border-b px-3"
 64 | 		>
 65 | 			<SearchIcon className="size-4 shrink-0 opacity-50" />
 66 | 			<CommandPrimitive.Input
 67 | 				data-slot="command-input"
 68 | 				className={cn(
 69 | 					"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
 70 | 					className,
 71 | 				)}
 72 | 				{...props}
 73 | 			/>
 74 | 		</div>
 75 | 	);
 76 | }
 77 | 
 78 | function CommandList({
 79 | 	className,
 80 | 	...props
 81 | }: React.ComponentProps<typeof CommandPrimitive.List>) {
 82 | 	return (
 83 | 		<CommandPrimitive.List
 84 | 			data-slot="command-list"
 85 | 			className={cn(
 86 | 				"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
 87 | 				className,
 88 | 			)}
 89 | 			{...props}
 90 | 		/>
 91 | 	);
 92 | }
 93 | 
 94 | function CommandEmpty({
 95 | 	...props
 96 | }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
 97 | 	return (
 98 | 		<CommandPrimitive.Empty
 99 | 			data-slot="command-empty"
100 | 			className="py-6 text-center text-sm"
101 | 			{...props}
102 | 		/>
103 | 	);
104 | }
105 | 
106 | function CommandGroup({
107 | 	className,
108 | 	...props
109 | }: React.ComponentProps<typeof CommandPrimitive.Group>) {
110 | 	return (
111 | 		<CommandPrimitive.Group
112 | 			data-slot="command-group"
113 | 			className={cn(
114 | 				"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
115 | 				className,
116 | 			)}
117 | 			{...props}
118 | 		/>
119 | 	);
120 | }
121 | 
122 | function CommandSeparator({
123 | 	className,
124 | 	...props
125 | }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
126 | 	return (
127 | 		<CommandPrimitive.Separator
128 | 			data-slot="command-separator"
129 | 			className={cn("bg-border -mx-1 h-px", className)}
130 | 			{...props}
131 | 		/>
132 | 	);
133 | }
134 | 
135 | function CommandItem({
136 | 	className,
137 | 	...props
138 | }: React.ComponentProps<typeof CommandPrimitive.Item>) {
139 | 	return (
140 | 		<CommandPrimitive.Item
141 | 			data-slot="command-item"
142 | 			className={cn(
143 | 				"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
144 | 				className,
145 | 			)}
146 | 			{...props}
147 | 		/>
148 | 	);
149 | }
150 | 
151 | function CommandShortcut({
152 | 	className,
153 | 	...props
154 | }: React.ComponentProps<"span">) {
155 | 	return (
156 | 		<span
157 | 			data-slot="command-shortcut"
158 | 			className={cn(
159 | 				"text-muted-foreground ml-auto text-xs tracking-widest",
160 | 				className,
161 | 			)}
162 | 			{...props}
163 | 		/>
164 | 	);
165 | }
166 | 
167 | export {
168 | 	Command,
169 | 	CommandDialog,
170 | 	CommandInput,
171 | 	CommandList,
172 | 	CommandEmpty,
173 | 	CommandGroup,
174 | 	CommandItem,
175 | 	CommandShortcut,
176 | 	CommandSeparator,
177 | };
178 | 
```

--------------------------------------------------------------------------------
/docs/components/ui/command.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import { Command as CommandPrimitive } from "cmdk";
  5 | import { SearchIcon } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | import {
  9 | 	Dialog,
 10 | 	DialogContent,
 11 | 	DialogDescription,
 12 | 	DialogHeader,
 13 | 	DialogTitle,
 14 | } from "@/components/ui/dialog";
 15 | 
 16 | function Command({
 17 | 	className,
 18 | 	...props
 19 | }: React.ComponentProps<typeof CommandPrimitive>) {
 20 | 	return (
 21 | 		<CommandPrimitive
 22 | 			data-slot="command"
 23 | 			className={cn(
 24 | 				"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
 25 | 				className,
 26 | 			)}
 27 | 			{...props}
 28 | 		/>
 29 | 	);
 30 | }
 31 | 
 32 | function CommandDialog({
 33 | 	title = "Command Palette",
 34 | 	description = "Search for a command to run...",
 35 | 	children,
 36 | 	...props
 37 | }: React.ComponentProps<typeof Dialog> & {
 38 | 	title?: string;
 39 | 	description?: string;
 40 | }) {
 41 | 	return (
 42 | 		<Dialog {...props}>
 43 | 			<DialogHeader className="sr-only">
 44 | 				<DialogTitle>{title}</DialogTitle>
 45 | 				<DialogDescription>{description}</DialogDescription>
 46 | 			</DialogHeader>
 47 | 			<DialogContent className="overflow-hidden p-0">
 48 | 				<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
 49 | 					{children}
 50 | 				</Command>
 51 | 			</DialogContent>
 52 | 		</Dialog>
 53 | 	);
 54 | }
 55 | 
 56 | function CommandInput({
 57 | 	className,
 58 | 	...props
 59 | }: React.ComponentProps<typeof CommandPrimitive.Input>) {
 60 | 	return (
 61 | 		<div
 62 | 			data-slot="command-input-wrapper"
 63 | 			className="flex h-9 items-center gap-2 border-b px-3"
 64 | 		>
 65 | 			<SearchIcon className="size-4 shrink-0 opacity-50" />
 66 | 			<CommandPrimitive.Input
 67 | 				data-slot="command-input"
 68 | 				className={cn(
 69 | 					"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
 70 | 					className,
 71 | 				)}
 72 | 				{...props}
 73 | 			/>
 74 | 		</div>
 75 | 	);
 76 | }
 77 | 
 78 | function CommandList({
 79 | 	className,
 80 | 	...props
 81 | }: React.ComponentProps<typeof CommandPrimitive.List>) {
 82 | 	return (
 83 | 		<CommandPrimitive.List
 84 | 			data-slot="command-list"
 85 | 			className={cn(
 86 | 				"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
 87 | 				className,
 88 | 			)}
 89 | 			{...props}
 90 | 		/>
 91 | 	);
 92 | }
 93 | 
 94 | function CommandEmpty({
 95 | 	...props
 96 | }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
 97 | 	return (
 98 | 		<CommandPrimitive.Empty
 99 | 			data-slot="command-empty"
100 | 			className="py-6 text-center text-sm"
101 | 			{...props}
102 | 		/>
103 | 	);
104 | }
105 | 
106 | function CommandGroup({
107 | 	className,
108 | 	...props
109 | }: React.ComponentProps<typeof CommandPrimitive.Group>) {
110 | 	return (
111 | 		<CommandPrimitive.Group
112 | 			data-slot="command-group"
113 | 			className={cn(
114 | 				"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
115 | 				className,
116 | 			)}
117 | 			{...props}
118 | 		/>
119 | 	);
120 | }
121 | 
122 | function CommandSeparator({
123 | 	className,
124 | 	...props
125 | }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
126 | 	return (
127 | 		<CommandPrimitive.Separator
128 | 			data-slot="command-separator"
129 | 			className={cn("bg-border -mx-1 h-px", className)}
130 | 			{...props}
131 | 		/>
132 | 	);
133 | }
134 | 
135 | function CommandItem({
136 | 	className,
137 | 	...props
138 | }: React.ComponentProps<typeof CommandPrimitive.Item>) {
139 | 	return (
140 | 		<CommandPrimitive.Item
141 | 			data-slot="command-item"
142 | 			className={cn(
143 | 				"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
144 | 				className,
145 | 			)}
146 | 			{...props}
147 | 		/>
148 | 	);
149 | }
150 | 
151 | function CommandShortcut({
152 | 	className,
153 | 	...props
154 | }: React.ComponentProps<"span">) {
155 | 	return (
156 | 		<span
157 | 			data-slot="command-shortcut"
158 | 			className={cn(
159 | 				"text-muted-foreground ml-auto text-xs tracking-widest",
160 | 				className,
161 | 			)}
162 | 			{...props}
163 | 		/>
164 | 	);
165 | }
166 | 
167 | export {
168 | 	Command,
169 | 	CommandDialog,
170 | 	CommandInput,
171 | 	CommandList,
172 | 	CommandEmpty,
173 | 	CommandGroup,
174 | 	CommandItem,
175 | 	CommandShortcut,
176 | 	CommandSeparator,
177 | };
178 | 
```

--------------------------------------------------------------------------------
/docs/components/message-feedback.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import { useState } from "react";
  4 | import { Check, Copy, ThumbsDown, ThumbsUp } from "lucide-react";
  5 | import { cn } from "@/lib/utils";
  6 | import { buttonVariants } from "fumadocs-ui/components/ui/button";
  7 | import {
  8 | 	submitFeedbackToInkeep,
  9 | 	logEventToInkeep,
 10 | } from "@/lib/inkeep-analytics";
 11 | 
 12 | interface MessageFeedbackProps {
 13 | 	messageId: string;
 14 | 	userMessageId?: string;
 15 | 	content: string;
 16 | 	className?: string;
 17 | }
 18 | 
 19 | export function MessageFeedback({
 20 | 	messageId,
 21 | 	userMessageId,
 22 | 	content,
 23 | 	className,
 24 | }: MessageFeedbackProps) {
 25 | 	const [feedback, setFeedback] = useState<"positive" | "negative" | null>(
 26 | 		null,
 27 | 	);
 28 | 	const [copied, setCopied] = useState(false);
 29 | 	const [isSubmittingFeedback, setIsSubmittingFeedback] = useState(false);
 30 | 	const [showSuccessCheckmark, setShowSuccessCheckmark] = useState<
 31 | 		"positive" | "negative" | null
 32 | 	>(null);
 33 | 
 34 | 	const handleFeedback = async (type: "positive" | "negative") => {
 35 | 		if (isSubmittingFeedback || feedback === type) return;
 36 | 
 37 | 		const feedbackMessageId = userMessageId || messageId;
 38 | 
 39 | 		setIsSubmittingFeedback(true);
 40 | 
 41 | 		try {
 42 | 			await submitFeedbackToInkeep(feedbackMessageId, type, [
 43 | 				{
 44 | 					label:
 45 | 						type === "positive" ? "helpful_response" : "unhelpful_response",
 46 | 					details:
 47 | 						type === "positive"
 48 | 							? "The response was helpful"
 49 | 							: "The response was not helpful",
 50 | 				},
 51 | 			]);
 52 | 
 53 | 			setFeedback(type);
 54 | 			setShowSuccessCheckmark(type);
 55 | 
 56 | 			setTimeout(() => {
 57 | 				setShowSuccessCheckmark(null);
 58 | 			}, 1000);
 59 | 		} catch (error) {
 60 | 		} finally {
 61 | 			setIsSubmittingFeedback(false);
 62 | 		}
 63 | 	};
 64 | 
 65 | 	const handleCopy = async () => {
 66 | 		if (copied) return;
 67 | 
 68 | 		const eventMessageId = userMessageId || messageId;
 69 | 
 70 | 		try {
 71 | 			await navigator.clipboard.writeText(content);
 72 | 			setCopied(true);
 73 | 
 74 | 			await logEventToInkeep("message:copied", "message", eventMessageId);
 75 | 
 76 | 			setTimeout(() => setCopied(false), 2000);
 77 | 		} catch (error) {
 78 | 			// Silently handle error
 79 | 		}
 80 | 	};
 81 | 
 82 | 	return (
 83 | 		<div
 84 | 			className={cn(
 85 | 				"flex items-center gap-1 mt-3 pt-2 border-t border-fd-border/30",
 86 | 				className,
 87 | 			)}
 88 | 		>
 89 | 			<button
 90 | 				type="button"
 91 | 				onClick={() => handleFeedback("positive")}
 92 | 				disabled={isSubmittingFeedback}
 93 | 				className={cn(
 94 | 					buttonVariants({
 95 | 						size: "icon-sm",
 96 | 						color: feedback === "positive" ? "primary" : "ghost",
 97 | 						className: cn(
 98 | 							"h-7 w-7 transition-colors",
 99 | 							isSubmittingFeedback && "opacity-50 cursor-not-allowed",
100 | 							feedback === "positive"
101 | 								? "bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50"
102 | 								: "hover:bg-fd-accent hover:text-fd-accent-foreground",
103 | 						),
104 | 					}),
105 | 				)}
106 | 				title={
107 | 					showSuccessCheckmark === "positive"
108 | 						? "Feedback submitted!"
109 | 						: "Helpful"
110 | 				}
111 | 			>
112 | 				{showSuccessCheckmark === "positive" ? (
113 | 					<Check className="h-3.5 w-3.5 text-green-600 animate-in fade-in duration-200" />
114 | 				) : (
115 | 					<ThumbsUp className="h-3.5 w-3.5 transition-all duration-200" />
116 | 				)}
117 | 			</button>
118 | 
119 | 			<button
120 | 				type="button"
121 | 				onClick={() => handleFeedback("negative")}
122 | 				disabled={isSubmittingFeedback}
123 | 				className={cn(
124 | 					buttonVariants({
125 | 						size: "icon-sm",
126 | 						color: feedback === "negative" ? "primary" : "ghost",
127 | 						className: cn(
128 | 							"h-7 w-7 transition-colors",
129 | 							isSubmittingFeedback && "opacity-50 cursor-not-allowed",
130 | 							feedback === "negative"
131 | 								? "bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400 dark:hover:bg-red-900/50"
132 | 								: "hover:bg-fd-accent hover:text-fd-accent-foreground",
133 | 						),
134 | 					}),
135 | 				)}
136 | 				title={
137 | 					showSuccessCheckmark === "negative"
138 | 						? "Feedback submitted!"
139 | 						: "Not helpful"
140 | 				}
141 | 			>
142 | 				{showSuccessCheckmark === "negative" ? (
143 | 					<Check className="h-3.5 w-3.5 text-green-600 animate-in fade-in duration-200" />
144 | 				) : (
145 | 					<ThumbsDown className="h-3.5 w-3.5 transition-all duration-200" />
146 | 				)}
147 | 			</button>
148 | 
149 | 			<button
150 | 				type="button"
151 | 				onClick={handleCopy}
152 | 				className={cn(
153 | 					buttonVariants({
154 | 						size: "icon-sm",
155 | 						color: "ghost",
156 | 						className:
157 | 							"h-7 w-7 hover:bg-fd-accent hover:text-fd-accent-foreground transition-colors",
158 | 					}),
159 | 				)}
160 | 				title={copied ? "Copied!" : "Copy message"}
161 | 			>
162 | 				{copied ? (
163 | 					<Check className="h-3.5 w-3.5 text-green-600" />
164 | 				) : (
165 | 					<Copy className="h-3.5 w-3.5" />
166 | 				)}
167 | 			</button>
168 | 		</div>
169 | 	);
170 | }
171 | 
```

--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "name": "@better-auth/core",
  3 |   "version": "1.4.0-beta.12",
  4 |   "description": "The most comprehensive authentication library for TypeScript.",
  5 |   "type": "module",
  6 |   "main": "./dist/index.js",
  7 |   "module": "./dist/index.js",
  8 |   "exports": {
  9 |     ".": {
 10 |       "import": {
 11 |         "types": "./dist/index.d.ts",
 12 |         "default": "./dist/index.js"
 13 |       },
 14 |       "require": {
 15 |         "types": "./dist/index.d.cts",
 16 |         "default": "./dist/index.cjs"
 17 |       }
 18 |     },
 19 |     "./api": {
 20 |       "import": {
 21 |         "types": "./dist/api/index.d.ts",
 22 |         "default": "./dist/api/index.js"
 23 |       },
 24 |       "require": {
 25 |         "types": "./dist/api/index.d.cts",
 26 |         "default": "./dist/api/index.cjs"
 27 |       }
 28 |     },
 29 |     "./async_hooks": {
 30 |       "import": {
 31 |         "types": "./dist/async_hooks/index.d.ts",
 32 |         "default": "./dist/async_hooks/index.js"
 33 |       },
 34 |       "require": {
 35 |         "types": "./dist/async_hooks/index.d.cts",
 36 |         "default": "./dist/async_hooks/index.cjs"
 37 |       }
 38 |     },
 39 |     "./context": {
 40 |       "import": {
 41 |         "types": "./dist/context/index.d.ts",
 42 |         "default": "./dist/context/index.js"
 43 |       },
 44 |       "require": {
 45 |         "types": "./dist/context/index.d.cts",
 46 |         "default": "./dist/context/index.cjs"
 47 |       }
 48 |     },
 49 |     "./env": {
 50 |       "import": {
 51 |         "types": "./dist/env/index.d.ts",
 52 |         "default": "./dist/env/index.js"
 53 |       },
 54 |       "require": {
 55 |         "types": "./dist/env/index.d.cts",
 56 |         "default": "./dist/env/index.cjs"
 57 |       }
 58 |     },
 59 |     "./error": {
 60 |       "import": {
 61 |         "types": "./dist/error/index.d.ts",
 62 |         "default": "./dist/error/index.js"
 63 |       },
 64 |       "require": {
 65 |         "types": "./dist/error/index.d.cts",
 66 |         "default": "./dist/error/index.cjs"
 67 |       }
 68 |     },
 69 |     "./utils": {
 70 |       "import": {
 71 |         "types": "./dist/utils/index.d.ts",
 72 |         "default": "./dist/utils/index.js"
 73 |       },
 74 |       "require": {
 75 |         "types": "./dist/utils/index.d.cts",
 76 |         "default": "./dist/utils/index.cjs"
 77 |       }
 78 |     },
 79 |     "./social-providers": {
 80 |       "import": {
 81 |         "types": "./dist/social-providers/index.d.ts",
 82 |         "default": "./dist/social-providers/index.js"
 83 |       },
 84 |       "require": {
 85 |         "types": "./dist/social-providers/index.d.cts",
 86 |         "default": "./dist/social-providers/index.cjs"
 87 |       }
 88 |     },
 89 |     "./db": {
 90 |       "import": {
 91 |         "types": "./dist/db/index.d.ts",
 92 |         "default": "./dist/db/index.js"
 93 |       },
 94 |       "require": {
 95 |         "types": "./dist/db/index.d.cts",
 96 |         "default": "./dist/db/index.cjs"
 97 |       }
 98 |     },
 99 |     "./db/adapter": {
100 |       "import": {
101 |         "types": "./dist/db/adapter/index.d.ts",
102 |         "default": "./dist/db/adapter/index.js"
103 |       },
104 |       "require": {
105 |         "types": "./dist/db/adapter/index.d.cts",
106 |         "default": "./dist/db/adapter/index.cjs"
107 |       }
108 |     },
109 |     "./oauth2": {
110 |       "import": {
111 |         "types": "./dist/oauth2/index.d.ts",
112 |         "default": "./dist/oauth2/index.js"
113 |       },
114 |       "require": {
115 |         "types": "./dist/oauth2/index.d.cts",
116 |         "default": "./dist/oauth2/index.cjs"
117 |       }
118 |     }
119 |   },
120 |   "typesVersions": {
121 |     "*": {
122 |       "index": [
123 |         "dist/index.d.ts"
124 |       ],
125 |       "async_hooks": [
126 |         "dist/async_hooks.d.ts"
127 |       ],
128 |       "db": [
129 |         "dist/db.d.ts"
130 |       ],
131 |       "db/adapter": [
132 |         "dist/db/adapter/index.d.ts"
133 |       ],
134 |       "env": [
135 |         "dist/env.d.ts"
136 |       ],
137 |       "error": [
138 |         "dist/error.d.ts"
139 |       ],
140 |       "middleware": [
141 |         "dist/middleware.d.ts"
142 |       ],
143 |       "oauth2": [
144 |         "dist/oauth2.d.ts"
145 |       ],
146 |       "social-providers": [
147 |         "dist/social-providers.d.ts"
148 |       ],
149 |       "utils": [
150 |         "dist/utils.d.ts"
151 |       ]
152 |     }
153 |   },
154 |   "scripts": {
155 |     "build": "tsdown",
156 |     "dev": "tsdown --watch",
157 |     "typecheck": "tsc --project tsconfig.json"
158 |   },
159 |   "devDependencies": {
160 |     "@better-auth/utils": "0.3.0",
161 |     "@better-fetch/fetch": "catalog:",
162 |     "@types/better-sqlite3": "^7.6.13",
163 |     "better-call": "catalog:",
164 |     "better-sqlite3": "^12.4.1",
165 |     "jose": "^6.1.0",
166 |     "kysely": "^0.28.5",
167 |     "nanostores": "^1.0.1",
168 |     "tsdown": "catalog:"
169 |   },
170 |   "dependencies": {
171 |     "zod": "^4.1.5"
172 |   },
173 |   "peerDependencies": {
174 |     "@better-auth/utils": "0.3.0",
175 |     "@better-fetch/fetch": "catalog:",
176 |     "better-call": "catalog:",
177 |     "better-sqlite3": "^12.4.1",
178 |     "jose": "^6.1.0",
179 |     "kysely": "^0.28.5",
180 |     "nanostores": "^1.0.1"
181 |   }
182 | }
183 | 
```

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

```typescript
  1 | import type { JWTPayload } from "jose";
  2 | import type { InferOptionSchema, Session, User } from "../../types";
  3 | import type { Awaitable } from "../../types/helper";
  4 | import type { schema } from "./schema";
  5 | 
  6 | export interface JwtOptions {
  7 | 	jwks?: {
  8 | 		/**
  9 | 		 * Disables the /jwks endpoint and uses this endpoint in discovery.
 10 | 		 *
 11 | 		 * Useful if jwks are not managed at /jwks or
 12 | 		 * if your jwks are signed with a certificate and placed on your CDN.
 13 | 		 */
 14 | 		remoteUrl?: string;
 15 | 
 16 | 		/**
 17 | 		 * Key pair configuration
 18 | 		 * @description A subset of the options available for the generateKeyPair function
 19 | 		 *
 20 | 		 * @see https://github.com/panva/jose/blob/main/src/runtime/node/generate.ts
 21 | 		 *
 22 | 		 * @default { alg: 'EdDSA', crv: 'Ed25519' }
 23 | 		 */
 24 | 		keyPairConfig?: JWKOptions;
 25 | 
 26 | 		/**
 27 | 		 * Disable private key encryption
 28 | 		 * @description Disable the encryption of the private key in the database
 29 | 		 *
 30 | 		 * @default false
 31 | 		 */
 32 | 		disablePrivateKeyEncryption?: boolean;
 33 | 	};
 34 | 
 35 | 	jwt?: {
 36 | 		/**
 37 | 		 * The issuer of the JWT
 38 | 		 */
 39 | 		issuer?: string;
 40 | 		/**
 41 | 		 * The audience of the JWT
 42 | 		 */
 43 | 		audience?: string;
 44 | 		/**
 45 | 		 * Set the "exp" (Expiration Time) Claim.
 46 | 		 *
 47 | 		 * - If a `number` is passed as an argument it is used as the claim directly.
 48 | 		 * - If a `Date` instance is passed as an argument it is converted to unix timestamp and used as the
 49 | 		 *   claim.
 50 | 		 * - If a `string` is passed as an argument it is resolved to a time span, and then added to the
 51 | 		 *   current unix timestamp and used as the claim.
 52 | 		 *
 53 | 		 * Format used for time span should be a number followed by a unit, such as "5 minutes" or "1
 54 | 		 * day".
 55 | 		 *
 56 | 		 * Valid units are: "sec", "secs", "second", "seconds", "s", "minute", "minutes", "min", "mins",
 57 | 		 * "m", "hour", "hours", "hr", "hrs", "h", "day", "days", "d", "week", "weeks", "w", "year",
 58 | 		 * "years", "yr", "yrs", and "y". It is not possible to specify months. 365.25 days is used as an
 59 | 		 * alias for a year.
 60 | 		 *
 61 | 		 * If the string is suffixed with "ago", or prefixed with a "-", the resulting time span gets
 62 | 		 * subtracted from the current unix timestamp. A "from now" suffix can also be used for
 63 | 		 * readability when adding to the current unix timestamp.
 64 | 		 *
 65 | 		 * @default 15m
 66 | 		 */
 67 | 		expirationTime?: number | string | Date;
 68 | 		/**
 69 | 		 * A function that is called to define the payload of the JWT
 70 | 		 */
 71 | 		definePayload?: (session: {
 72 | 			user: User & Record<string, any>;
 73 | 			session: Session & Record<string, any>;
 74 | 		}) => Promise<Record<string, any>> | Record<string, any>;
 75 | 		/**
 76 | 		 * A function that is called to get the subject of the JWT
 77 | 		 *
 78 | 		 * @default session.user.id
 79 | 		 */
 80 | 		getSubject?: (session: {
 81 | 			user: User & Record<string, any>;
 82 | 			session: Session & Record<string, any>;
 83 | 		}) => Promise<string> | string;
 84 | 		/**
 85 | 		 * A custom function to remote sign the jwt payload.
 86 | 		 *
 87 | 		 * All headers, such as `alg` and `kid`,
 88 | 		 * MUST be defined within this function.
 89 | 		 * You can safely define the header `typ: 'JWT'`.
 90 | 		 *
 91 | 		 * @requires jwks.remoteUrl
 92 | 		 * @invalidates other jwt.* options
 93 | 		 */
 94 | 		sign?: (payload: JWTPayload) => Awaitable<string>;
 95 | 	};
 96 | 
 97 | 	/**
 98 | 	 * Disables setting JWTs through middleware.
 99 | 	 *
100 | 	 * Recommended to set `true` when using an oAuth provider plugin
101 | 	 * like OIDC or MCP where session payloads should not be signed.
102 | 	 *
103 | 	 * @default false
104 | 	 */
105 | 	disableSettingJwtHeader?: boolean;
106 | 
107 | 	/**
108 | 	 * Custom schema for the admin plugin
109 | 	 */
110 | 	schema?: InferOptionSchema<typeof schema>;
111 | }
112 | 
113 | /**
114 |  * Asymmetric (JWS) Supported.
115 |  *
116 |  * @see https://github.com/panva/jose/issues/210
117 |  */
118 | // JWE is symmetric (ie sharing a secret) thus a jwks is not applicable since there is no public key to share.
119 | // All new JWK "alg" and/or "crv" MUST have an associated test in jwt.test.ts
120 | export type JWKOptions =
121 | 	| {
122 | 			alg: "EdDSA"; // EdDSA with Ed25519 key
123 | 			crv?: "Ed25519";
124 | 	  }
125 | 	| {
126 | 			alg: "ES256"; // ECDSA with P-256 curve
127 | 			crv?: never; // Only one valid option, no need for crv
128 | 	  }
129 | 	| {
130 | 			alg: "ES512"; // ECDSA with P-521 curve
131 | 			crv?: never; // Only P-521 for ES512
132 | 	  }
133 | 	| {
134 | 			alg: "PS256"; // RSA-PSS with SHA-256
135 | 			modulusLength?: number; // Default to 2048 or higher
136 | 	  }
137 | 	| {
138 | 			alg: "RS256"; // RSA with SHA-256
139 | 			modulusLength?: number; // Default to 2048 or higher
140 | 	  };
141 | 
142 | export type JWSAlgorithms = JWKOptions["alg"];
143 | 
144 | export interface Jwk {
145 | 	id: string;
146 | 	publicKey: string;
147 | 	privateKey: string;
148 | 	createdAt: Date;
149 | 	alg?: JWSAlgorithms;
150 | 	crv?: "Ed25519" | "P-256" | "P-521";
151 | }
152 | 
```

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

```typescript
  1 | import * as z from "zod";
  2 | import { defaultKeyHasher } from "..";
  3 | import { createAuthEndpoint } from "@better-auth/core/api";
  4 | import { sessionMiddleware } from "../../api";
  5 | import { generateRandomString } from "../../crypto";
  6 | import type { BetterAuthPlugin } from "@better-auth/core";
  7 | import type { Session, User } from "../../types";
  8 | import type { GenericEndpointContext } from "@better-auth/core";
  9 | 
 10 | interface OneTimeTokenOptions {
 11 | 	/**
 12 | 	 * Expires in minutes
 13 | 	 *
 14 | 	 * @default 3
 15 | 	 */
 16 | 	expiresIn?: number;
 17 | 	/**
 18 | 	 * Only allow server initiated requests
 19 | 	 */
 20 | 	disableClientRequest?: boolean;
 21 | 	/**
 22 | 	 * Generate a custom token
 23 | 	 */
 24 | 	generateToken?: (
 25 | 		session: {
 26 | 			user: User & Record<string, any>;
 27 | 			session: Session & Record<string, any>;
 28 | 		},
 29 | 		ctx: GenericEndpointContext,
 30 | 	) => Promise<string>;
 31 | 	/**
 32 | 	 * This option allows you to configure how the token is stored in your database.
 33 | 	 * Note: This will not affect the token that's sent, it will only affect the token stored in your database.
 34 | 	 *
 35 | 	 * @default "plain"
 36 | 	 */
 37 | 	storeToken?:
 38 | 		| "plain"
 39 | 		| "hashed"
 40 | 		| { type: "custom-hasher"; hash: (token: string) => Promise<string> };
 41 | }
 42 | 
 43 | export const oneTimeToken = (options?: OneTimeTokenOptions) => {
 44 | 	const opts = {
 45 | 		storeToken: "plain",
 46 | 		...options,
 47 | 	} satisfies OneTimeTokenOptions;
 48 | 
 49 | 	async function storeToken(ctx: GenericEndpointContext, token: string) {
 50 | 		if (opts.storeToken === "hashed") {
 51 | 			return await defaultKeyHasher(token);
 52 | 		}
 53 | 		if (
 54 | 			typeof opts.storeToken === "object" &&
 55 | 			"type" in opts.storeToken &&
 56 | 			opts.storeToken.type === "custom-hasher"
 57 | 		) {
 58 | 			return await opts.storeToken.hash(token);
 59 | 		}
 60 | 
 61 | 		return token;
 62 | 	}
 63 | 
 64 | 	return {
 65 | 		id: "one-time-token",
 66 | 		endpoints: {
 67 | 			/**
 68 | 			 * ### Endpoint
 69 | 			 *
 70 | 			 * GET `/one-time-token/generate`
 71 | 			 *
 72 | 			 * ### API Methods
 73 | 			 *
 74 | 			 * **server:**
 75 | 			 * `auth.api.generateOneTimeToken`
 76 | 			 *
 77 | 			 * **client:**
 78 | 			 * `authClient.oneTimeToken.generate`
 79 | 			 *
 80 | 			 * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/one-time-token#api-method-one-time-token-generate)
 81 | 			 */
 82 | 			generateOneTimeToken: createAuthEndpoint(
 83 | 				"/one-time-token/generate",
 84 | 				{
 85 | 					method: "GET",
 86 | 					use: [sessionMiddleware],
 87 | 				},
 88 | 				async (c) => {
 89 | 					//if request exist, it means it's a client request
 90 | 					if (opts?.disableClientRequest && c.request) {
 91 | 						throw c.error("BAD_REQUEST", {
 92 | 							message: "Client requests are disabled",
 93 | 						});
 94 | 					}
 95 | 					const session = c.context.session;
 96 | 					const token = opts?.generateToken
 97 | 						? await opts.generateToken(session, c)
 98 | 						: generateRandomString(32);
 99 | 					const expiresAt = new Date(
100 | 						Date.now() + (opts?.expiresIn ?? 3) * 60 * 1000,
101 | 					);
102 | 					const storedToken = await storeToken(c, token);
103 | 					await c.context.internalAdapter.createVerificationValue({
104 | 						value: session.session.token,
105 | 						identifier: `one-time-token:${storedToken}`,
106 | 						expiresAt,
107 | 					});
108 | 					return c.json({ token });
109 | 				},
110 | 			),
111 | 			/**
112 | 			 * ### Endpoint
113 | 			 *
114 | 			 * POST `/one-time-token/verify`
115 | 			 *
116 | 			 * ### API Methods
117 | 			 *
118 | 			 * **server:**
119 | 			 * `auth.api.verifyOneTimeToken`
120 | 			 *
121 | 			 * **client:**
122 | 			 * `authClient.oneTimeToken.verify`
123 | 			 *
124 | 			 * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/one-time-token#api-method-one-time-token-verify)
125 | 			 */
126 | 			verifyOneTimeToken: createAuthEndpoint(
127 | 				"/one-time-token/verify",
128 | 				{
129 | 					method: "POST",
130 | 					body: z.object({
131 | 						token: z.string().meta({
132 | 							description: 'The token to verify. Eg: "some-token"',
133 | 						}),
134 | 					}),
135 | 				},
136 | 				async (c) => {
137 | 					const { token } = c.body;
138 | 					const storedToken = await storeToken(c, token);
139 | 					const verificationValue =
140 | 						await c.context.internalAdapter.findVerificationValue(
141 | 							`one-time-token:${storedToken}`,
142 | 						);
143 | 					if (!verificationValue) {
144 | 						throw c.error("BAD_REQUEST", {
145 | 							message: "Invalid token",
146 | 						});
147 | 					}
148 | 					await c.context.internalAdapter.deleteVerificationValue(
149 | 						verificationValue.id,
150 | 					);
151 | 					if (verificationValue.expiresAt < new Date()) {
152 | 						throw c.error("BAD_REQUEST", {
153 | 							message: "Token expired",
154 | 						});
155 | 					}
156 | 					const session = await c.context.internalAdapter.findSession(
157 | 						verificationValue.value,
158 | 					);
159 | 					if (!session) {
160 | 						throw c.error("BAD_REQUEST", {
161 | 							message: "Session not found",
162 | 						});
163 | 					}
164 | 					return c.json(session);
165 | 				},
166 | 			),
167 | 		},
168 | 	} satisfies BetterAuthPlugin;
169 | };
170 | 
```

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

```markdown
  1 | ---
  2 | title: Astro Integration
  3 | description: Integrate Better Auth with Astro.
  4 | ---
  5 | 
  6 | Better Auth comes with first class support for Astro. This guide will show you how to integrate Better Auth with Astro.
  7 | 
  8 | 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).
  9 | 
 10 | ### Mount the handler
 11 | 
 12 | To enable Better Auth to handle requests, we need to mount the handler to a catch all API route. Create a file inside `/pages/api/auth` called `[...all].ts` and add the following code:
 13 | 
 14 | ```ts title="pages/api/auth/[...all].ts"
 15 | import { auth } from "~/auth";
 16 | import type { APIRoute } from "astro";
 17 | 
 18 | export const ALL: APIRoute = async (ctx) => {
 19 | 	// If you want to use rate limiting, make sure to set the 'x-forwarded-for' header to the request headers from the context
 20 | 	// ctx.request.headers.set("x-forwarded-for", ctx.clientAddress);
 21 | 	return auth.handler(ctx.request);
 22 | };
 23 | ```
 24 | 
 25 | <Callout>
 26 |     You can change the path on your better-auth configuration but it's recommended to keep it as `/api/auth/[...all]`
 27 | </Callout>
 28 | 
 29 | ## Create a client
 30 | 
 31 | Astro supports multiple frontend frameworks, so you can easily import your client based on the framework you're using.
 32 | 
 33 | If you're not using a frontend framework, you can still import the vanilla client.
 34 | 
 35 | 
 36 | <Tabs items={[ "vanilla", "react", "vue", "svelte", "solid",
 37 |  ]} defaultValue="react">
 38 |     <Tab value="vanilla">
 39 |             ```ts  title="lib/auth-client.ts"
 40 |             import { createAuthClient } from "better-auth/client"
 41 |             export const authClient =  createAuthClient()
 42 |             ```
 43 |     </Tab>
 44 |     <Tab value="react" title="lib/auth-client.ts">
 45 |             ```ts  title="lib/auth-client.ts"
 46 |             import { createAuthClient } from "better-auth/react"
 47 |             export const authClient =  createAuthClient()
 48 |             ```
 49 |     </Tab>
 50 |     <Tab value="vue" title="lib/auth-client.ts">
 51 |             ```ts  title="lib/auth-client.ts"
 52 |             import { createAuthClient } from "better-auth/vue"
 53 |             export const authClient =  createAuthClient()
 54 |             ```
 55 |     </Tab>
 56 |     <Tab value="svelte" title="lib/auth-client.ts">
 57 |             ```ts  title="lib/auth-client.ts"
 58 |             import { createAuthClient } from "better-auth/svelte"
 59 |             export const authClient =  createAuthClient()
 60 |             ```
 61 |     </Tab>
 62 |     <Tab value="solid" title="lib/auth-client.ts">
 63 |             ```ts title="lib/auth-client.ts"
 64 |             import { createAuthClient } from "better-auth/solid"
 65 |             export const authClient =  createAuthClient()
 66 |             ```
 67 |     </Tab>
 68 | </Tabs>
 69 | 
 70 | ## Auth Middleware
 71 | 
 72 | ### Astro Locals types
 73 | 
 74 | To have types for your Astro locals, you need to set it inside the `env.d.ts` file.
 75 | 
 76 | ```ts title="env.d.ts"
 77 | 
 78 | /// <reference path="../.astro/types.d.ts" />
 79 | 
 80 | declare namespace App {
 81 |     // Note: 'import {} from ""' syntax does not work in .d.ts files.
 82 |     interface Locals {
 83 |         user: import("better-auth").User | null;
 84 |         session: import("better-auth").Session | null;
 85 |     }
 86 | }
 87 | ```
 88 | 
 89 | ### Middleware
 90 | 
 91 | To protect your routes, you can check if the user is authenticated using the `getSession` method in middleware and set the user and session data using the Astro locals with the types we set before. Start by creating a `middleware.ts` file in the root of your project and follow the example below:
 92 | 
 93 | ```ts title="middleware.ts"
 94 | import { auth } from "@/auth";
 95 | import { defineMiddleware } from "astro:middleware";
 96 | 
 97 | export const onRequest = defineMiddleware(async (context, next) => {
 98 |     const isAuthed = await auth.api
 99 |         .getSession({
100 |             headers: context.request.headers,
101 |         })
102 | 
103 |     if (isAuthed) {
104 |         context.locals.user = isAuthed.user;
105 |         context.locals.session = isAuthed.session;
106 |     } else {
107 |         context.locals.user = null;
108 |         context.locals.session = null;
109 |     }
110 | 
111 |     return next();
112 | });
113 | ```
114 | 
115 | ### Getting session on the server inside `.astro` file
116 | 
117 | You can use `Astro.locals` to check if the user has session and get the user data from the server side. Here is an example of how you can get the session inside an `.astro` file:
118 | 
119 | ```astro
120 | ---
121 | import { UserCard } from "@/components/user-card";
122 | 
123 | const session = () => {
124 |     if (Astro.locals.session) {
125 |         return Astro.locals.session;
126 |     } else {
127 |         // Redirect to login page if the user is not authenticated
128 |         return Astro.redirect("/login");
129 |     }
130 | }
131 | 
132 | ---
133 | 
134 | <UserCard initialSession={session} />
135 | ```
136 | 
```

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

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import { decodeJwt } from "jose";
  3 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  4 | import {
  5 | 	createAuthorizationURL,
  6 | 	refreshAccessToken,
  7 | 	validateAuthorizationCode,
  8 | } from "../oauth2";
  9 | 
 10 | export interface LineIdTokenPayload {
 11 | 	iss: string;
 12 | 	sub: string;
 13 | 	aud: string;
 14 | 	exp: number;
 15 | 	iat: number;
 16 | 	name?: string;
 17 | 	picture?: string;
 18 | 	email?: string;
 19 | 	amr?: string[];
 20 | 	nonce?: string;
 21 | }
 22 | 
 23 | export interface LineUserInfo {
 24 | 	sub: string;
 25 | 	name?: string;
 26 | 	picture?: string;
 27 | 	email?: string;
 28 | }
 29 | 
 30 | export interface LineOptions
 31 | 	extends ProviderOptions<LineUserInfo | LineIdTokenPayload> {
 32 | 	clientId: string;
 33 | }
 34 | 
 35 | /**
 36 |  * LINE Login v2.1
 37 |  * - Authorization endpoint: https://access.line.me/oauth2/v2.1/authorize
 38 |  * - Token endpoint: https://api.line.me/oauth2/v2.1/token
 39 |  * - UserInfo endpoint: https://api.line.me/oauth2/v2.1/userinfo
 40 |  * - Verify ID token: https://api.line.me/oauth2/v2.1/verify
 41 |  *
 42 |  * Docs: https://developers.line.biz/en/reference/line-login/#issue-access-token
 43 |  */
 44 | export const line = (options: LineOptions) => {
 45 | 	const authorizationEndpoint = "https://access.line.me/oauth2/v2.1/authorize";
 46 | 	const tokenEndpoint = "https://api.line.me/oauth2/v2.1/token";
 47 | 	const userInfoEndpoint = "https://api.line.me/oauth2/v2.1/userinfo";
 48 | 	const verifyIdTokenEndpoint = "https://api.line.me/oauth2/v2.1/verify";
 49 | 
 50 | 	return {
 51 | 		id: "line",
 52 | 		name: "LINE",
 53 | 		async createAuthorizationURL({
 54 | 			state,
 55 | 			scopes,
 56 | 			codeVerifier,
 57 | 			redirectURI,
 58 | 			loginHint,
 59 | 		}) {
 60 | 			const _scopes = options.disableDefaultScope
 61 | 				? []
 62 | 				: ["openid", "profile", "email"];
 63 | 			options.scope && _scopes.push(...options.scope);
 64 | 			scopes && _scopes.push(...scopes);
 65 | 			return await createAuthorizationURL({
 66 | 				id: "line",
 67 | 				options,
 68 | 				authorizationEndpoint,
 69 | 				scopes: _scopes,
 70 | 				state,
 71 | 				codeVerifier,
 72 | 				redirectURI,
 73 | 				loginHint,
 74 | 			});
 75 | 		},
 76 | 		validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
 77 | 			return validateAuthorizationCode({
 78 | 				code,
 79 | 				codeVerifier,
 80 | 				redirectURI,
 81 | 				options,
 82 | 				tokenEndpoint,
 83 | 			});
 84 | 		},
 85 | 		refreshAccessToken: options.refreshAccessToken
 86 | 			? options.refreshAccessToken
 87 | 			: async (refreshToken) => {
 88 | 					return refreshAccessToken({
 89 | 						refreshToken,
 90 | 						options: {
 91 | 							clientId: options.clientId,
 92 | 							clientSecret: options.clientSecret,
 93 | 						},
 94 | 						tokenEndpoint,
 95 | 					});
 96 | 				},
 97 | 		async verifyIdToken(token, nonce) {
 98 | 			if (options.disableIdTokenSignIn) {
 99 | 				return false;
100 | 			}
101 | 			if (options.verifyIdToken) {
102 | 				return options.verifyIdToken(token, nonce);
103 | 			}
104 | 			const body = new URLSearchParams();
105 | 			body.set("id_token", token);
106 | 			body.set("client_id", options.clientId);
107 | 			if (nonce) body.set("nonce", nonce);
108 | 			const { data, error } = await betterFetch<LineIdTokenPayload>(
109 | 				verifyIdTokenEndpoint,
110 | 				{
111 | 					method: "POST",
112 | 					headers: {
113 | 						"content-type": "application/x-www-form-urlencoded",
114 | 					},
115 | 					body,
116 | 				},
117 | 			);
118 | 			if (error || !data) {
119 | 				return false;
120 | 			}
121 | 			// aud must match clientId; nonce (if provided) must also match
122 | 			if (data.aud !== options.clientId) return false;
123 | 			if (nonce && data.nonce && data.nonce !== nonce) return false;
124 | 			return true;
125 | 		},
126 | 		async getUserInfo(token) {
127 | 			if (options.getUserInfo) {
128 | 				return options.getUserInfo(token);
129 | 			}
130 | 			let profile: LineUserInfo | LineIdTokenPayload | null = null;
131 | 			// Prefer ID token if available
132 | 			if (token.idToken) {
133 | 				try {
134 | 					profile = decodeJwt(token.idToken) as LineIdTokenPayload;
135 | 				} catch {}
136 | 			}
137 | 			// Fallback to UserInfo endpoint
138 | 			if (!profile) {
139 | 				const { data } = await betterFetch<LineUserInfo>(userInfoEndpoint, {
140 | 					headers: {
141 | 						authorization: `Bearer ${token.accessToken}`,
142 | 					},
143 | 				});
144 | 				profile = data || null;
145 | 			}
146 | 			if (!profile) return null;
147 | 			const userMap = await options.mapProfileToUser?.(profile as any);
148 | 			// ID preference order
149 | 			const id = (profile as any).sub || (profile as any).userId;
150 | 			const name = (profile as any).name || (profile as any).displayName;
151 | 			const image =
152 | 				(profile as any).picture || (profile as any).pictureUrl || undefined;
153 | 			const email = (profile as any).email;
154 | 			return {
155 | 				user: {
156 | 					id,
157 | 					name,
158 | 					email,
159 | 					image,
160 | 					// LINE does not expose email verification status in ID token/userinfo
161 | 					emailVerified: false,
162 | 					...userMap,
163 | 				},
164 | 				data: profile as any,
165 | 			};
166 | 		},
167 | 		options,
168 | 	} satisfies OAuthProvider<LineUserInfo | LineIdTokenPayload, LineOptions>;
169 | };
170 | 
```

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

```markdown
  1 | ---
  2 | title: TypeScript
  3 | description: Better Auth TypeScript integration.
  4 | ---
  5 | 
  6 | Better Auth is designed to be type-safe. Both the client and server are built with TypeScript, allowing you to easily infer types.
  7 | 
  8 | 
  9 | ## TypeScript Config
 10 | 
 11 | ### Strict Mode
 12 | 
 13 | Better Auth is designed to work with TypeScript's strict mode. We recommend enabling strict mode in your TypeScript config file:
 14 | 
 15 | ```json title="tsconfig.json"
 16 | {
 17 |   "compilerOptions": {
 18 |     "strict": true
 19 |   }
 20 | }
 21 | ```
 22 | 
 23 | if you can't set `strict` to `true`, you can enable `strictNullChecks`:
 24 | 
 25 | ```json title="tsconfig.json"
 26 | {
 27 |   "compilerOptions": {
 28 |     "strictNullChecks": true,
 29 |   }
 30 | }
 31 | ```
 32 | 
 33 | <Callout type="warn">
 34 | If you're running into issues with TypeScript inference exceeding maximum length the compiler will serialize,
 35 | then please make sure you're following the instructions above, as well as ensuring that both `declaration` and `composite` are not enabled.
 36 | </Callout>
 37 | 
 38 | ## Inferring Types
 39 | 
 40 | Both the client SDK and the server offer types that can be inferred using the `$Infer` property. Plugins can extend base types like `User` and `Session`, and you can use `$Infer` to infer these types. Additionally, plugins can provide extra types that can also be inferred through `$Infer`.
 41 | 
 42 | ```ts title="auth-client.ts" 
 43 | import { createAuthClient } from "better-auth/client"
 44 | 
 45 | const authClient = createAuthClient()
 46 | 
 47 | export type Session = typeof authClient.$Infer.Session
 48 | ```
 49 | 
 50 | The `Session` type includes both `session` and `user` properties. The user property represents the user object type, and the `session` property represents the `session` object type.
 51 | 
 52 | You can also infer types on the server side.
 53 | 
 54 | ```ts title="auth.ts" 
 55 | import { betterAuth } from "better-auth"
 56 | import Database from "better-sqlite3"
 57 | 
 58 | export const auth = betterAuth({
 59 |     database: new Database("database.db")
 60 | })
 61 | 
 62 | type Session = typeof auth.$Infer.Session
 63 | ```
 64 | 
 65 | 
 66 | ## Additional Fields
 67 | 
 68 | Better Auth allows you to add additional fields to the user and session objects. All additional fields are properly inferred and available on the server and client side.
 69 | 
 70 | ```ts 
 71 | import { betterAuth } from "better-auth"
 72 | import Database from "better-sqlite3"
 73 | 
 74 | export const auth = betterAuth({
 75 |     database: new Database("database.db"),
 76 |     user: {
 77 |        additionalFields: {
 78 |           role: {
 79 |               type: "string",
 80 |               input: false
 81 |             } 
 82 |         }
 83 |     }
 84 |    
 85 | })
 86 | 
 87 | type Session = typeof auth.$Infer.Session
 88 | ```
 89 | 
 90 | In the example above, we added a `role` field to the user object. This field is now available on the `Session` type.
 91 | 
 92 | 
 93 | ### The `input` property
 94 | 
 95 | The `input` property in an additional field configuration determines whether the field should be included in the user input. This property defaults to `true`, meaning the field will be part of the user input during operations like registration.
 96 | 
 97 | To prevent a field from being part of the user input, you must explicitly set `input: false`:
 98 | 
 99 | ```ts
100 | additionalFields: {
101 |     role: {
102 |         type: "string",
103 |         input: false
104 |     }
105 | }
106 | ```
107 | 
108 | When `input` is set to `false`, the field will be excluded from user input, preventing users from passing a value for it.
109 | 
110 | By default, additional fields are included in the user input, which can lead to security vulnerabilities if not handled carefully. For fields that should not be set by the user, like a `role`, it is crucial to set `input: false` in the configuration.
111 | 
112 | ### Inferring Additional Fields on Client
113 | 
114 | To make sure proper type inference for additional fields on the client side, you need to inform the client about these fields. There are two approaches to achieve this, depending on your project structure:
115 | 
116 | 1. For Monorepo or Single-Project Setups
117 | 
118 | If your server and client code reside in the same project, you can use the `inferAdditionalFields` plugin to automatically infer the additional fields from your server configuration.
119 | 
120 | ```ts
121 | import { inferAdditionalFields } from "better-auth/client/plugins";
122 | import { createAuthClient } from "better-auth/react";
123 | import type { auth } from "./auth";
124 | 
125 | export const authClient = createAuthClient({
126 |   plugins: [inferAdditionalFields<typeof auth>()],
127 | });
128 | ```
129 | 
130 | 2. For Separate Client-Server Projects
131 | 
132 | If your client and server are in separate projects, you'll need to manually specify the additional fields when creating the auth client.
133 | 
134 | ```ts
135 | import { inferAdditionalFields } from "better-auth/client/plugins";
136 | 
137 | export const authClient = createAuthClient({
138 |   plugins: [inferAdditionalFields({
139 |       user: {
140 |         role: {
141 |           type: "string"
142 |         }
143 |       }
144 |   })],
145 | });
146 | ```
147 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/last-login-method/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { createAuthMiddleware } from "@better-auth/core/api";
  2 | import type { BetterAuthPlugin } from "@better-auth/core";
  3 | import type { GenericEndpointContext } from "@better-auth/core";
  4 | 
  5 | /**
  6 |  * Configuration for tracking different authentication methods
  7 |  */
  8 | export interface LastLoginMethodOptions {
  9 | 	/**
 10 | 	 * Name of the cookie to store the last login method
 11 | 	 * @default "better-auth.last_used_login_method"
 12 | 	 */
 13 | 	cookieName?: string;
 14 | 	/**
 15 | 	 * Cookie expiration time in seconds
 16 | 	 * @default 2592000 (30 days)
 17 | 	 */
 18 | 	maxAge?: number;
 19 | 	/**
 20 | 	 * Custom method to resolve the last login method
 21 | 	 * @param ctx - The context from the hook
 22 | 	 * @returns The last login method
 23 | 	 */
 24 | 	customResolveMethod?: (ctx: GenericEndpointContext) => string | null;
 25 | 	/**
 26 | 	 * Store the last login method in the database. This will create a new field in the user table.
 27 | 	 * @default false
 28 | 	 */
 29 | 	storeInDatabase?: boolean;
 30 | 	/**
 31 | 	 * Custom schema for the plugin
 32 | 	 * @default undefined
 33 | 	 */
 34 | 	schema?: {
 35 | 		user?: {
 36 | 			lastLoginMethod?: string;
 37 | 		};
 38 | 	};
 39 | }
 40 | 
 41 | /**
 42 |  * Plugin to track the last used login method
 43 |  */
 44 | export const lastLoginMethod = <O extends LastLoginMethodOptions>(
 45 | 	userConfig?: O,
 46 | ) => {
 47 | 	const paths = [
 48 | 		"/callback/:id",
 49 | 		"/oauth2/callback/:id",
 50 | 		"/sign-in/email",
 51 | 		"/sign-up/email",
 52 | 	];
 53 | 
 54 | 	const defaultResolveMethod = (ctx: GenericEndpointContext) => {
 55 | 		if (paths.includes(ctx.path)) {
 56 | 			return ctx.params?.id ? ctx.params.id : ctx.path.split("/").pop();
 57 | 		}
 58 | 		return null;
 59 | 	};
 60 | 
 61 | 	const config = {
 62 | 		cookieName: "better-auth.last_used_login_method",
 63 | 		maxAge: 60 * 60 * 24 * 30,
 64 | 		...userConfig,
 65 | 	} satisfies LastLoginMethodOptions;
 66 | 
 67 | 	return {
 68 | 		id: "last-login-method",
 69 | 		init(ctx) {
 70 | 			return {
 71 | 				options: {
 72 | 					databaseHooks: {
 73 | 						user: {
 74 | 							create: {
 75 | 								async before(user, context) {
 76 | 									if (!config.storeInDatabase) return;
 77 | 									if (!context) return;
 78 | 									const lastUsedLoginMethod =
 79 | 										config.customResolveMethod?.(context) ??
 80 | 										defaultResolveMethod(context);
 81 | 									if (lastUsedLoginMethod) {
 82 | 										return {
 83 | 											data: {
 84 | 												...user,
 85 | 												lastLoginMethod: lastUsedLoginMethod,
 86 | 											},
 87 | 										};
 88 | 									}
 89 | 								},
 90 | 							},
 91 | 						},
 92 | 						session: {
 93 | 							create: {
 94 | 								async after(session, context) {
 95 | 									if (!config.storeInDatabase) return;
 96 | 									if (!context) return;
 97 | 									const lastUsedLoginMethod =
 98 | 										config.customResolveMethod?.(context) ??
 99 | 										defaultResolveMethod(context);
100 | 									if (lastUsedLoginMethod && session?.userId) {
101 | 										try {
102 | 											await ctx.internalAdapter.updateUser(session.userId, {
103 | 												lastLoginMethod: lastUsedLoginMethod,
104 | 											});
105 | 										} catch (error) {
106 | 											ctx.logger.error(
107 | 												"Failed to update lastLoginMethod",
108 | 												error,
109 | 											);
110 | 										}
111 | 									}
112 | 								},
113 | 							},
114 | 						},
115 | 					},
116 | 				},
117 | 			};
118 | 		},
119 | 		hooks: {
120 | 			after: [
121 | 				{
122 | 					matcher() {
123 | 						return true;
124 | 					},
125 | 					handler: createAuthMiddleware(async (ctx) => {
126 | 						const lastUsedLoginMethod =
127 | 							config.customResolveMethod?.(ctx) ?? defaultResolveMethod(ctx);
128 | 						if (lastUsedLoginMethod) {
129 | 							const setCookie = ctx.context.responseHeaders?.get("set-cookie");
130 | 							const sessionTokenName =
131 | 								ctx.context.authCookies.sessionToken.name;
132 | 							const hasSessionToken =
133 | 								setCookie && setCookie.includes(sessionTokenName);
134 | 							if (hasSessionToken) {
135 | 								// Inherit cookie attributes from Better Auth's centralized cookie system
136 | 								// This ensures consistency with cross-origin, cross-subdomain, and security settings
137 | 								const cookieAttributes = {
138 | 									...ctx.context.authCookies.sessionToken.options,
139 | 									maxAge: config.maxAge,
140 | 									httpOnly: false, // Override: plugin cookies are not httpOnly
141 | 								};
142 | 
143 | 								ctx.setCookie(
144 | 									config.cookieName,
145 | 									lastUsedLoginMethod,
146 | 									cookieAttributes,
147 | 								);
148 | 							}
149 | 						}
150 | 					}),
151 | 				},
152 | 			],
153 | 		},
154 | 		schema: (config.storeInDatabase
155 | 			? {
156 | 					user: {
157 | 						fields: {
158 | 							lastLoginMethod: {
159 | 								type: "string",
160 | 								input: false,
161 | 								required: false,
162 | 								fieldName:
163 | 									config.schema?.user?.lastLoginMethod || "lastLoginMethod",
164 | 							},
165 | 						},
166 | 					},
167 | 				}
168 | 			: undefined) as O["storeInDatabase"] extends true
169 | 			? {
170 | 					user: {
171 | 						fields: {
172 | 							lastLoginMethod: {
173 | 								type: "string";
174 | 								required: false;
175 | 								input: false;
176 | 							};
177 | 						};
178 | 					};
179 | 				}
180 | 			: undefined,
181 | 	} satisfies BetterAuthPlugin;
182 | };
183 | 
```

--------------------------------------------------------------------------------
/docs/content/changelogs/1-2.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: 1.2 Release
  3 | description: Stripe, Captcha, API Keys, Teams, Init CLI, and more.
  4 | date: 2025-03-01
  5 | ---
  6 | 
  7 | # Better Auth 1.2 – Stripe, Captcha, API Keys, Teams, Init CLI, and more
  8 | 
  9 | To upgrade, run:
 10 | 
 11 | ```package-install
 12 | npm install [email protected]
 13 | ```
 14 | 
 15 | ---
 16 | 
 17 | ### **Stripe Plugin (Beta)**
 18 | 
 19 | Stripe integration for customer management, subscriptions, and webhooks.
 20 | 
 21 | ```package-install
 22 | npm install @better-auth/stripe
 23 | ```
 24 | 
 25 | ```ts title="auth.ts"
 26 | import { stripe } from "@better-auth/stripe"; // [!code highlight]
 27 | 
 28 | export const auth = betterAuth({
 29 |   plugins: [
 30 |     stripe({
 31 |       // [!code highlight]
 32 |       createCustomerOnSignup: true, // [!code highlight]
 33 |       subscription: { // [!code highlight]
 34 |         enabled: true, // [!code highlight]
 35 |         plans: [// [!code highlight]
 36 |           { // [!code highlight]
 37 |             name: "pro", // [!code highlight]
 38 |             priceId: "price_1234567890", // [!code highlight]
 39 |           }, // [!code highlight]
 40 |         ], // [!code highlight]
 41 |       }, // [!code highlight]
 42 |     }), // [!code highlight]
 43 |   ],
 44 | });
 45 | ```
 46 | 
 47 | Read the [Stripe Plugin docs](/docs/plugins/stripe) for more information.
 48 | 
 49 | ### **Captcha Plugin**
 50 | 
 51 | Protect your authentication flows with Google reCAPTCHA and Cloudflare Turnstile. Works for signup, signin, and password resets.
 52 | 
 53 | ```ts title="auth.ts"
 54 | import { captcha } from "better-auth/plugins";
 55 | 
 56 | const auth = betterAuth({
 57 |   plugins: [
 58 |     // [!code highlight]
 59 |     captcha({
 60 |       // [!code highlight]
 61 |       provider: "cloudflare-turnstile", // or "google-recaptcha" // [!code highlight]
 62 |       secretKey: process.env.TURNSTILE_SECRET_KEY!, // [!code highlight]
 63 |     }), // [!code highlight]
 64 |   ], // [!code highlight]
 65 | });
 66 | ```
 67 | 
 68 | Read the [Captcha Plugin docs](/docs/plugins/captcha) for more information.
 69 | 
 70 | ### **API Key Plugin**
 71 | 
 72 | Generate and manage API keys with rate limiting, expiration, and metadata. Supports session creation from API keys.
 73 | 
 74 | ```ts title="auth.ts"
 75 | import { apiKey } from "better-auth/plugins";
 76 | 
 77 | const auth = betterAuth({
 78 |   plugins: [apiKey()],
 79 | });
 80 | ```
 81 | 
 82 | Read the [API Key Plugin docs](/docs/plugins/api-key) for more information.
 83 | 
 84 | ### **Teams/Sub-Organizations**
 85 | 
 86 | Organizations can now have teams or sub-organizations under them.
 87 | 
 88 | ```ts title="auth.ts"
 89 | const auth = betterAuth({
 90 |   plugins: [
 91 |     organization({
 92 |       teams: {
 93 |         enabled: true,
 94 |       },
 95 |     }),
 96 |   ],
 97 | });
 98 | ```
 99 | 
100 | Read the [Organization Plugin docs](/docs/plugins/organization#teams) for more information.
101 | 
102 | ### **Init CLI**
103 | 
104 | The CLI now includes an `init` command to add Better Auth to your project.
105 | 
106 | ```bash title="terminal"
107 | npx @better-auth/cli init
108 | ```
109 | 
110 | ### **Username**
111 | 
112 | - Added `displayName` for case-insensitive lookups while preserving original formatting.
113 | - Built-in validation.
114 | 
115 | <Callout type="info">
116 |   If you're using the Username plugin, make sure to add the `displayName` field
117 |   to your schema.
118 | </Callout>
119 | 
120 | ### **Organization**
121 | 
122 | - **Multiple Roles per User** – Assign more than one role to a user.
123 | 
124 | ### **Admin Plugin**
125 | 
126 | - Manage roles and permissions within the admin plugin. [Learn more](/docs/plugins/admin)
127 | - `adminUserIds` option to grant specific users admin privileges. [Learn more](/docs/plugins/admin#usage)
128 | 
129 | ---
130 | 
131 | ## 🎭 New Social Providers
132 | 
133 | - [TikTok](/docs/authentication/tiktok)
134 | - [Roblox](/docs/authentication/roblox)
135 | - [VK](/docs/authentication/vk)
136 | 
137 | ---
138 | 
139 | ## ✨ Core Enhancements
140 | 
141 | - **Auto Cleanup** for expired verification data
142 | - **Improved Google One Tap** integration with JWT verification and enhanced prompt handling
143 | - **Phone-based Password Reset** functionality
144 | - **Provider Control Options**:
145 |   - Disable signups for specific providers
146 |   - Disable implicit signups for specific providers
147 |   - Control default scopes and allow custom scopes on request
148 | - **Enhanced Database Hooks** with additional context information
149 | 
150 | ---
151 | 
152 | ## 🚀 Performance Boosts
153 | 
154 | We rewrote **better-call** (the core library behind Better Auth) to fix TypeScript editor lag. Your IDE should now feel much snappier when working with Better Auth.
155 | 
156 | ---
157 | 
158 | ## ⚡ CLI Enhancements
159 | 
160 | ### **`init` Command**
161 | 
162 | The CLI now includes an `init` command to speed up setup:
163 | 
164 | - Scaffold new projects
165 | - Generate schemas
166 | - Run migrations
167 | 
168 | [Learn more](/docs/concepts/cli)
169 | 
170 | ---
171 | 
172 | ## 🛠 Bug Fixes & Stability Improvements
173 | 
174 | A lot of fixes and refinements to make everything smoother, faster, and more reliable. Check out the [changelog](https://github.com/better-auth/better-auth/releases/tag/v1.2.0) for more details.
175 | 
176 | ---
177 | 
178 | ```package-install
179 | npm install [email protected]
180 | ```
181 | 
182 | **Upgrade now and take advantage of these powerful new features!** 🚀
183 | 
```

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

```typescript
  1 | // @ts-nocheck
  2 | "use client";
  3 | 
  4 | import * as React from "react";
  5 | import { Cross2Icon } from "@radix-ui/react-icons";
  6 | import * as ToastPrimitives from "@radix-ui/react-toast";
  7 | import { cva } from "class-variance-authority";
  8 | 
  9 | import { cn } from "@/lib/utils";
 10 | 
 11 | const ToastProvider = ToastPrimitives.Provider;
 12 | 
 13 | const ToastViewport = ({
 14 | 	ref,
 15 | 	className,
 16 | 	...props
 17 | }: React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport> & {
 18 | 	ref: React.RefObject<React.ElementRef<typeof ToastPrimitives.Viewport>>;
 19 | }) => (
 20 | 	<ToastPrimitives.Viewport
 21 | 		ref={ref}
 22 | 		className={cn(
 23 | 			"fixed top-0 z-100 flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
 24 | 			className,
 25 | 		)}
 26 | 		{...props}
 27 | 	/>
 28 | );
 29 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
 30 | 
 31 | const toastVariants = cva(
 32 | 	"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-(--radix-toast-swipe-end-x) data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x) data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
 33 | 	{
 34 | 		variants: {
 35 | 			variant: {
 36 | 				default: "border bg-background text-foreground",
 37 | 				destructive:
 38 | 					"destructive group border-destructive bg-destructive text-destructive-foreground",
 39 | 			},
 40 | 		},
 41 | 		defaultVariants: {
 42 | 			variant: "default",
 43 | 		},
 44 | 	},
 45 | );
 46 | 
 47 | const Toast = ({ ref, className, variant, ...props }) => {
 48 | 	return (
 49 | 		<ToastPrimitives.Root
 50 | 			ref={ref}
 51 | 			className={cn(toastVariants({ variant }), className)}
 52 | 			{...props}
 53 | 		/>
 54 | 	);
 55 | };
 56 | Toast.displayName = ToastPrimitives.Root.displayName;
 57 | 
 58 | const ToastAction = ({
 59 | 	ref,
 60 | 	className,
 61 | 	...props
 62 | }: React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action> & {
 63 | 	ref: React.RefObject<React.ElementRef<typeof ToastPrimitives.Action>>;
 64 | }) => (
 65 | 	<ToastPrimitives.Action
 66 | 		ref={ref}
 67 | 		className={cn(
 68 | 			"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
 69 | 			className,
 70 | 		)}
 71 | 		{...props}
 72 | 	/>
 73 | );
 74 | ToastAction.displayName = ToastPrimitives.Action.displayName;
 75 | 
 76 | const ToastClose = ({
 77 | 	ref,
 78 | 	className,
 79 | 	...props
 80 | }: React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close> & {
 81 | 	ref: React.RefObject<React.ElementRef<typeof ToastPrimitives.Close>>;
 82 | }) => (
 83 | 	<ToastPrimitives.Close
 84 | 		ref={ref}
 85 | 		className={cn(
 86 | 			"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
 87 | 			className,
 88 | 		)}
 89 | 		toast-close=""
 90 | 		{...props}
 91 | 	>
 92 | 		<Cross2Icon className="h-4 w-4" />
 93 | 	</ToastPrimitives.Close>
 94 | );
 95 | ToastClose.displayName = ToastPrimitives.Close.displayName;
 96 | 
 97 | const ToastTitle = ({
 98 | 	ref,
 99 | 	className,
100 | 	...props
101 | }: React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title> & {
102 | 	ref: React.RefObject<React.ElementRef<typeof ToastPrimitives.Title>>;
103 | }) => (
104 | 	<ToastPrimitives.Title
105 | 		ref={ref}
106 | 		className={cn("text-sm font-semibold [&+div]:text-xs", className)}
107 | 		{...props}
108 | 	/>
109 | );
110 | ToastTitle.displayName = ToastPrimitives.Title.displayName;
111 | 
112 | const ToastDescription = ({
113 | 	ref,
114 | 	className,
115 | 	...props
116 | }: React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description> & {
117 | 	ref: React.RefObject<React.ElementRef<typeof ToastPrimitives.Description>>;
118 | }) => (
119 | 	<ToastPrimitives.Description
120 | 		ref={ref}
121 | 		className={cn("text-sm opacity-90", className)}
122 | 		{...props}
123 | 	/>
124 | );
125 | ToastDescription.displayName = ToastPrimitives.Description.displayName;
126 | 
127 | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
128 | 
129 | type ToastActionElement = React.ReactElement<typeof ToastAction>;
130 | 
131 | export {
132 | 	type ToastProps,
133 | 	type ToastActionElement,
134 | 	ToastProvider,
135 | 	ToastViewport,
136 | 	Toast,
137 | 	ToastTitle,
138 | 	ToastDescription,
139 | 	ToastClose,
140 | 	ToastAction,
141 | };
142 | 
```

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

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
  4 | import { createRemoteJWKSet, jwtVerify, decodeJwt } from "jose";
  5 | import { refreshAccessToken } from "../oauth2";
  6 | export interface FacebookProfile {
  7 | 	id: string;
  8 | 	name: string;
  9 | 	email: string;
 10 | 	email_verified: boolean;
 11 | 	picture: {
 12 | 		data: {
 13 | 			height: number;
 14 | 			is_silhouette: boolean;
 15 | 			url: string;
 16 | 			width: number;
 17 | 		};
 18 | 	};
 19 | }
 20 | 
 21 | export interface FacebookOptions extends ProviderOptions<FacebookProfile> {
 22 | 	clientId: string;
 23 | 	/**
 24 | 	 * Extend list of fields to retrieve from the Facebook user profile.
 25 | 	 *
 26 | 	 * @default ["id", "name", "email", "picture"]
 27 | 	 */
 28 | 	fields?: string[];
 29 | 
 30 | 	/**
 31 | 	 * The config id to use when undergoing oauth
 32 | 	 */
 33 | 	configId?: string;
 34 | }
 35 | 
 36 | export const facebook = (options: FacebookOptions) => {
 37 | 	return {
 38 | 		id: "facebook",
 39 | 		name: "Facebook",
 40 | 		async createAuthorizationURL({ state, scopes, redirectURI, loginHint }) {
 41 | 			const _scopes = options.disableDefaultScope
 42 | 				? []
 43 | 				: ["email", "public_profile"];
 44 | 			options.scope && _scopes.push(...options.scope);
 45 | 			scopes && _scopes.push(...scopes);
 46 | 			return await createAuthorizationURL({
 47 | 				id: "facebook",
 48 | 				options,
 49 | 				authorizationEndpoint: "https://www.facebook.com/v21.0/dialog/oauth",
 50 | 				scopes: _scopes,
 51 | 				state,
 52 | 				redirectURI,
 53 | 				loginHint,
 54 | 				additionalParams: options.configId
 55 | 					? {
 56 | 							config_id: options.configId,
 57 | 						}
 58 | 					: {},
 59 | 			});
 60 | 		},
 61 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
 62 | 			return validateAuthorizationCode({
 63 | 				code,
 64 | 				redirectURI,
 65 | 				options,
 66 | 				tokenEndpoint: "https://graph.facebook.com/oauth/access_token",
 67 | 			});
 68 | 		},
 69 | 		async verifyIdToken(token, nonce) {
 70 | 			if (options.disableIdTokenSignIn) {
 71 | 				return false;
 72 | 			}
 73 | 
 74 | 			if (options.verifyIdToken) {
 75 | 				return options.verifyIdToken(token, nonce);
 76 | 			}
 77 | 
 78 | 			/* limited login */
 79 | 			// check is limited token
 80 | 			if (token.split(".").length === 3) {
 81 | 				try {
 82 | 					const { payload: jwtClaims } = await jwtVerify(
 83 | 						token,
 84 | 						createRemoteJWKSet(
 85 | 							// https://developers.facebook.com/docs/facebook-login/limited-login/token/#jwks
 86 | 							new URL(
 87 | 								"https://limited.facebook.com/.well-known/oauth/openid/jwks/",
 88 | 							),
 89 | 						),
 90 | 						{
 91 | 							algorithms: ["RS256"],
 92 | 							audience: options.clientId,
 93 | 							issuer: "https://www.facebook.com",
 94 | 						},
 95 | 					);
 96 | 
 97 | 					if (nonce && jwtClaims.nonce !== nonce) {
 98 | 						return false;
 99 | 					}
100 | 
101 | 					return !!jwtClaims;
102 | 				} catch (error) {
103 | 					return false;
104 | 				}
105 | 			}
106 | 
107 | 			/* access_token */
108 | 			return true;
109 | 		},
110 | 		refreshAccessToken: options.refreshAccessToken
111 | 			? options.refreshAccessToken
112 | 			: async (refreshToken) => {
113 | 					return refreshAccessToken({
114 | 						refreshToken,
115 | 						options: {
116 | 							clientId: options.clientId,
117 | 							clientKey: options.clientKey,
118 | 							clientSecret: options.clientSecret,
119 | 						},
120 | 						tokenEndpoint:
121 | 							"https://graph.facebook.com/v18.0/oauth/access_token",
122 | 					});
123 | 				},
124 | 		async getUserInfo(token) {
125 | 			if (options.getUserInfo) {
126 | 				return options.getUserInfo(token);
127 | 			}
128 | 
129 | 			if (token.idToken && token.idToken.split(".").length === 3) {
130 | 				const profile = decodeJwt(token.idToken) as {
131 | 					sub: string;
132 | 					email: string;
133 | 					name: string;
134 | 					picture: string;
135 | 				};
136 | 
137 | 				const user = {
138 | 					id: profile.sub,
139 | 					name: profile.name,
140 | 					email: profile.email,
141 | 					picture: {
142 | 						data: {
143 | 							url: profile.picture,
144 | 							height: 100,
145 | 							width: 100,
146 | 							is_silhouette: false,
147 | 						},
148 | 					},
149 | 				};
150 | 
151 | 				// https://developers.facebook.com/docs/facebook-login/limited-login/permissions
152 | 				const userMap = await options.mapProfileToUser?.({
153 | 					...user,
154 | 					email_verified: true,
155 | 				});
156 | 
157 | 				return {
158 | 					user: {
159 | 						...user,
160 | 						emailVerified: true,
161 | 						...userMap,
162 | 					},
163 | 					data: profile,
164 | 				};
165 | 			}
166 | 
167 | 			const fields = [
168 | 				"id",
169 | 				"name",
170 | 				"email",
171 | 				"picture",
172 | 				...(options?.fields || []),
173 | 			];
174 | 			const { data: profile, error } = await betterFetch<FacebookProfile>(
175 | 				"https://graph.facebook.com/me?fields=" + fields.join(","),
176 | 				{
177 | 					auth: {
178 | 						type: "Bearer",
179 | 						token: token.accessToken,
180 | 					},
181 | 				},
182 | 			);
183 | 			if (error) {
184 | 				return null;
185 | 			}
186 | 			const userMap = await options.mapProfileToUser?.(profile);
187 | 			return {
188 | 				user: {
189 | 					id: profile.id,
190 | 					name: profile.name,
191 | 					email: profile.email,
192 | 					image: profile.picture.data.url,
193 | 					emailVerified: profile.email_verified,
194 | 					...userMap,
195 | 				},
196 | 				data: profile,
197 | 			};
198 | 		},
199 | 		options,
200 | 	} satisfies OAuthProvider<FacebookProfile>;
201 | };
202 | 
```

--------------------------------------------------------------------------------
/docs/app/blog/_components/changelog-layout.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import Link from "next/link";
  2 | 
  3 | import clsx from "clsx";
  4 | import { DiscordLogoIcon } from "@radix-ui/react-icons";
  5 | 
  6 | function BookIcon(props: React.ComponentPropsWithoutRef<"svg">) {
  7 | 	return (
  8 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
  9 | 			<path d="M7 3.41a1 1 0 0 0-.668-.943L2.275 1.039a.987.987 0 0 0-.877.166c-.25.192-.398.493-.398.812V12.2c0 .454.296.853.725.977l3.948 1.365A1 1 0 0 0 7 13.596V3.41ZM9 13.596a1 1 0 0 0 1.327.946l3.948-1.365c.429-.124.725-.523.725-.977V2.017c0-.32-.147-.62-.398-.812a.987.987 0 0 0-.877-.166L9.668 2.467A1 1 0 0 0 9 3.41v10.186Z" />
 10 | 		</svg>
 11 | 	);
 12 | }
 13 | 
 14 | function GitHubIcon(props: React.ComponentPropsWithoutRef<"svg">) {
 15 | 	return (
 16 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
 17 | 			<path d="M8 .198a8 8 0 0 0-8 8 7.999 7.999 0 0 0 5.47 7.59c.4.076.547-.172.547-.384 0-.19-.007-.694-.01-1.36-2.226.482-2.695-1.074-2.695-1.074-.364-.923-.89-1.17-.89-1.17-.725-.496.056-.486.056-.486.803.056 1.225.824 1.225.824.714 1.224 1.873.87 2.33.666.072-.518.278-.87.507-1.07-1.777-.2-3.644-.888-3.644-3.954 0-.873.31-1.586.823-2.146-.09-.202-.36-1.016.07-2.118 0 0 .67-.214 2.2.82a7.67 7.67 0 0 1 2-.27 7.67 7.67 0 0 1 2 .27c1.52-1.034 2.19-.82 2.19-.82.43 1.102.16 1.916.08 2.118.51.56.82 1.273.82 2.146 0 3.074-1.87 3.75-3.65 3.947.28.24.54.73.54 1.48 0 1.07-.01 1.93-.01 2.19 0 .21.14.46.55.38A7.972 7.972 0 0 0 16 8.199a8 8 0 0 0-8-8Z" />
 18 | 		</svg>
 19 | 	);
 20 | }
 21 | 
 22 | function FeedIcon(props: React.ComponentPropsWithoutRef<"svg">) {
 23 | 	return (
 24 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
 25 | 			<path
 26 | 				fillRule="evenodd"
 27 | 				clipRule="evenodd"
 28 | 				d="M2.5 3a.5.5 0 0 1 .5-.5h.5c5.523 0 10 4.477 10 10v.5a.5.5 0 0 1-.5.5h-.5a.5.5 0 0 1-.5-.5v-.5A8.5 8.5 0 0 0 3.5 4H3a.5.5 0 0 1-.5-.5V3Zm0 4.5A.5.5 0 0 1 3 7h.5A5.5 5.5 0 0 1 9 12.5v.5a.5.5 0 0 1-.5.5H8a.5.5 0 0 1-.5-.5v-.5a4 4 0 0 0-4-4H3a.5.5 0 0 1-.5-.5v-.5Zm0 5a1 1 0 1 1 2 0 1 1 0 0 1-2 0Z"
 29 | 			/>
 30 | 		</svg>
 31 | 	);
 32 | }
 33 | 
 34 | function XIcon(props: React.ComponentPropsWithoutRef<"svg">) {
 35 | 	return (
 36 | 		<svg viewBox="0 0 16 16" aria-hidden="true" fill="currentColor" {...props}>
 37 | 			<path d="M9.51762 6.77491L15.3459 0H13.9648L8.90409 5.88256L4.86212 0H0.200195L6.31244 8.89547L0.200195 16H1.58139L6.92562 9.78782L11.1942 16H15.8562L9.51728 6.77491H9.51762ZM7.62588 8.97384L7.00658 8.08805L2.07905 1.03974H4.20049L8.17706 6.72795L8.79636 7.61374L13.9654 15.0075H11.844L7.62588 8.97418V8.97384Z" />
 38 | 		</svg>
 39 | 	);
 40 | }
 41 | 
 42 | export function Intro() {
 43 | 	return (
 44 | 		<>
 45 | 			<h1 className="mt-14  font-sans  font-semibold tracking-tighter text-5xl">
 46 | 				All of the changes made will be{" "}
 47 | 				<span className="">available here.</span>
 48 | 			</h1>
 49 | 			<p className="mt-4 text-sm text-gray-600 dark:text-gray-300">
 50 | 				Better Auth is comprehensive authentication library for TypeScript that
 51 | 				provides a wide range of features to make authentication easier and more
 52 | 				secure.
 53 | 			</p>
 54 | 			<hr className="h-px bg-gray-300 mt-5" />
 55 | 			<div className="mt-8 flex flex-wrap text-gray-600 dark:text-gray-300  justify-center gap-x-1 gap-y-3 sm:gap-x-2 lg:justify-start">
 56 | 				<IconLink
 57 | 					href="/docs"
 58 | 					icon={BookIcon}
 59 | 					className="flex-none text-gray-600 dark:text-gray-300"
 60 | 				>
 61 | 					Documentation
 62 | 				</IconLink>
 63 | 				<IconLink
 64 | 					href="https://github.com/better-auth/better-auth"
 65 | 					icon={GitHubIcon}
 66 | 					className="flex-none text-gray-600 dark:text-gray-300"
 67 | 				>
 68 | 					GitHub
 69 | 				</IconLink>
 70 | 				<IconLink
 71 | 					href="https://discord.gg/better-auth"
 72 | 					icon={DiscordLogoIcon}
 73 | 					className="flex-none text-gray-600 dark:text-gray-300"
 74 | 				>
 75 | 					Community
 76 | 				</IconLink>
 77 | 			</div>
 78 | 		</>
 79 | 	);
 80 | }
 81 | 
 82 | export function IntroFooter() {
 83 | 	return (
 84 | 		<p className="flex items-baseline gap-x-2 text-[0.8125rem]/6 text-gray-500">
 85 | 			Brought to you by{" "}
 86 | 			<IconLink href="#" icon={XIcon} compact>
 87 | 				BETTER-AUTH.
 88 | 			</IconLink>
 89 | 		</p>
 90 | 	);
 91 | }
 92 | 
 93 | export function IconLink({
 94 | 	children,
 95 | 	className,
 96 | 	compact = false,
 97 | 	icon: Icon,
 98 | 	...props
 99 | }: React.ComponentPropsWithoutRef<typeof Link> & {
100 | 	compact?: boolean;
101 | 	icon?: React.ComponentType<{ className?: string }>;
102 | }) {
103 | 	return (
104 | 		<Link
105 | 			{...props}
106 | 			className={clsx(
107 | 				className,
108 | 				"group relative isolate flex items-center px-2 py-0.5 text-[0.8125rem]/6 font-medium text-black/70 dark:text-white/30 transition-colors hover:text-stone-300 rounded-none",
109 | 				compact ? "gap-x-2" : "gap-x-3",
110 | 			)}
111 | 		>
112 | 			<span className="absolute inset-0 -z-10 scale-75 rounded-lg bg-white/5 opacity-0 transition group-hover:scale-100 group-hover:opacity-100" />
113 | 			{Icon && <Icon className="h-4 w-4 flex-none" />}
114 | 			<span className="self-baseline text-black/70 dark:text-white">
115 | 				{children}
116 | 			</span>
117 | 		</Link>
118 | 	);
119 | }
120 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/custom-session/custom-session.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, expectTypeOf, it } from "vitest";
  2 | import { getTestInstance } from "../../test-utils/test-instance";
  3 | import { customSession } from ".";
  4 | import { admin } from "../admin";
  5 | import { createAuthClient } from "../../client";
  6 | import { customSessionClient } from "./client";
  7 | import type { BetterAuthOptions } from "../../types";
  8 | import { adminClient } from "../admin/client";
  9 | import { multiSession } from "../multi-session";
 10 | import { multiSessionClient } from "../multi-session/client";
 11 | import { parseSetCookieHeader } from "../../cookies";
 12 | 
 13 | describe("Custom Session Plugin Tests", async () => {
 14 | 	const options = {
 15 | 		plugins: [admin(), multiSession()],
 16 | 	} satisfies BetterAuthOptions;
 17 | 	const { auth, signInWithTestUser, testUser, customFetchImpl, cookieSetter } =
 18 | 		await getTestInstance({
 19 | 			session: {
 20 | 				maxAge: 10,
 21 | 				updateAge: 0,
 22 | 				cookieCache: {
 23 | 					enabled: true,
 24 | 					maxAge: 10,
 25 | 				},
 26 | 			},
 27 | 			plugins: [
 28 | 				...options.plugins,
 29 | 				customSession(
 30 | 					async ({ user, session }) => {
 31 | 						const newData = {
 32 | 							message: "Hello, World!",
 33 | 						};
 34 | 						return {
 35 | 							user: {
 36 | 								firstName: user.name.split(" ")[0],
 37 | 								lastName: user.name.split(" ")[1],
 38 | 							},
 39 | 							newData,
 40 | 							session,
 41 | 						};
 42 | 					},
 43 | 					options,
 44 | 					{ shouldMutateListDeviceSessionsEndpoint: true },
 45 | 				),
 46 | 			],
 47 | 		});
 48 | 
 49 | 	const client = createAuthClient({
 50 | 		baseURL: "http://localhost:3000",
 51 | 		plugins: [
 52 | 			customSessionClient<typeof auth>(),
 53 | 			adminClient(),
 54 | 			multiSessionClient(),
 55 | 		],
 56 | 		fetchOptions: { customFetchImpl },
 57 | 	});
 58 | 
 59 | 	it("should return the session", async () => {
 60 | 		const { headers } = await signInWithTestUser();
 61 | 		const session = await auth.api.getSession({ headers });
 62 | 		const s = await client.getSession({ fetchOptions: { headers } });
 63 | 		expect(s.data?.newData).toEqual({ message: "Hello, World!" });
 64 | 		expect(session?.newData).toEqual({ message: "Hello, World!" });
 65 | 	});
 66 | 
 67 | 	it("should return set cookie headers", async () => {
 68 | 		const { headers } = await signInWithTestUser();
 69 | 		const s = await client.getSession({
 70 | 			fetchOptions: {
 71 | 				headers,
 72 | 				onResponse(context) {
 73 | 					const header = context.response.headers.get("set-cookie");
 74 | 					expect(header).toBeDefined();
 75 | 
 76 | 					const cookies = parseSetCookieHeader(header!);
 77 | 					expect(cookies.has("better-auth.session_token")).toBe(true);
 78 | 					expect(cookies.has("better-auth.session_data")).toBe(true);
 79 | 				},
 80 | 			},
 81 | 		});
 82 | 	});
 83 | 
 84 | 	it("should return the custom session for multi-session", async () => {
 85 | 		let headers = new Headers();
 86 | 		const testUser = {
 87 | 			email: "[email protected]",
 88 | 			password: "password",
 89 | 			name: "Name",
 90 | 		};
 91 | 
 92 | 		await client.signUp.email(
 93 | 			{
 94 | 				name: testUser.name,
 95 | 				email: testUser.email,
 96 | 				password: testUser.password,
 97 | 			},
 98 | 			{
 99 | 				onSuccess: cookieSetter(headers),
100 | 			},
101 | 		);
102 | 		const sessions = await auth.api.listDeviceSessions({
103 | 			headers,
104 | 		});
105 | 		const session = sessions[0]!;
106 | 		//@ts-expect-error
107 | 		expect(session.newData).toEqual({ message: "Hello, World!" });
108 | 	});
109 | 
110 | 	it.skipIf(globalThis.gc == null)(
111 | 		"should not create memory leaks with multiple plugin instances",
112 | 		async () => {
113 | 			const initialMemory = process.memoryUsage();
114 | 
115 | 			const pluginInstances = [];
116 | 			const sessionCount = 100;
117 | 
118 | 			for (let i = 0; i < sessionCount; i++) {
119 | 				const plugin = customSession(async ({ user, session }) => {
120 | 					return {
121 | 						user: {
122 | 							...user,
123 | 							testField: `test-${i}`,
124 | 						},
125 | 						session,
126 | 						iteration: i,
127 | 					};
128 | 				});
129 | 				pluginInstances.push(plugin);
130 | 			}
131 | 			// Force garbage collection (only works if Node.js is started with --expose-gc)
132 | 			// @ts-expect-error
133 | 			globalThis.gc();
134 | 
135 | 			const afterPluginCreation = process.memoryUsage();
136 | 
137 | 			const memoryIncrease =
138 | 				afterPluginCreation.heapUsed - initialMemory.heapUsed;
139 | 			const memoryIncreasePerPlugin = memoryIncrease / sessionCount;
140 | 			// Each plugin instance should not use more than <5KB of memory
141 | 			// (this is a reasonable threshold that indicates no major memory leak)
142 | 			expect(memoryIncreasePerPlugin).toBeLessThan(5 * 1024);
143 | 			// Verify that plugins are still functional
144 | 			expect(pluginInstances).toHaveLength(sessionCount);
145 | 			expect(pluginInstances[0]!.id).toBe("custom-session");
146 | 			expect(pluginInstances[sessionCount - 1]!.id).toBe("custom-session");
147 | 		},
148 | 	);
149 | 
150 | 	it("should infer the session type", async () => {
151 | 		const { auth } = await getTestInstance({
152 | 			plugins: [
153 | 				customSession(async ({ user, session }) => {
154 | 					return {
155 | 						custom: {
156 | 							field: "field",
157 | 						},
158 | 					};
159 | 				}),
160 | 			],
161 | 		});
162 | 		type Session = typeof auth.$Infer.Session;
163 | 
164 | 		expectTypeOf<Session>().toEqualTypeOf<{
165 | 			custom: {
166 | 				field: string;
167 | 			};
168 | 		}>();
169 | 	});
170 | });
171 | 
```

--------------------------------------------------------------------------------
/packages/core/src/oauth2/oauth-provider.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { LiteralString } from "../types";
  2 | 
  3 | export interface OAuth2Tokens {
  4 | 	tokenType?: string;
  5 | 	accessToken?: string;
  6 | 	refreshToken?: string;
  7 | 	accessTokenExpiresAt?: Date;
  8 | 	refreshTokenExpiresAt?: Date;
  9 | 	scopes?: string[];
 10 | 	idToken?: string;
 11 | }
 12 | 
 13 | export type OAuth2UserInfo = {
 14 | 	id: string | number;
 15 | 	name?: string;
 16 | 	email?: string | null;
 17 | 	image?: string;
 18 | 	emailVerified: boolean;
 19 | };
 20 | 
 21 | export interface OAuthProvider<
 22 | 	T extends Record<string, any> = Record<string, any>,
 23 | 	O extends Record<string, any> = Partial<ProviderOptions>,
 24 | > {
 25 | 	id: LiteralString;
 26 | 	createAuthorizationURL: (data: {
 27 | 		state: string;
 28 | 		codeVerifier: string;
 29 | 		scopes?: string[];
 30 | 		redirectURI: string;
 31 | 		display?: string;
 32 | 		loginHint?: string;
 33 | 	}) => Promise<URL> | URL;
 34 | 	name: string;
 35 | 	validateAuthorizationCode: (data: {
 36 | 		code: string;
 37 | 		redirectURI: string;
 38 | 		codeVerifier?: string;
 39 | 		deviceId?: string;
 40 | 	}) => Promise<OAuth2Tokens>;
 41 | 	getUserInfo: (
 42 | 		token: OAuth2Tokens & {
 43 | 			/**
 44 | 			 * The user object from the provider
 45 | 			 * This is only available for some providers like Apple
 46 | 			 */
 47 | 			user?: {
 48 | 				name?: {
 49 | 					firstName?: string;
 50 | 					lastName?: string;
 51 | 				};
 52 | 				email?: string;
 53 | 			};
 54 | 		},
 55 | 	) => Promise<{
 56 | 		user: OAuth2UserInfo;
 57 | 		data: T;
 58 | 	} | null>;
 59 | 	/**
 60 | 	 * Custom function to refresh a token
 61 | 	 */
 62 | 	refreshAccessToken?: (refreshToken: string) => Promise<OAuth2Tokens>;
 63 | 	revokeToken?: (token: string) => Promise<void>;
 64 | 	/**
 65 | 	 * Verify the id token
 66 | 	 * @param token - The id token
 67 | 	 * @param nonce - The nonce
 68 | 	 * @returns True if the id token is valid, false otherwise
 69 | 	 */
 70 | 	verifyIdToken?: (token: string, nonce?: string) => Promise<boolean>;
 71 | 	/**
 72 | 	 * Disable implicit sign up for new users. When set to true for the provider,
 73 | 	 * sign-in need to be called with with requestSignUp as true to create new users.
 74 | 	 */
 75 | 	disableImplicitSignUp?: boolean;
 76 | 	/**
 77 | 	 * Disable sign up for new users.
 78 | 	 */
 79 | 	disableSignUp?: boolean;
 80 | 	/**
 81 | 	 * Options for the provider
 82 | 	 */
 83 | 	options?: O;
 84 | }
 85 | 
 86 | export type ProviderOptions<Profile extends Record<string, any> = any> = {
 87 | 	/**
 88 | 	 * The client ID of your application.
 89 | 	 *
 90 | 	 * This is usually a string but can be any type depending on the provider.
 91 | 	 */
 92 | 	clientId?: unknown;
 93 | 	/**
 94 | 	 * The client secret of your application
 95 | 	 */
 96 | 	clientSecret?: string;
 97 | 	/**
 98 | 	 * The scopes you want to request from the provider
 99 | 	 */
100 | 	scope?: string[];
101 | 	/**
102 | 	 * Remove default scopes of the provider
103 | 	 */
104 | 	disableDefaultScope?: boolean;
105 | 	/**
106 | 	 * The redirect URL for your application. This is where the provider will
107 | 	 * redirect the user after the sign in process. Make sure this URL is
108 | 	 * whitelisted in the provider's dashboard.
109 | 	 */
110 | 	redirectURI?: string;
111 | 	/**
112 | 	 * The client key of your application
113 | 	 * Tiktok Social Provider uses this field instead of clientId
114 | 	 */
115 | 	clientKey?: string;
116 | 	/**
117 | 	 * Disable provider from allowing users to sign in
118 | 	 * with this provider with an id token sent from the
119 | 	 * client.
120 | 	 */
121 | 	disableIdTokenSignIn?: boolean;
122 | 	/**
123 | 	 * verifyIdToken function to verify the id token
124 | 	 */
125 | 	verifyIdToken?: (token: string, nonce?: string) => Promise<boolean>;
126 | 	/**
127 | 	 * Custom function to get user info from the provider
128 | 	 */
129 | 	getUserInfo?: (token: OAuth2Tokens) => Promise<{
130 | 		user: {
131 | 			id: string;
132 | 			name?: string;
133 | 			email?: string | null;
134 | 			image?: string;
135 | 			emailVerified: boolean;
136 | 			[key: string]: any;
137 | 		};
138 | 		data: any;
139 | 	}>;
140 | 	/**
141 | 	 * Custom function to refresh a token
142 | 	 */
143 | 	refreshAccessToken?: (refreshToken: string) => Promise<OAuth2Tokens>;
144 | 	/**
145 | 	 * Custom function to map the provider profile to a
146 | 	 * user.
147 | 	 */
148 | 	mapProfileToUser?: (profile: Profile) =>
149 | 		| {
150 | 				id?: string;
151 | 				name?: string;
152 | 				email?: string | null;
153 | 				image?: string;
154 | 				emailVerified?: boolean;
155 | 				[key: string]: any;
156 | 		  }
157 | 		| Promise<{
158 | 				id?: string;
159 | 				name?: string;
160 | 				email?: string | null;
161 | 				image?: string;
162 | 				emailVerified?: boolean;
163 | 				[key: string]: any;
164 | 		  }>;
165 | 	/**
166 | 	 * Disable implicit sign up for new users. When set to true for the provider,
167 | 	 * sign-in need to be called with with requestSignUp as true to create new users.
168 | 	 */
169 | 	disableImplicitSignUp?: boolean;
170 | 	/**
171 | 	 * Disable sign up for new users.
172 | 	 */
173 | 	disableSignUp?: boolean;
174 | 	/**
175 | 	 * The prompt to use for the authorization code request
176 | 	 */
177 | 	prompt?:
178 | 		| "select_account"
179 | 		| "consent"
180 | 		| "login"
181 | 		| "none"
182 | 		| "select_account consent";
183 | 	/**
184 | 	 * The response mode to use for the authorization code request
185 | 	 */
186 | 	responseMode?: "query" | "form_post";
187 | 	/**
188 | 	 * If enabled, the user info will be overridden with the provider user info
189 | 	 * This is useful if you want to use the provider user info to update the user info
190 | 	 *
191 | 	 * @default false
192 | 	 */
193 | 	overrideUserInfoOnSignIn?: boolean;
194 | };
195 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/authentication/facebook.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Facebook
  3 | description: Facebook provider setup and usage.
  4 | ---
  5 | 
  6 | <Steps>
  7 |     <Step> 
  8 |         ### Get your Facebook credentials
  9 |         To use Facebook sign in, you need a client ID and client Secret. You can get them from the [Facebook Developer Portal](https://developers.facebook.com/).
 10 |         Select your app, navigate to **App Settings > Basic**, locate the following:  
 11 |         - **App ID**: This is your `clientId`
 12 |         - **App Secret**: This is your `clientSecret`.
 13 | 
 14 |         <Callout type="warn">
 15 |         Avoid exposing the `clientSecret` in client-side code (e.g., frontend apps) because it’s sensitive information.
 16 |         </Callout>
 17 | 
 18 |         Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/facebook` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
 19 |     </Step>
 20 | 
 21 |   <Step>
 22 |         ### Configure the provider
 23 |         To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
 24 | 
 25 |         ```ts title="auth.ts"  
 26 |         import { betterAuth } from "better-auth"
 27 |         
 28 |         export const auth = betterAuth({
 29 |             socialProviders: {
 30 |                 facebook: { // [!code highlight]
 31 |                     clientId: process.env.FACEBOOK_CLIENT_ID as string, // [!code highlight]
 32 |                     clientSecret: process.env.FACEBOOK_CLIENT_SECRET as string, // [!code highlight]
 33 |                 }, // [!code highlight]
 34 |             },
 35 |         })
 36 |         ```
 37 | 
 38 |         <Callout>
 39 |         BetterAuth also supports Facebook Login for Business, all you need
 40 |         to do is provide the `configId` as listed in **Facebook Login For Business > Configurations** alongside your `clientId` and `clientSecret`. Note that the app must be a Business app and, since BetterAuth expects to have an email address and account id, the configuration must be of the "User access token" type. "System-user access token" is not supported.
 41 |         </Callout>
 42 |     </Step>
 43 |        <Step>
 44 |         ### Sign In with Facebook
 45 |         To sign in with Facebook, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
 46 |         - `provider`: The provider to use. It should be set to `facebook`.
 47 | 
 48 |         ```ts title="auth-client.ts"
 49 |         import { createAuthClient } from "better-auth/auth-client"
 50 |         const authClient = createAuthClient()
 51 | 
 52 |         const signIn = async () => {
 53 |             const data = await authClient.signIn.social({
 54 |                 provider: "facebook"
 55 |             })
 56 |         }
 57 |         ```
 58 |     </Step>
 59 | </Steps>
 60 | 
 61 | ## Additional Configuration
 62 | 
 63 | ### Scopes
 64 | By default, Facebook provides basic user information. If you need additional permissions, you can specify scopes in your auth configuration:
 65 | 
 66 | ```ts title="auth.ts"
 67 | export const auth = betterAuth({
 68 |     socialProviders: {
 69 |         facebook: {
 70 |             clientId: process.env.FACEBOOK_CLIENT_ID as string,
 71 |             clientSecret: process.env.FACEBOOK_CLIENT_SECRET as string,
 72 |             scopes: ["email", "public_profile", "user_friends"], // Overwrites permissions
 73 |             fields: ["user_friends"], // Extending list of fields
 74 |         },
 75 |     },
 76 | })
 77 | ```
 78 | 
 79 | Additional options:
 80 | - `scopes`: Access basic account information (overwrites).
 81 |     - Default: `"email", "public_profile"`
 82 | - `fields`: Extend list of fields to retrieve from the Facebook user profile (assignment).
 83 |     - Default: `"id", "name", "email", "picture"`
 84 | 
 85 | ### Sign In with Facebook With ID or Access Token
 86 | 
 87 | To sign in with Facebook using the ID Token, you can use the `signIn.social` function to pass the ID Token.
 88 | 
 89 | This is useful when you have the ID Token from Facebook on the client-side and want to use it to sign in on the server.
 90 | 
 91 | <Callout>
 92 |  If ID token is provided no redirection will happen, and the user will be signed in directly.
 93 | </Callout>
 94 | 
 95 | For limited login, you need to pass `idToken.token`, for only `accessToken` you need to pass `idToken.accessToken` and `idToken.token` together because of (#1183)[https://github.com/better-auth/better-auth/issues/1183].
 96 | 
 97 | 
 98 | ```ts title="auth-client.ts"
 99 | const data = await authClient.signIn.social({
100 |     provider: "facebook",
101 |     idToken: {  // [!code highlight]
102 |         ...(platform === 'ios' ?  // [!code highlight]
103 |             { token: idToken }  // [!code highlight]
104 |             : { token: accessToken, accessToken: accessToken }), // [!code highlight]
105 |     },
106 | })
107 | ```
108 | 
109 | For a complete list of available permissions, refer to the [Permissions Reference](https://developers.facebook.com/docs/permissions).
110 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/additional-fields/additional-fields.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { type Session } from "./../../types";
  2 | import { describe, expect, expectTypeOf, it } from "vitest";
  3 | import { getTestInstance } from "../../test-utils/test-instance";
  4 | import { createAuthClient } from "../../client";
  5 | import { inferAdditionalFields } from "./client";
  6 | import { twoFactor, twoFactorClient } from "../two-factor";
  7 | 
  8 | describe("additionalFields", async () => {
  9 | 	const { auth, signInWithTestUser, customFetchImpl, sessionSetter } =
 10 | 		await getTestInstance({
 11 | 			plugins: [twoFactor()],
 12 | 			user: {
 13 | 				additionalFields: {
 14 | 					newField: {
 15 | 						type: "string",
 16 | 						defaultValue: "default-value",
 17 | 					},
 18 | 					nonRequiredFiled: {
 19 | 						type: "string",
 20 | 						required: false,
 21 | 					},
 22 | 				},
 23 | 			},
 24 | 		});
 25 | 
 26 | 	it("should extends fields", async () => {
 27 | 		const { headers } = await signInWithTestUser();
 28 | 		const res = await auth.api.getSession({
 29 | 			headers,
 30 | 		});
 31 | 		expect(res?.user.newField).toBeDefined();
 32 | 		expect(res?.user.nonRequiredFiled).toBeNull();
 33 | 	});
 34 | 
 35 | 	it("should require additional fields on signUp", async () => {
 36 | 		await auth.api
 37 | 			.signUpEmail({
 38 | 				body: {
 39 | 					email: "[email protected]",
 40 | 					name: "test",
 41 | 					password: "test-password",
 42 | 					newField: "new-field",
 43 | 					nonRequiredFiled: "non-required-field",
 44 | 				},
 45 | 			})
 46 | 			.catch(() => {});
 47 | 
 48 | 		const client = createAuthClient({
 49 | 			plugins: [
 50 | 				inferAdditionalFields({
 51 | 					user: {
 52 | 						newField: {
 53 | 							type: "string",
 54 | 						},
 55 | 						nonRequiredFiled: {
 56 | 							type: "string",
 57 | 							defaultValue: "test",
 58 | 						},
 59 | 					},
 60 | 				}),
 61 | 			],
 62 | 			baseURL: "http://localhost:3000",
 63 | 			fetchOptions: {
 64 | 				customFetchImpl,
 65 | 			},
 66 | 		});
 67 | 		const headers = new Headers();
 68 | 		await client.signUp.email(
 69 | 			{
 70 | 				email: "[email protected]",
 71 | 				name: "test3",
 72 | 				password: "test-password",
 73 | 				newField: "new-field",
 74 | 			},
 75 | 			{
 76 | 				onSuccess: sessionSetter(headers),
 77 | 			},
 78 | 		);
 79 | 		const res = await client.getSession({
 80 | 			fetchOptions: {
 81 | 				headers,
 82 | 			},
 83 | 		});
 84 | 		expect(res.data?.user.newField).toBe("new-field");
 85 | 	});
 86 | 
 87 | 	it("should infer additional fields on update", async () => {
 88 | 		const client = createAuthClient({
 89 | 			plugins: [
 90 | 				inferAdditionalFields({
 91 | 					user: {
 92 | 						newField: {
 93 | 							type: "string",
 94 | 						},
 95 | 					},
 96 | 				}),
 97 | 			],
 98 | 			baseURL: "http://localhost:3000",
 99 | 			fetchOptions: {
100 | 				customFetchImpl,
101 | 			},
102 | 		});
103 | 		const headers = new Headers();
104 | 		await client.signUp.email(
105 | 			{
106 | 				email: "[email protected]",
107 | 				name: "test5",
108 | 				password: "test-password",
109 | 				newField: "new-field",
110 | 			},
111 | 			{
112 | 				onSuccess: sessionSetter(headers),
113 | 			},
114 | 		);
115 | 		const res = await client.updateUser({
116 | 			name: "test",
117 | 			newField: "updated-field",
118 | 			fetchOptions: {
119 | 				headers,
120 | 			},
121 | 		});
122 | 		const session = await client.getSession({
123 | 			fetchOptions: {
124 | 				headers,
125 | 				throw: true,
126 | 			},
127 | 		});
128 | 		expect(session?.user.newField).toBe("updated-field");
129 | 	});
130 | 
131 | 	it("should work with other plugins", async () => {
132 | 		const client = createAuthClient({
133 | 			plugins: [
134 | 				inferAdditionalFields({
135 | 					user: {
136 | 						newField: {
137 | 							type: "string",
138 | 							required: true,
139 | 						},
140 | 					},
141 | 				}),
142 | 				twoFactorClient(),
143 | 			],
144 | 			baseURL: "http://localhost:3000",
145 | 			fetchOptions: {
146 | 				customFetchImpl,
147 | 			},
148 | 		});
149 | 		expectTypeOf(client.twoFactor).toMatchTypeOf<{}>();
150 | 
151 | 		const headers = new Headers();
152 | 		await client.signUp.email(
153 | 			{
154 | 				email: "[email protected]",
155 | 				name: "test4",
156 | 				password: "test-password",
157 | 				newField: "new-field",
158 | 			},
159 | 			{
160 | 				onSuccess: sessionSetter(headers),
161 | 			},
162 | 		);
163 | 		const res = await client.updateUser(
164 | 			{
165 | 				name: "test",
166 | 				newField: "updated-field",
167 | 			},
168 | 			{
169 | 				headers,
170 | 			},
171 | 		);
172 | 	});
173 | 
174 | 	it("should infer it on the client", async () => {
175 | 		const client = createAuthClient({
176 | 			plugins: [inferAdditionalFields<typeof auth>()],
177 | 		});
178 | 		type t = Awaited<ReturnType<typeof client.getSession>>["data"];
179 | 		expectTypeOf<t>().toMatchTypeOf<{
180 | 			user: {
181 | 				id: string;
182 | 				email: string;
183 | 				emailVerified: boolean;
184 | 				name: string;
185 | 				createdAt: Date;
186 | 				updatedAt: Date;
187 | 				image?: string | undefined;
188 | 				newField: string;
189 | 				nonRequiredFiled?: string | undefined;
190 | 			};
191 | 			session: Session;
192 | 		} | null>;
193 | 	});
194 | 
195 | 	it("should infer it on the client without direct import", async () => {
196 | 		const client = createAuthClient({
197 | 			plugins: [
198 | 				inferAdditionalFields({
199 | 					user: {
200 | 						newField: {
201 | 							type: "string",
202 | 						},
203 | 					},
204 | 				}),
205 | 			],
206 | 		});
207 | 		type t = Awaited<ReturnType<typeof client.getSession>>["data"];
208 | 		expectTypeOf<t>().toMatchTypeOf<{
209 | 			user: {
210 | 				id: string;
211 | 				email: string;
212 | 				emailVerified: boolean;
213 | 				name: string;
214 | 				createdAt: Date;
215 | 				updatedAt: Date;
216 | 				image?: string | undefined;
217 | 				newField: string;
218 | 			};
219 | 			session: Session;
220 | 		} | null>;
221 | 	});
222 | });
223 | 
```

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

```markdown
 1 | ---
 2 | title: Captcha
 3 | description: Captcha plugin
 4 | ---
 5 | 
 6 | The **Captcha Plugin** integrates bot protection into your Better Auth system by adding captcha verification for key endpoints. This plugin ensures that only human users can perform actions like signing up, signing in, or resetting passwords. The following providers are currently supported:
 7 | - [Google reCAPTCHA](https://developers.google.com/recaptcha)
 8 | - [Cloudflare Turnstile](https://www.cloudflare.com/application-services/products/turnstile/)
 9 | - [hCaptcha](https://www.hcaptcha.com/)
10 | - [CaptchaFox](https://captchafox.com/)
11 | 
12 | <Callout type="info">
13 |   This plugin works out of the box with <Link href="/docs/authentication/email-password">Email & Password</Link> authentication. To use it with other authentication methods, you will need to configure the <Link href="/docs/plugins/captcha#plugin-options">endpoints</Link> array in the plugin options.
14 | </Callout>
15 | 
16 | ## Installation
17 | 
18 | <Steps>
19 |   <Step>
20 |     ### Add the plugin to your **auth** config
21 | 
22 |     ```ts title="auth.ts"
23 |     import { betterAuth } from "better-auth";
24 |     import { captcha } from "better-auth/plugins";
25 | 
26 |     export const auth = betterAuth({
27 |         plugins: [ // [!code highlight]
28 |             captcha({ // [!code highlight]
29 |                 provider: "cloudflare-turnstile", // or google-recaptcha, hcaptcha, captchafox // [!code highlight]
30 |                 secretKey: process.env.TURNSTILE_SECRET_KEY!, // [!code highlight]
31 |             }), // [!code highlight]
32 |         ], // [!code highlight]
33 |     });
34 |     ```
35 | 
36 |   </Step>
37 |   <Step>
38 |     ### Add the captcha token to your request headers
39 | 
40 |     Add the captcha token to your request headers for all protected endpoints. This example shows how to include it in a `signIn` request:
41 | 
42 |     ```ts
43 |     await authClient.signIn.email({
44 |         email: "[email protected]",
45 |         password: "secure-password",
46 |         fetchOptions: { // [!code highlight]
47 |             headers: { // [!code highlight]
48 |                 "x-captcha-response": turnstileToken, // [!code highlight]
49 |                 "x-captcha-user-remote-ip": userIp, // optional: forwards the user's IP address to the captcha service // [!code highlight]
50 |             }, // [!code highlight]
51 |         }, // [!code highlight]
52 |     });
53 |     ```
54 | 
55 |     - To implement Cloudflare Turnstile on the client side, follow the official [Cloudflare Turnstile documentation](https://developers.cloudflare.com/turnstile/) or use a library like [react-turnstile](https://www.npmjs.com/package/@marsidev/react-turnstile).
56 |     - To implement Google reCAPTCHA on the client side, follow the official [Google reCAPTCHA documentation](https://developers.google.com/recaptcha/intro) or use libraries like [react-google-recaptcha](https://www.npmjs.com/package/react-google-recaptcha) (v2) and [react-google-recaptcha-v3](https://www.npmjs.com/package/react-google-recaptcha-v3) (v3).
57 |     - To implement hCaptcha on the client side, follow the official [hCaptcha documentation](https://docs.hcaptcha.com/#add-the-hcaptcha-widget-to-your-webpage) or use libraries like [@hcaptcha/react-hcaptcha](https://www.npmjs.com/package/@hcaptcha/react-hcaptcha)
58 |     - To implement CaptchaFox on the client side, follow the official [CaptchaFox documentation](https://docs.captchafox.com/getting-started) or use libraries like [@captchafox/react](https://www.npmjs.com/package/@captchafox/react)
59 |   </Step>
60 | </Steps>
61 | 
62 | ## How it works
63 | 
64 | <Steps>
65 |   <Step>
66 |     The plugin acts as a middleware: it intercepts all `POST` requests to configured endpoints (see `endpoints`
67 |     in the [Plugin Options](#plugin-options) section).
68 |   </Step>
69 |   <Step>
70 |     it validates the captcha token on the server, by calling the captcha provider's `/siteverify`.
71 |   </Step>
72 |   <Step>
73 |     - if the token is missing, gets rejected by the captcha provider, or if the `/siteverify` endpoint is
74 |     unavailable, the plugin returns an error and interrupts the request.
75 |     - if the token is accepted by the captcha provider, the middleware returns `undefined`, meaning the request is allowed to proceed.
76 | 
77 |   </Step>
78 | </Steps>
79 | 
80 | ## Plugin Options
81 | 
82 | - **`provider` (required)**: your captcha provider.
83 | - **`secretKey` (required)**: your provider's secret key used for the server-side validation.
84 | - `endpoints` (optional): overrides the default array of paths where captcha validation is enforced. Default is: `["/sign-up/email", "/sign-in/email", "/forget-password",]`.
85 | - `minScore` (optional - only *Google ReCAPTCHA v3*): minimum score threshold. Default is `0.5`.
86 | - `siteKey` (optional - only *hCaptcha* and *CaptchaFox*): prevents tokens issued on one sitekey from being redeemed elsewhere.
87 | - `siteVerifyURLOverride` (optional): overrides endpoint URL for the captcha verification request.
```
Page 15/69FirstPrevNextLast