This is page 11 of 69. Use http://codebase.md/better-auth/better-auth?format=md&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
│   │   │       ├── react-logo@2x.png
│   │   │       ├── react-logo@3x.png
│   │   │       └── splash.png
│   │   ├── babel.config.js
│   │   ├── components.json
│   │   ├── expo-env.d.ts
│   │   ├── index.ts
│   │   ├── metro.config.js
│   │   ├── nativewind-env.d.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── app
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── api
│   │   │   │   │   └── auth
│   │   │   │   │       └── [...route]+api.ts
│   │   │   │   ├── dashboard.tsx
│   │   │   │   ├── forget-password.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   └── sign-up.tsx
│   │   │   ├── components
│   │   │   │   ├── icons
│   │   │   │   │   └── google.tsx
│   │   │   │   └── ui
│   │   │   │       ├── avatar.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       └── text.tsx
│   │   │   ├── global.css
│   │   │   └── lib
│   │   │       ├── auth-client.ts
│   │   │       ├── auth.ts
│   │   │       ├── icons
│   │   │       │   ├── iconWithClassName.ts
│   │   │       │   └── X.tsx
│   │   │       └── utils.ts
│   │   ├── tailwind.config.js
│   │   └── tsconfig.json
│   └── nextjs
│       ├── .env.example
│       ├── .gitignore
│       ├── app
│       │   ├── (auth)
│       │   │   ├── forget-password
│       │   │   │   └── page.tsx
│       │   │   ├── reset-password
│       │   │   │   └── page.tsx
│       │   │   ├── sign-in
│       │   │   │   ├── loading.tsx
│       │   │   │   └── page.tsx
│       │   │   └── two-factor
│       │   │       ├── otp
│       │   │       │   └── page.tsx
│       │   │       └── page.tsx
│       │   ├── accept-invitation
│       │   │   └── [id]
│       │   │       ├── invitation-error.tsx
│       │   │       └── page.tsx
│       │   ├── admin
│       │   │   └── page.tsx
│       │   ├── api
│       │   │   └── auth
│       │   │       └── [...all]
│       │   │           └── route.ts
│       │   ├── apps
│       │   │   └── register
│       │   │       └── page.tsx
│       │   ├── client-test
│       │   │   └── page.tsx
│       │   ├── dashboard
│       │   │   ├── change-plan.tsx
│       │   │   ├── client.tsx
│       │   │   ├── organization-card.tsx
│       │   │   ├── page.tsx
│       │   │   ├── upgrade-button.tsx
│       │   │   └── user-card.tsx
│       │   ├── device
│       │   │   ├── approve
│       │   │   │   └── page.tsx
│       │   │   ├── denied
│       │   │   │   └── page.tsx
│       │   │   ├── layout.tsx
│       │   │   ├── page.tsx
│       │   │   └── success
│       │   │       └── page.tsx
│       │   ├── favicon.ico
│       │   ├── features.tsx
│       │   ├── fonts
│       │   │   ├── GeistMonoVF.woff
│       │   │   └── GeistVF.woff
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   ├── oauth
│       │   │   └── authorize
│       │   │       ├── concet-buttons.tsx
│       │   │       └── page.tsx
│       │   ├── page.tsx
│       │   └── pricing
│       │       └── page.tsx
│       ├── components
│       │   ├── account-switch.tsx
│       │   ├── blocks
│       │   │   └── pricing.tsx
│       │   ├── logo.tsx
│       │   ├── one-tap.tsx
│       │   ├── sign-in-btn.tsx
│       │   ├── sign-in.tsx
│       │   ├── sign-up.tsx
│       │   ├── theme-provider.tsx
│       │   ├── theme-toggle.tsx
│       │   ├── tier-labels.tsx
│       │   ├── ui
│       │   │   ├── accordion.tsx
│       │   │   ├── alert-dialog.tsx
│       │   │   ├── alert.tsx
│       │   │   ├── aspect-ratio.tsx
│       │   │   ├── avatar.tsx
│       │   │   ├── badge.tsx
│       │   │   ├── breadcrumb.tsx
│       │   │   ├── button.tsx
│       │   │   ├── calendar.tsx
│       │   │   ├── card.tsx
│       │   │   ├── carousel.tsx
│       │   │   ├── chart.tsx
│       │   │   ├── checkbox.tsx
│       │   │   ├── collapsible.tsx
│       │   │   ├── command.tsx
│       │   │   ├── context-menu.tsx
│       │   │   ├── copy-button.tsx
│       │   │   ├── dialog.tsx
│       │   │   ├── drawer.tsx
│       │   │   ├── dropdown-menu.tsx
│       │   │   ├── form.tsx
│       │   │   ├── hover-card.tsx
│       │   │   ├── input-otp.tsx
│       │   │   ├── input.tsx
│       │   │   ├── label.tsx
│       │   │   ├── menubar.tsx
│       │   │   ├── navigation-menu.tsx
│       │   │   ├── pagination.tsx
│       │   │   ├── password-input.tsx
│       │   │   ├── popover.tsx
│       │   │   ├── progress.tsx
│       │   │   ├── radio-group.tsx
│       │   │   ├── resizable.tsx
│       │   │   ├── scroll-area.tsx
│       │   │   ├── select.tsx
│       │   │   ├── separator.tsx
│       │   │   ├── sheet.tsx
│       │   │   ├── skeleton.tsx
│       │   │   ├── slider.tsx
│       │   │   ├── sonner.tsx
│       │   │   ├── switch.tsx
│       │   │   ├── table.tsx
│       │   │   ├── tabs.tsx
│       │   │   ├── tabs2.tsx
│       │   │   ├── textarea.tsx
│       │   │   ├── toast.tsx
│       │   │   ├── toaster.tsx
│       │   │   ├── toggle-group.tsx
│       │   │   ├── toggle.tsx
│       │   │   └── tooltip.tsx
│       │   └── wrapper.tsx
│       ├── components.json
│       ├── hooks
│       │   └── use-toast.ts
│       ├── lib
│       │   ├── auth-client.ts
│       │   ├── auth-types.ts
│       │   ├── auth.ts
│       │   ├── email
│       │   │   ├── invitation.tsx
│       │   │   ├── resend.ts
│       │   │   └── reset-password.tsx
│       │   ├── metadata.ts
│       │   ├── shared.ts
│       │   └── utils.ts
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.mjs
│       ├── proxy.ts
│       ├── public
│       │   ├── __og.png
│       │   ├── _og.png
│       │   ├── favicon
│       │   │   ├── android-chrome-192x192.png
│       │   │   ├── android-chrome-512x512.png
│       │   │   ├── apple-touch-icon.png
│       │   │   ├── favicon-16x16.png
│       │   │   ├── favicon-32x32.png
│       │   │   ├── favicon.ico
│       │   │   ├── light
│       │   │   │   ├── android-chrome-192x192.png
│       │   │   │   ├── android-chrome-512x512.png
│       │   │   │   ├── apple-touch-icon.png
│       │   │   │   ├── favicon-16x16.png
│       │   │   │   ├── favicon-32x32.png
│       │   │   │   ├── favicon.ico
│       │   │   │   └── site.webmanifest
│       │   │   └── site.webmanifest
│       │   ├── logo.svg
│       │   └── og.png
│       ├── README.md
│       ├── tailwind.config.ts
│       ├── tsconfig.json
│       └── turbo.json
├── docker-compose.yml
├── docs
│   ├── .env.example
│   ├── .gitignore
│   ├── app
│   │   ├── api
│   │   │   ├── ai-chat
│   │   │   │   └── route.ts
│   │   │   ├── analytics
│   │   │   │   ├── conversation
│   │   │   │   │   └── route.ts
│   │   │   │   ├── event
│   │   │   │   │   └── route.ts
│   │   │   │   └── feedback
│   │   │   │       └── route.ts
│   │   │   ├── chat
│   │   │   │   └── route.ts
│   │   │   ├── og
│   │   │   │   └── route.tsx
│   │   │   ├── og-release
│   │   │   │   └── route.tsx
│   │   │   ├── search
│   │   │   │   └── route.ts
│   │   │   └── support
│   │   │       └── route.ts
│   │   ├── blog
│   │   │   ├── _components
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── blog-list.tsx
│   │   │   │   ├── changelog-layout.tsx
│   │   │   │   ├── default-changelog.tsx
│   │   │   │   ├── fmt-dates.tsx
│   │   │   │   ├── icons.tsx
│   │   │   │   ├── stat-field.tsx
│   │   │   │   └── support.tsx
│   │   │   ├── [[...slug]]
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── changelogs
│   │   │   ├── _components
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── changelog-layout.tsx
│   │   │   │   ├── default-changelog.tsx
│   │   │   │   ├── fmt-dates.tsx
│   │   │   │   ├── grid-pattern.tsx
│   │   │   │   ├── icons.tsx
│   │   │   │   └── stat-field.tsx
│   │   │   ├── [[...slug]]
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── community
│   │   │   ├── _components
│   │   │   │   ├── header.tsx
│   │   │   │   └── stats.tsx
│   │   │   └── page.tsx
│   │   ├── docs
│   │   │   ├── [[...slug]]
│   │   │   │   ├── page.client.tsx
│   │   │   │   └── page.tsx
│   │   │   ├── layout.tsx
│   │   │   └── lib
│   │   │       └── get-llm-text.ts
│   │   ├── global.css
│   │   ├── layout.config.tsx
│   │   ├── layout.tsx
│   │   ├── llms.txt
│   │   │   ├── [...slug]
│   │   │   │   └── route.ts
│   │   │   └── route.ts
│   │   ├── not-found.tsx
│   │   ├── page.tsx
│   │   ├── reference
│   │   │   └── route.ts
│   │   ├── sitemap.xml
│   │   ├── static.json
│   │   │   └── route.ts
│   │   └── v1
│   │       ├── _components
│   │       │   └── v1-text.tsx
│   │       ├── bg-line.tsx
│   │       └── page.tsx
│   ├── assets
│   │   ├── Geist.ttf
│   │   └── GeistMono.ttf
│   ├── components
│   │   ├── ai-chat-modal.tsx
│   │   ├── anchor-scroll-fix.tsx
│   │   ├── api-method-tabs.tsx
│   │   ├── api-method.tsx
│   │   ├── banner.tsx
│   │   ├── blocks
│   │   │   └── features.tsx
│   │   ├── builder
│   │   │   ├── beam.tsx
│   │   │   ├── code-tabs
│   │   │   │   ├── code-editor.tsx
│   │   │   │   ├── code-tabs.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── tab-bar.tsx
│   │   │   │   └── theme.ts
│   │   │   ├── index.tsx
│   │   │   ├── sign-in.tsx
│   │   │   ├── sign-up.tsx
│   │   │   ├── social-provider.tsx
│   │   │   ├── store.ts
│   │   │   └── tabs.tsx
│   │   ├── display-techstack.tsx
│   │   ├── divider-text.tsx
│   │   ├── docs
│   │   │   ├── docs.client.tsx
│   │   │   ├── docs.tsx
│   │   │   ├── layout
│   │   │   │   ├── nav.tsx
│   │   │   │   ├── theme-toggle.tsx
│   │   │   │   ├── toc-thumb.tsx
│   │   │   │   └── toc.tsx
│   │   │   ├── page.client.tsx
│   │   │   ├── page.tsx
│   │   │   ├── shared.tsx
│   │   │   └── ui
│   │   │       ├── button.tsx
│   │   │       ├── collapsible.tsx
│   │   │       ├── popover.tsx
│   │   │       └── scroll-area.tsx
│   │   ├── endpoint.tsx
│   │   ├── features.tsx
│   │   ├── floating-ai-search.tsx
│   │   ├── fork-button.tsx
│   │   ├── generate-apple-jwt.tsx
│   │   ├── generate-secret.tsx
│   │   ├── github-stat.tsx
│   │   ├── icons.tsx
│   │   ├── landing
│   │   │   ├── gradient-bg.tsx
│   │   │   ├── grid-pattern.tsx
│   │   │   ├── hero.tsx
│   │   │   ├── section-svg.tsx
│   │   │   ├── section.tsx
│   │   │   ├── spotlight.tsx
│   │   │   └── testimonials.tsx
│   │   ├── logo-context-menu.tsx
│   │   ├── logo.tsx
│   │   ├── markdown-renderer.tsx
│   │   ├── markdown.tsx
│   │   ├── mdx
│   │   │   ├── add-to-cursor.tsx
│   │   │   └── database-tables.tsx
│   │   ├── message-feedback.tsx
│   │   ├── mobile-search-icon.tsx
│   │   ├── nav-bar.tsx
│   │   ├── nav-link.tsx
│   │   ├── nav-mobile.tsx
│   │   ├── promo-card.tsx
│   │   ├── resource-card.tsx
│   │   ├── resource-grid.tsx
│   │   ├── resource-section.tsx
│   │   ├── ripple.tsx
│   │   ├── search-dialog.tsx
│   │   ├── side-bar.tsx
│   │   ├── sidebar-content.tsx
│   │   ├── techstack-icons.tsx
│   │   ├── theme-provider.tsx
│   │   ├── theme-toggler.tsx
│   │   └── ui
│   │       ├── accordion.tsx
│   │       ├── alert-dialog.tsx
│   │       ├── alert.tsx
│   │       ├── aside-link.tsx
│   │       ├── aspect-ratio.tsx
│   │       ├── avatar.tsx
│   │       ├── background-beams.tsx
│   │       ├── background-boxes.tsx
│   │       ├── badge.tsx
│   │       ├── breadcrumb.tsx
│   │       ├── button.tsx
│   │       ├── calendar.tsx
│   │       ├── callout.tsx
│   │       ├── card.tsx
│   │       ├── carousel.tsx
│   │       ├── chart.tsx
│   │       ├── checkbox.tsx
│   │       ├── code-block.tsx
│   │       ├── collapsible.tsx
│   │       ├── command.tsx
│   │       ├── context-menu.tsx
│   │       ├── dialog.tsx
│   │       ├── drawer.tsx
│   │       ├── dropdown-menu.tsx
│   │       ├── dynamic-code-block.tsx
│   │       ├── fade-in.tsx
│   │       ├── form.tsx
│   │       ├── hover-card.tsx
│   │       ├── input-otp.tsx
│   │       ├── input.tsx
│   │       ├── label.tsx
│   │       ├── menubar.tsx
│   │       ├── navigation-menu.tsx
│   │       ├── pagination.tsx
│   │       ├── popover.tsx
│   │       ├── progress.tsx
│   │       ├── radio-group.tsx
│   │       ├── resizable.tsx
│   │       ├── scroll-area.tsx
│   │       ├── select.tsx
│   │       ├── separator.tsx
│   │       ├── sheet.tsx
│   │       ├── sidebar.tsx
│   │       ├── skeleton.tsx
│   │       ├── slider.tsx
│   │       ├── sonner.tsx
│   │       ├── sparkles.tsx
│   │       ├── switch.tsx
│   │       ├── table.tsx
│   │       ├── tabs.tsx
│   │       ├── textarea.tsx
│   │       ├── toggle-group.tsx
│   │       ├── toggle.tsx
│   │       ├── tooltip-docs.tsx
│   │       ├── tooltip.tsx
│   │       └── use-copy-button.tsx
│   ├── components.json
│   ├── content
│   │   ├── blogs
│   │   │   ├── 0-supabase-auth-to-planetscale-migration.mdx
│   │   │   ├── 1-3.mdx
│   │   │   ├── authjs-joins-better-auth.mdx
│   │   │   └── seed-round.mdx
│   │   ├── changelogs
│   │   │   ├── 1-2.mdx
│   │   │   └── 1.0.mdx
│   │   └── docs
│   │       ├── adapters
│   │       │   ├── community-adapters.mdx
│   │       │   ├── drizzle.mdx
│   │       │   ├── mongo.mdx
│   │       │   ├── mssql.mdx
│   │       │   ├── mysql.mdx
│   │       │   ├── other-relational-databases.mdx
│   │       │   ├── postgresql.mdx
│   │       │   ├── prisma.mdx
│   │       │   └── sqlite.mdx
│   │       ├── authentication
│   │       │   ├── apple.mdx
│   │       │   ├── atlassian.mdx
│   │       │   ├── cognito.mdx
│   │       │   ├── discord.mdx
│   │       │   ├── dropbox.mdx
│   │       │   ├── email-password.mdx
│   │       │   ├── facebook.mdx
│   │       │   ├── figma.mdx
│   │       │   ├── github.mdx
│   │       │   ├── gitlab.mdx
│   │       │   ├── google.mdx
│   │       │   ├── huggingface.mdx
│   │       │   ├── kakao.mdx
│   │       │   ├── kick.mdx
│   │       │   ├── line.mdx
│   │       │   ├── linear.mdx
│   │       │   ├── linkedin.mdx
│   │       │   ├── microsoft.mdx
│   │       │   ├── naver.mdx
│   │       │   ├── notion.mdx
│   │       │   ├── other-social-providers.mdx
│   │       │   ├── paypal.mdx
│   │       │   ├── reddit.mdx
│   │       │   ├── roblox.mdx
│   │       │   ├── salesforce.mdx
│   │       │   ├── slack.mdx
│   │       │   ├── spotify.mdx
│   │       │   ├── tiktok.mdx
│   │       │   ├── twitch.mdx
│   │       │   ├── twitter.mdx
│   │       │   ├── vk.mdx
│   │       │   └── zoom.mdx
│   │       ├── basic-usage.mdx
│   │       ├── comparison.mdx
│   │       ├── concepts
│   │       │   ├── api.mdx
│   │       │   ├── cli.mdx
│   │       │   ├── client.mdx
│   │       │   ├── cookies.mdx
│   │       │   ├── database.mdx
│   │       │   ├── email.mdx
│   │       │   ├── hooks.mdx
│   │       │   ├── oauth.mdx
│   │       │   ├── plugins.mdx
│   │       │   ├── rate-limit.mdx
│   │       │   ├── session-management.mdx
│   │       │   ├── typescript.mdx
│   │       │   └── users-accounts.mdx
│   │       ├── examples
│   │       │   ├── astro.mdx
│   │       │   ├── next-js.mdx
│   │       │   ├── nuxt.mdx
│   │       │   ├── remix.mdx
│   │       │   └── svelte-kit.mdx
│   │       ├── guides
│   │       │   ├── auth0-migration-guide.mdx
│   │       │   ├── browser-extension-guide.mdx
│   │       │   ├── clerk-migration-guide.mdx
│   │       │   ├── create-a-db-adapter.mdx
│   │       │   ├── next-auth-migration-guide.mdx
│   │       │   ├── optimizing-for-performance.mdx
│   │       │   ├── saml-sso-with-okta.mdx
│   │       │   ├── supabase-migration-guide.mdx
│   │       │   └── your-first-plugin.mdx
│   │       ├── installation.mdx
│   │       ├── integrations
│   │       │   ├── astro.mdx
│   │       │   ├── convex.mdx
│   │       │   ├── elysia.mdx
│   │       │   ├── expo.mdx
│   │       │   ├── express.mdx
│   │       │   ├── fastify.mdx
│   │       │   ├── hono.mdx
│   │       │   ├── lynx.mdx
│   │       │   ├── nestjs.mdx
│   │       │   ├── next.mdx
│   │       │   ├── nitro.mdx
│   │       │   ├── nuxt.mdx
│   │       │   ├── remix.mdx
│   │       │   ├── solid-start.mdx
│   │       │   ├── svelte-kit.mdx
│   │       │   ├── tanstack.mdx
│   │       │   └── waku.mdx
│   │       ├── introduction.mdx
│   │       ├── meta.json
│   │       ├── plugins
│   │       │   ├── 2fa.mdx
│   │       │   ├── admin.mdx
│   │       │   ├── anonymous.mdx
│   │       │   ├── api-key.mdx
│   │       │   ├── autumn.mdx
│   │       │   ├── bearer.mdx
│   │       │   ├── captcha.mdx
│   │       │   ├── community-plugins.mdx
│   │       │   ├── device-authorization.mdx
│   │       │   ├── dodopayments.mdx
│   │       │   ├── dub.mdx
│   │       │   ├── email-otp.mdx
│   │       │   ├── generic-oauth.mdx
│   │       │   ├── have-i-been-pwned.mdx
│   │       │   ├── jwt.mdx
│   │       │   ├── last-login-method.mdx
│   │       │   ├── magic-link.mdx
│   │       │   ├── mcp.mdx
│   │       │   ├── multi-session.mdx
│   │       │   ├── oauth-proxy.mdx
│   │       │   ├── oidc-provider.mdx
│   │       │   ├── one-tap.mdx
│   │       │   ├── one-time-token.mdx
│   │       │   ├── open-api.mdx
│   │       │   ├── organization.mdx
│   │       │   ├── passkey.mdx
│   │       │   ├── phone-number.mdx
│   │       │   ├── polar.mdx
│   │       │   ├── siwe.mdx
│   │       │   ├── sso.mdx
│   │       │   ├── stripe.mdx
│   │       │   └── username.mdx
│   │       └── reference
│   │           ├── contributing.mdx
│   │           ├── faq.mdx
│   │           ├── options.mdx
│   │           ├── resources.mdx
│   │           ├── security.mdx
│   │           └── telemetry.mdx
│   ├── hooks
│   │   └── use-mobile.ts
│   ├── ignore-build.sh
│   ├── lib
│   │   ├── blog.ts
│   │   ├── chat
│   │   │   └── inkeep-qa-schema.ts
│   │   ├── constants.ts
│   │   ├── export-search-indexes.ts
│   │   ├── inkeep-analytics.ts
│   │   ├── is-active.ts
│   │   ├── metadata.ts
│   │   ├── source.ts
│   │   └── utils.ts
│   ├── next.config.js
│   ├── package.json
│   ├── postcss.config.js
│   ├── proxy.ts
│   ├── public
│   │   ├── avatars
│   │   │   └── beka.jpg
│   │   ├── blogs
│   │   │   ├── authjs-joins.png
│   │   │   ├── seed-round.png
│   │   │   └── supabase-ps.png
│   │   ├── branding
│   │   │   ├── better-auth-brand-assets.zip
│   │   │   ├── better-auth-logo-dark.png
│   │   │   ├── better-auth-logo-dark.svg
│   │   │   ├── better-auth-logo-light.png
│   │   │   ├── better-auth-logo-light.svg
│   │   │   ├── better-auth-logo-wordmark-dark.png
│   │   │   ├── better-auth-logo-wordmark-dark.svg
│   │   │   ├── better-auth-logo-wordmark-light.png
│   │   │   └── better-auth-logo-wordmark-light.svg
│   │   ├── extension-id.png
│   │   ├── favicon
│   │   │   ├── android-chrome-192x192.png
│   │   │   ├── android-chrome-512x512.png
│   │   │   ├── apple-touch-icon.png
│   │   │   ├── favicon-16x16.png
│   │   │   ├── favicon-32x32.png
│   │   │   ├── favicon.ico
│   │   │   ├── light
│   │   │   │   ├── android-chrome-192x192.png
│   │   │   │   ├── android-chrome-512x512.png
│   │   │   │   ├── apple-touch-icon.png
│   │   │   │   ├── favicon-16x16.png
│   │   │   │   ├── favicon-32x32.png
│   │   │   │   ├── favicon.ico
│   │   │   │   └── site.webmanifest
│   │   │   └── site.webmanifest
│   │   ├── images
│   │   │   └── blogs
│   │   │       └── better auth (1).png
│   │   ├── logo.png
│   │   ├── logo.svg
│   │   ├── LogoDark.webp
│   │   ├── LogoLight.webp
│   │   ├── og.png
│   │   ├── open-api-reference.png
│   │   ├── people-say
│   │   │   ├── code-with-antonio.jpg
│   │   │   ├── dagmawi-babi.png
│   │   │   ├── dax.png
│   │   │   ├── dev-ed.png
│   │   │   ├── egoist.png
│   │   │   ├── guillermo-rauch.png
│   │   │   ├── jonathan-wilke.png
│   │   │   ├── josh-tried-coding.jpg
│   │   │   ├── kitze.jpg
│   │   │   ├── lazar-nikolov.png
│   │   │   ├── nizzy.png
│   │   │   ├── omar-mcadam.png
│   │   │   ├── ryan-vogel.jpg
│   │   │   ├── saltyatom.jpg
│   │   │   ├── sebastien-chopin.png
│   │   │   ├── shreyas-mididoddi.png
│   │   │   ├── tech-nerd.png
│   │   │   ├── theo.png
│   │   │   ├── vybhav-bhargav.png
│   │   │   └── xavier-pladevall.jpg
│   │   ├── plus.svg
│   │   ├── release-og
│   │   │   ├── 1-2.png
│   │   │   ├── 1-3.png
│   │   │   └── changelog-og.png
│   │   └── v1-og.png
│   ├── README.md
│   ├── scripts
│   │   ├── endpoint-to-doc
│   │   │   ├── index.ts
│   │   │   ├── input.ts
│   │   │   ├── output.mdx
│   │   │   └── readme.md
│   │   └── sync-orama.ts
│   ├── source.config.ts
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   └── turbo.json
├── e2e
│   ├── integration
│   │   ├── package.json
│   │   ├── playwright.config.ts
│   │   ├── solid-vinxi
│   │   │   ├── .gitignore
│   │   │   ├── app.config.ts
│   │   │   ├── e2e
│   │   │   │   ├── test.spec.ts
│   │   │   │   └── utils.ts
│   │   │   ├── package.json
│   │   │   ├── public
│   │   │   │   └── favicon.ico
│   │   │   ├── src
│   │   │   │   ├── app.tsx
│   │   │   │   ├── entry-client.tsx
│   │   │   │   ├── entry-server.tsx
│   │   │   │   ├── global.d.ts
│   │   │   │   ├── lib
│   │   │   │   │   ├── auth-client.ts
│   │   │   │   │   └── auth.ts
│   │   │   │   └── routes
│   │   │   │       ├── [...404].tsx
│   │   │   │       ├── api
│   │   │   │       │   └── auth
│   │   │   │       │       └── [...all].ts
│   │   │   │       └── index.tsx
│   │   │   └── tsconfig.json
│   │   ├── test-utils
│   │   │   ├── package.json
│   │   │   └── src
│   │   │       └── playwright.ts
│   │   └── vanilla-node
│   │       ├── e2e
│   │       │   ├── app.ts
│   │       │   ├── domain.spec.ts
│   │       │   ├── postgres-js.spec.ts
│   │       │   ├── test.spec.ts
│   │       │   └── utils.ts
│   │       ├── index.html
│   │       ├── package.json
│   │       ├── src
│   │       │   ├── main.ts
│   │       │   └── vite-env.d.ts
│   │       ├── tsconfig.json
│   │       └── vite.config.ts
│   └── smoke
│       ├── package.json
│       ├── test
│       │   ├── bun.spec.ts
│       │   ├── cloudflare.spec.ts
│       │   ├── deno.spec.ts
│       │   ├── fixtures
│       │   │   ├── bun-simple.ts
│       │   │   ├── cloudflare
│       │   │   │   ├── .gitignore
│       │   │   │   ├── drizzle
│       │   │   │   │   ├── 0000_clean_vector.sql
│       │   │   │   │   └── meta
│       │   │   │   │       ├── _journal.json
│       │   │   │   │       └── 0000_snapshot.json
│       │   │   │   ├── drizzle.config.ts
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── auth-schema.ts
│       │   │   │   │   ├── db.ts
│       │   │   │   │   └── index.ts
│       │   │   │   ├── test
│       │   │   │   │   ├── apply-migrations.ts
│       │   │   │   │   ├── env.d.ts
│       │   │   │   │   └── index.test.ts
│       │   │   │   ├── tsconfig.json
│       │   │   │   ├── vitest.config.ts
│       │   │   │   ├── worker-configuration.d.ts
│       │   │   │   └── wrangler.json
│       │   │   ├── deno-simple.ts
│       │   │   ├── tsconfig-decelration
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── demo.ts
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-exact-optional-property-types
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── index.ts
│       │   │   │   │   ├── organization.ts
│       │   │   │   │   ├── user-additional-fields.ts
│       │   │   │   │   └── username.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-isolated-module-bundler
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-verbatim-module-syntax-node10
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   └── vite
│       │   │       ├── package.json
│       │   │       ├── src
│       │   │       │   ├── client.ts
│       │   │       │   └── server.ts
│       │   │       ├── tsconfig.json
│       │   │       └── vite.config.ts
│       │   ├── ssr.ts
│       │   ├── typecheck.spec.ts
│       │   └── vite.spec.ts
│       └── tsconfig.json
├── LICENSE.md
├── package.json
├── packages
│   ├── better-auth
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── __snapshots__
│   │   │   │   └── init.test.ts.snap
│   │   │   ├── adapters
│   │   │   │   ├── adapter-factory
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── test
│   │   │   │   │   │   ├── __snapshots__
│   │   │   │   │   │   │   └── adapter-factory.test.ts.snap
│   │   │   │   │   │   └── adapter-factory.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── create-test-suite.ts
│   │   │   │   ├── drizzle-adapter
│   │   │   │   │   ├── drizzle-adapter.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── test
│   │   │   │   │       ├── .gitignore
│   │   │   │   │       ├── adapter.drizzle.mysql.test.ts
│   │   │   │   │       ├── adapter.drizzle.pg.test.ts
│   │   │   │   │       ├── adapter.drizzle.sqlite.test.ts
│   │   │   │   │       └── generate-schema.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kysely-adapter
│   │   │   │   │   ├── bun-sqlite-dialect.ts
│   │   │   │   │   ├── dialect.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── kysely-adapter.ts
│   │   │   │   │   ├── node-sqlite-dialect.ts
│   │   │   │   │   ├── test
│   │   │   │   │   │   ├── adapter.kysely.mssql.test.ts
│   │   │   │   │   │   ├── adapter.kysely.mysql.test.ts
│   │   │   │   │   │   ├── adapter.kysely.pg.test.ts
│   │   │   │   │   │   ├── adapter.kysely.sqlite.test.ts
│   │   │   │   │   │   └── node-sqlite-dialect.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── memory-adapter
│   │   │   │   │   ├── adapter.memory.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── memory-adapter.ts
│   │   │   │   ├── mongodb-adapter
│   │   │   │   │   ├── adapter.mongo-db.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── mongodb-adapter.ts
│   │   │   │   ├── prisma-adapter
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── prisma-adapter.ts
│   │   │   │   │   └── test
│   │   │   │   │       ├── .gitignore
│   │   │   │   │       ├── base.prisma
│   │   │   │   │       ├── generate-auth-config.ts
│   │   │   │   │       ├── generate-prisma-schema.ts
│   │   │   │   │       ├── get-prisma-client.ts
│   │   │   │   │       ├── prisma.mysql.test.ts
│   │   │   │   │       ├── prisma.pg.test.ts
│   │   │   │   │       ├── prisma.sqlite.test.ts
│   │   │   │   │       └── push-prisma-schema.ts
│   │   │   │   ├── test-adapter.ts
│   │   │   │   ├── test.ts
│   │   │   │   ├── tests
│   │   │   │   │   ├── auth-flow.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── normal.ts
│   │   │   │   │   ├── number-id.ts
│   │   │   │   │   ├── performance.ts
│   │   │   │   │   └── transactions.ts
│   │   │   │   └── utils.ts
│   │   │   ├── api
│   │   │   │   ├── check-endpoint-conflicts.test.ts
│   │   │   │   ├── index.test.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── middlewares
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── origin-check.test.ts
│   │   │   │   │   └── origin-check.ts
│   │   │   │   ├── rate-limiter
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── rate-limiter.test.ts
│   │   │   │   ├── routes
│   │   │   │   │   ├── account.test.ts
│   │   │   │   │   ├── account.ts
│   │   │   │   │   ├── callback.ts
│   │   │   │   │   ├── email-verification.test.ts
│   │   │   │   │   ├── email-verification.ts
│   │   │   │   │   ├── error.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── ok.ts
│   │   │   │   │   ├── reset-password.test.ts
│   │   │   │   │   ├── reset-password.ts
│   │   │   │   │   ├── session-api.test.ts
│   │   │   │   │   ├── session.ts
│   │   │   │   │   ├── sign-in.test.ts
│   │   │   │   │   ├── sign-in.ts
│   │   │   │   │   ├── sign-out.test.ts
│   │   │   │   │   ├── sign-out.ts
│   │   │   │   │   ├── sign-up.test.ts
│   │   │   │   │   ├── sign-up.ts
│   │   │   │   │   ├── update-user.test.ts
│   │   │   │   │   └── update-user.ts
│   │   │   │   ├── to-auth-endpoints.test.ts
│   │   │   │   └── to-auth-endpoints.ts
│   │   │   ├── auth.test.ts
│   │   │   ├── auth.ts
│   │   │   ├── call.test.ts
│   │   │   ├── client
│   │   │   │   ├── client-ssr.test.ts
│   │   │   │   ├── client.test.ts
│   │   │   │   ├── config.ts
│   │   │   │   ├── fetch-plugins.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── lynx
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── lynx-store.ts
│   │   │   │   ├── parser.ts
│   │   │   │   ├── path-to-object.ts
│   │   │   │   ├── plugins
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── infer-plugin.ts
│   │   │   │   ├── proxy.ts
│   │   │   │   ├── query.ts
│   │   │   │   ├── react
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── react-store.ts
│   │   │   │   ├── session-atom.ts
│   │   │   │   ├── solid
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── solid-store.ts
│   │   │   │   ├── svelte
│   │   │   │   │   └── index.ts
│   │   │   │   ├── test-plugin.ts
│   │   │   │   ├── types.ts
│   │   │   │   ├── url.test.ts
│   │   │   │   ├── vanilla.ts
│   │   │   │   └── vue
│   │   │   │       ├── index.ts
│   │   │   │       └── vue-store.ts
│   │   │   ├── cookies
│   │   │   │   ├── check-cookies.ts
│   │   │   │   ├── cookie-utils.ts
│   │   │   │   ├── cookies.test.ts
│   │   │   │   └── index.ts
│   │   │   ├── crypto
│   │   │   │   ├── buffer.ts
│   │   │   │   ├── hash.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── jwt.ts
│   │   │   │   ├── password.test.ts
│   │   │   │   ├── password.ts
│   │   │   │   └── random.ts
│   │   │   ├── db
│   │   │   │   ├── db.test.ts
│   │   │   │   ├── field.ts
│   │   │   │   ├── get-migration.ts
│   │   │   │   ├── get-schema.ts
│   │   │   │   ├── get-tables.test.ts
│   │   │   │   ├── get-tables.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── internal-adapter.test.ts
│   │   │   │   ├── internal-adapter.ts
│   │   │   │   ├── schema.ts
│   │   │   │   ├── secondary-storage.test.ts
│   │   │   │   ├── to-zod.ts
│   │   │   │   ├── utils.ts
│   │   │   │   └── with-hooks.ts
│   │   │   ├── index.ts
│   │   │   ├── init.test.ts
│   │   │   ├── init.ts
│   │   │   ├── integrations
│   │   │   │   ├── next-js.ts
│   │   │   │   ├── node.ts
│   │   │   │   ├── react-start.ts
│   │   │   │   ├── solid-start.ts
│   │   │   │   └── svelte-kit.ts
│   │   │   ├── oauth2
│   │   │   │   ├── index.ts
│   │   │   │   ├── link-account.test.ts
│   │   │   │   ├── link-account.ts
│   │   │   │   ├── state.ts
│   │   │   │   └── utils.ts
│   │   │   ├── plugins
│   │   │   │   ├── access
│   │   │   │   │   ├── access.test.ts
│   │   │   │   │   ├── access.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── additional-fields
│   │   │   │   │   ├── additional-fields.test.ts
│   │   │   │   │   └── client.ts
│   │   │   │   ├── admin
│   │   │   │   │   ├── access
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── statement.ts
│   │   │   │   │   ├── admin.test.ts
│   │   │   │   │   ├── admin.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── has-permission.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── anonymous
│   │   │   │   │   ├── anon.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── api-key
│   │   │   │   │   ├── api-key.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── rate-limit.ts
│   │   │   │   │   ├── routes
│   │   │   │   │   │   ├── create-api-key.ts
│   │   │   │   │   │   ├── delete-all-expired-api-keys.ts
│   │   │   │   │   │   ├── delete-api-key.ts
│   │   │   │   │   │   ├── get-api-key.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── list-api-keys.ts
│   │   │   │   │   │   ├── update-api-key.ts
│   │   │   │   │   │   └── verify-api-key.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── bearer
│   │   │   │   │   ├── bearer.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── captcha
│   │   │   │   │   ├── captcha.test.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── utils.ts
│   │   │   │   │   └── verify-handlers
│   │   │   │   │       ├── captchafox.ts
│   │   │   │   │       ├── cloudflare-turnstile.ts
│   │   │   │   │       ├── google-recaptcha.ts
│   │   │   │   │       ├── h-captcha.ts
│   │   │   │   │       └── index.ts
│   │   │   │   ├── custom-session
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── custom-session.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── device-authorization
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── device-authorization.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── schema.ts
│   │   │   │   ├── email-otp
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── email-otp.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── generic-oauth
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── generic-oauth.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── haveibeenpwned
│   │   │   │   │   ├── haveibeenpwned.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── jwt
│   │   │   │   │   ├── adapter.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── jwt.test.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── sign.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── last-login-method
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── custom-prefix.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── last-login-method.test.ts
│   │   │   │   ├── magic-link
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── magic-link.test.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── mcp
│   │   │   │   │   ├── authorize.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── mcp.test.ts
│   │   │   │   ├── multi-session
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── multi-session.test.ts
│   │   │   │   ├── oauth-proxy
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── oauth-proxy.test.ts
│   │   │   │   ├── oidc-provider
│   │   │   │   │   ├── authorize.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── oidc.test.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── ui.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── one-tap
│   │   │   │   │   ├── client.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── one-time-token
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── one-time-token.test.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── open-api
│   │   │   │   │   ├── generator.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── logo.ts
│   │   │   │   │   └── open-api.test.ts
│   │   │   │   ├── organization
│   │   │   │   │   ├── access
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── statement.ts
│   │   │   │   │   ├── adapter.ts
│   │   │   │   │   ├── call.ts
│   │   │   │   │   ├── client.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── has-permission.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── organization-hook.test.ts
│   │   │   │   │   ├── organization.test.ts
│   │   │   │   │   ├── organization.ts
│   │   │   │   │   ├── permission.ts
│   │   │   │   │   ├── routes
│   │   │   │   │   │   ├── crud-access-control.test.ts
│   │   │   │   │   │   ├── crud-access-control.ts
│   │   │   │   │   │   ├── crud-invites.ts
│   │   │   │   │   │   ├── crud-members.test.ts
│   │   │   │   │   │   ├── crud-members.ts
│   │   │   │   │   │   ├── crud-org.test.ts
│   │   │   │   │   │   ├── crud-org.ts
│   │   │   │   │   │   └── crud-team.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── team.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── passkey
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── passkey.test.ts
│   │   │   │   ├── phone-number
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── phone-number-error.ts
│   │   │   │   │   └── phone-number.test.ts
│   │   │   │   ├── siwe
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── siwe.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── two-factor
│   │   │   │   │   ├── backup-codes
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── constant.ts
│   │   │   │   │   ├── error-code.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── otp
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── totp
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── two-factor.test.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── utils.ts
│   │   │   │   │   └── verify-two-factor.ts
│   │   │   │   └── username
│   │   │   │       ├── client.ts
│   │   │   │       ├── error-codes.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── schema.ts
│   │   │   │       └── username.test.ts
│   │   │   ├── social-providers
│   │   │   │   └── index.ts
│   │   │   ├── social.test.ts
│   │   │   ├── test-utils
│   │   │   │   ├── headers.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── state.ts
│   │   │   │   └── test-instance.ts
│   │   │   ├── types
│   │   │   │   ├── adapter.ts
│   │   │   │   ├── api.ts
│   │   │   │   ├── helper.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── models.ts
│   │   │   │   ├── plugins.ts
│   │   │   │   └── types.test.ts
│   │   │   └── utils
│   │   │       ├── await-object.ts
│   │   │       ├── boolean.ts
│   │   │       ├── clone.ts
│   │   │       ├── constants.ts
│   │   │       ├── date.ts
│   │   │       ├── ensure-utc.ts
│   │   │       ├── get-request-ip.ts
│   │   │       ├── hashing.ts
│   │   │       ├── hide-metadata.ts
│   │   │       ├── id.ts
│   │   │       ├── import-util.ts
│   │   │       ├── index.ts
│   │   │       ├── is-atom.ts
│   │   │       ├── is-promise.ts
│   │   │       ├── json.ts
│   │   │       ├── merger.ts
│   │   │       ├── middleware-response.ts
│   │   │       ├── misc.ts
│   │   │       ├── password.ts
│   │   │       ├── plugin-helper.ts
│   │   │       ├── shim.ts
│   │   │       ├── time.ts
│   │   │       ├── url.ts
│   │   │       └── wildcard.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   ├── vitest.config.ts
│   │   └── vitest.setup.ts
│   ├── cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── commands
│   │   │   │   ├── generate.ts
│   │   │   │   ├── info.ts
│   │   │   │   ├── init.ts
│   │   │   │   ├── login.ts
│   │   │   │   ├── mcp.ts
│   │   │   │   ├── migrate.ts
│   │   │   │   └── secret.ts
│   │   │   ├── generators
│   │   │   │   ├── auth-config.ts
│   │   │   │   ├── drizzle.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kysely.ts
│   │   │   │   ├── prisma.ts
│   │   │   │   └── types.ts
│   │   │   ├── index.ts
│   │   │   └── utils
│   │   │       ├── add-svelte-kit-env-modules.ts
│   │   │       ├── check-package-managers.ts
│   │   │       ├── format-ms.ts
│   │   │       ├── get-config.ts
│   │   │       ├── get-package-info.ts
│   │   │       ├── get-tsconfig-info.ts
│   │   │       └── install-dependencies.ts
│   │   ├── test
│   │   │   ├── __snapshots__
│   │   │   │   ├── auth-schema-mysql-enum.txt
│   │   │   │   ├── auth-schema-mysql-number-id.txt
│   │   │   │   ├── auth-schema-mysql-passkey-number-id.txt
│   │   │   │   ├── auth-schema-mysql-passkey.txt
│   │   │   │   ├── auth-schema-mysql.txt
│   │   │   │   ├── auth-schema-number-id.txt
│   │   │   │   ├── auth-schema-pg-enum.txt
│   │   │   │   ├── auth-schema-pg-passkey.txt
│   │   │   │   ├── auth-schema-sqlite-enum.txt
│   │   │   │   ├── auth-schema-sqlite-number-id.txt
│   │   │   │   ├── auth-schema-sqlite-passkey-number-id.txt
│   │   │   │   ├── auth-schema-sqlite-passkey.txt
│   │   │   │   ├── auth-schema-sqlite.txt
│   │   │   │   ├── auth-schema.txt
│   │   │   │   ├── migrations.sql
│   │   │   │   ├── schema-mongodb.prisma
│   │   │   │   ├── schema-mysql-custom.prisma
│   │   │   │   ├── schema-mysql.prisma
│   │   │   │   ├── schema-numberid.prisma
│   │   │   │   └── schema.prisma
│   │   │   ├── generate-all-db.test.ts
│   │   │   ├── generate.test.ts
│   │   │   ├── get-config.test.ts
│   │   │   ├── info.test.ts
│   │   │   └── migrate.test.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.test.json
│   │   └── tsdown.config.ts
│   ├── core
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── api
│   │   │   │   └── index.ts
│   │   │   ├── async_hooks
│   │   │   │   └── index.ts
│   │   │   ├── context
│   │   │   │   ├── endpoint-context.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── transaction.ts
│   │   │   ├── db
│   │   │   │   ├── adapter
│   │   │   │   │   └── index.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── plugin.ts
│   │   │   │   ├── schema
│   │   │   │   │   ├── account.ts
│   │   │   │   │   ├── rate-limit.ts
│   │   │   │   │   ├── session.ts
│   │   │   │   │   ├── shared.ts
│   │   │   │   │   ├── user.ts
│   │   │   │   │   └── verification.ts
│   │   │   │   └── type.ts
│   │   │   ├── env
│   │   │   │   ├── color-depth.ts
│   │   │   │   ├── env-impl.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── logger.test.ts
│   │   │   │   └── logger.ts
│   │   │   ├── error
│   │   │   │   ├── codes.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── oauth2
│   │   │   │   ├── client-credentials-token.ts
│   │   │   │   ├── create-authorization-url.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── oauth-provider.ts
│   │   │   │   ├── refresh-access-token.ts
│   │   │   │   ├── utils.ts
│   │   │   │   └── validate-authorization-code.ts
│   │   │   ├── social-providers
│   │   │   │   ├── apple.ts
│   │   │   │   ├── atlassian.ts
│   │   │   │   ├── cognito.ts
│   │   │   │   ├── discord.ts
│   │   │   │   ├── dropbox.ts
│   │   │   │   ├── facebook.ts
│   │   │   │   ├── figma.ts
│   │   │   │   ├── github.ts
│   │   │   │   ├── gitlab.ts
│   │   │   │   ├── google.ts
│   │   │   │   ├── huggingface.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kakao.ts
│   │   │   │   ├── kick.ts
│   │   │   │   ├── line.ts
│   │   │   │   ├── linear.ts
│   │   │   │   ├── linkedin.ts
│   │   │   │   ├── microsoft-entra-id.ts
│   │   │   │   ├── naver.ts
│   │   │   │   ├── notion.ts
│   │   │   │   ├── paypal.ts
│   │   │   │   ├── reddit.ts
│   │   │   │   ├── roblox.ts
│   │   │   │   ├── salesforce.ts
│   │   │   │   ├── slack.ts
│   │   │   │   ├── spotify.ts
│   │   │   │   ├── tiktok.ts
│   │   │   │   ├── twitch.ts
│   │   │   │   ├── twitter.ts
│   │   │   │   ├── vk.ts
│   │   │   │   └── zoom.ts
│   │   │   ├── types
│   │   │   │   ├── context.ts
│   │   │   │   ├── cookie.ts
│   │   │   │   ├── helper.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── init-options.ts
│   │   │   │   ├── plugin-client.ts
│   │   │   │   └── plugin.ts
│   │   │   └── utils
│   │   │       ├── error-codes.ts
│   │   │       └── index.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── expo
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── expo.test.ts
│   │   │   └── index.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── sso
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── index.ts
│   │   │   ├── oidc.test.ts
│   │   │   └── saml.test.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── stripe
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── hooks.ts
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   ├── stripe.test.ts
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   └── vitest.config.ts
│   └── telemetry
│       ├── package.json
│       ├── src
│       │   ├── detectors
│       │   │   ├── detect-auth-config.ts
│       │   │   ├── detect-database.ts
│       │   │   ├── detect-framework.ts
│       │   │   ├── detect-project-info.ts
│       │   │   ├── detect-runtime.ts
│       │   │   └── detect-system-info.ts
│       │   ├── index.ts
│       │   ├── project-id.ts
│       │   ├── telemetry.test.ts
│       │   ├── types.ts
│       │   └── utils
│       │       ├── hash.ts
│       │       ├── id.ts
│       │       ├── import-util.ts
│       │       └── package-json.ts
│       ├── tsconfig.json
│       └── tsdown.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── SECURITY.md
├── tsconfig.json
└── turbo.json
```

# Files

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/bearer/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { serializeSignedCookie } from "better-call";
  2 | import type { BetterAuthPlugin } from "@better-auth/core";
  3 | import { parseSetCookieHeader } from "../../cookies";
  4 | import { createAuthMiddleware } from "@better-auth/core/api";
  5 | import { createHMAC } from "@better-auth/utils/hmac";
  6 | 
  7 | interface BearerOptions {
  8 | 	/**
  9 | 	 * If true, only signed tokens
 10 | 	 * will be converted to session
 11 | 	 * cookies
 12 | 	 *
 13 | 	 * @default false
 14 | 	 */
 15 | 	requireSignature?: boolean;
 16 | }
 17 | 
 18 | /**
 19 |  * Converts bearer token to session cookie
 20 |  */
 21 | export const bearer = (options?: BearerOptions) => {
 22 | 	return {
 23 | 		id: "bearer",
 24 | 		hooks: {
 25 | 			before: [
 26 | 				{
 27 | 					matcher(context) {
 28 | 						return Boolean(
 29 | 							context.request?.headers.get("authorization") ||
 30 | 								context.headers?.get("authorization"),
 31 | 						);
 32 | 					},
 33 | 					handler: createAuthMiddleware(async (c) => {
 34 | 						const token =
 35 | 							c.request?.headers.get("authorization")?.replace("Bearer ", "") ||
 36 | 							c.headers?.get("Authorization")?.replace("Bearer ", "");
 37 | 						if (!token) {
 38 | 							return;
 39 | 						}
 40 | 
 41 | 						let signedToken = "";
 42 | 						if (token.includes(".")) {
 43 | 							signedToken = token.replace("=", "");
 44 | 						} else {
 45 | 							if (options?.requireSignature) {
 46 | 								return;
 47 | 							}
 48 | 							signedToken = (
 49 | 								await serializeSignedCookie("", token, c.context.secret)
 50 | 							).replace("=", "");
 51 | 						}
 52 | 						try {
 53 | 							const decodedToken = decodeURIComponent(signedToken);
 54 | 							const isValid = await createHMAC(
 55 | 								"SHA-256",
 56 | 								"base64urlnopad",
 57 | 							).verify(
 58 | 								c.context.secret,
 59 | 								decodedToken.split(".")[0]!,
 60 | 								decodedToken.split(".")[1]!,
 61 | 							);
 62 | 							if (!isValid) {
 63 | 								return;
 64 | 							}
 65 | 						} catch (e) {
 66 | 							return;
 67 | 						}
 68 | 						const existingHeaders = (c.request?.headers ||
 69 | 							c.headers) as Headers;
 70 | 						const headers = new Headers({
 71 | 							...Object.fromEntries(existingHeaders?.entries()),
 72 | 						});
 73 | 						headers.append(
 74 | 							"cookie",
 75 | 							`${c.context.authCookies.sessionToken.name}=${signedToken}`,
 76 | 						);
 77 | 						return {
 78 | 							context: {
 79 | 								headers,
 80 | 							},
 81 | 						};
 82 | 					}),
 83 | 				},
 84 | 			],
 85 | 			after: [
 86 | 				{
 87 | 					matcher(context) {
 88 | 						return true;
 89 | 					},
 90 | 					handler: createAuthMiddleware(async (ctx) => {
 91 | 						const setCookie = ctx.context.responseHeaders?.get("set-cookie");
 92 | 						if (!setCookie) {
 93 | 							return;
 94 | 						}
 95 | 						const parsedCookies = parseSetCookieHeader(setCookie);
 96 | 						const cookieName = ctx.context.authCookies.sessionToken.name;
 97 | 						const sessionCookie = parsedCookies.get(cookieName);
 98 | 						if (
 99 | 							!sessionCookie ||
100 | 							!sessionCookie.value ||
101 | 							sessionCookie["max-age"] === 0
102 | 						) {
103 | 							return;
104 | 						}
105 | 						const token = sessionCookie.value;
106 | 						const exposedHeaders =
107 | 							ctx.context.responseHeaders?.get(
108 | 								"access-control-expose-headers",
109 | 							) || "";
110 | 						const headersSet = new Set(
111 | 							exposedHeaders
112 | 								.split(",")
113 | 								.map((header) => header.trim())
114 | 								.filter(Boolean),
115 | 						);
116 | 						headersSet.add("set-auth-token");
117 | 						ctx.setHeader("set-auth-token", token);
118 | 						ctx.setHeader(
119 | 							"Access-Control-Expose-Headers",
120 | 							Array.from(headersSet).join(", "),
121 | 						);
122 | 					}),
123 | 				},
124 | 			],
125 | 		},
126 | 	} satisfies BetterAuthPlugin;
127 | };
128 | 
```

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

```markdown
  1 | ---
  2 | title: Dub
  3 | description: Better Auth Plugin for Lead Tracking using Dub links and OAuth Linking
  4 | ---
  5 | 
  6 | [Dub](https://dub.co/) is an open source modern link management platform for entrepreneurs, creators, and growth teams.
  7 | 
  8 | This plugins allows you to track leads when a user signs up using a Dub link. It also adds OAuth linking support to allow you to build integrations extending Dub's linking management infrastructure.
  9 | 
 10 | ## Installation
 11 | 
 12 | <Steps>
 13 |     <Step>
 14 |         ### Install the plugin
 15 |         First, install the plugin:
 16 | 
 17 |         ```package-install
 18 |         @dub/better-auth
 19 |         ```
 20 |     </Step>
 21 |     <Step>
 22 |         ### Install the Dub SDK
 23 | 
 24 |         Next, install the Dub SDK on your server:
 25 | 
 26 |         ```package-install
 27 |         dub
 28 |         ```
 29 |     </Step>
 30 |     <Step>
 31 |         ### Configure the plugin
 32 | 
 33 |         Add the plugin to your auth config:
 34 | 
 35 |         ```ts title="auth.ts"
 36 |         import { betterAuth } from "better-auth"
 37 |         import { dubAnalytics } from "@dub/better-auth"
 38 |         import { dub } from "dub"
 39 | 
 40 |         export const auth = betterAuth({
 41 |             plugins: [
 42 |                 dubAnalytics({
 43 |                     dubClient: new Dub()
 44 |                 })
 45 |             ]
 46 |         })
 47 |         ```
 48 |     </Step>
 49 | 
 50 | </Steps>
 51 | 
 52 | ## Usage
 53 | 
 54 | ### Lead Tracking
 55 | 
 56 | By default, the plugin will track sign up events as leads. You can disable this by setting `disableLeadTracking` to `true`.
 57 | 
 58 | ```ts
 59 | import { dubAnalytics } from "@dub/better-auth";
 60 | import { betterAuth } from "better-auth";
 61 | import { Dub } from "dub";
 62 | 
 63 | const dub = new Dub();
 64 | 
 65 | const betterAuth = betterAuth({
 66 |   plugins: [
 67 |     dubAnalytics({
 68 |       dubClient: dub,
 69 |       disableLeadTracking: true, // Disable lead tracking
 70 |     }),
 71 |   ],
 72 | });
 73 | ```
 74 | 
 75 | ### OAuth Linking
 76 | 
 77 | The plugin supports OAuth for account linking.
 78 | 
 79 | First, you need to setup OAuth app in Dub. Dub supports OAuth 2.0 authentication, which is recommended if you build integrations extending Dub’s functionality [Learn more about OAuth](https://dub.co/docs/integrations/quickstart#integrating-via-oauth-2-0-recommended).
 80 | 
 81 | Once you get the client ID and client secret, you can configure the plugin.
 82 | 
 83 | ```ts
 84 | dubAnalytics({
 85 |   dubClient: dub,
 86 |   oauth: {
 87 |     clientId: "your-client-id",
 88 |     clientSecret: "your-client-secret",
 89 |   },
 90 | });
 91 | ```
 92 | 
 93 | And in the client, you need to use the `dubAnalyticsClient` plugin.
 94 | 
 95 | ```ts
 96 | import { createAuthClient } from "better-auth/client";
 97 | import { dubAnalyticsClient } from "@dub/better-auth/client";
 98 | 
 99 | const authClient = createAuthClient({
100 |   plugins: [dubAnalyticsClient()],
101 | });
102 | ```
103 | 
104 | To link account with Dub, you need to use the `dub.link`.
105 | 
106 | <APIMethod path="/dub/link" method="POST" requireSession>
107 | ```ts
108 | type dubLink = {
109 |   /**
110 |    * URL to redirect to after linking
111 |    * @clientOnly
112 |   */
113 |   callbackURL: string = "/dashboard"
114 | }
115 | ```
116 | </APIMethod>
117 | 
118 | ## Options
119 | 
120 | You can pass the following options to the plugin:
121 | 
122 | ### `dubClient`
123 | 
124 | The Dub client instance.
125 | 
126 | ### `disableLeadTracking`
127 | 
128 | Disable lead tracking for sign up events.
129 | 
130 | ### `leadEventName`
131 | 
132 | Event name for sign up leads.
133 | 
134 | ### `customLeadTrack`
135 | 
136 | Custom lead track function.
137 | 
138 | ### `oauth`
139 | 
140 | Dub OAuth configuration.
141 | 
142 | ### `oauth.clientId`
143 | 
144 | Client ID for Dub OAuth.
145 | 
146 | ### `oauth.clientSecret`
147 | 
148 | Client secret for Dub OAuth.
149 | 
150 | ### `oauth.pkce`
151 | 
152 | Enable PKCE for Dub OAuth.
153 | 
```

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

```typescript
  1 | import type { BetterFetch, BetterFetchOption } from "@better-fetch/fetch";
  2 | import type { Atom, PreinitializedWritableAtom } from "nanostores";
  3 | import type { ProxyRequest } from "./path-to-object";
  4 | import type { BetterAuthClientPlugin } from "@better-auth/core";
  5 | import { isAtom } from "../utils/is-atom";
  6 | 
  7 | function getMethod(
  8 | 	path: string,
  9 | 	knownPathMethods: Record<string, "POST" | "GET">,
 10 | 	args:
 11 | 		| { fetchOptions?: BetterFetchOption; query?: Record<string, any> }
 12 | 		| undefined,
 13 | ) {
 14 | 	const method = knownPathMethods[path];
 15 | 	const { fetchOptions, query, ...body } = args || {};
 16 | 	if (method) {
 17 | 		return method;
 18 | 	}
 19 | 	if (fetchOptions?.method) {
 20 | 		return fetchOptions.method;
 21 | 	}
 22 | 	if (body && Object.keys(body).length > 0) {
 23 | 		return "POST";
 24 | 	}
 25 | 	return "GET";
 26 | }
 27 | 
 28 | export type AuthProxySignal = {
 29 | 	atom: PreinitializedWritableAtom<boolean>;
 30 | 	matcher: (path: string) => boolean;
 31 | };
 32 | 
 33 | export function createDynamicPathProxy<T extends Record<string, any>>(
 34 | 	routes: T,
 35 | 	client: BetterFetch,
 36 | 	knownPathMethods: Record<string, "POST" | "GET">,
 37 | 	atoms: Record<string, Atom>,
 38 | 	atomListeners: BetterAuthClientPlugin["atomListeners"],
 39 | ): T {
 40 | 	function createProxy(path: string[] = []): any {
 41 | 		return new Proxy(function () {}, {
 42 | 			get(_, prop) {
 43 | 				if (typeof prop !== "string") {
 44 | 					return undefined;
 45 | 				}
 46 | 				if (prop === "then" || prop === "catch" || prop === "finally") {
 47 | 					return undefined;
 48 | 				}
 49 | 				const fullPath = [...path, prop];
 50 | 				let current: any = routes;
 51 | 				for (const segment of fullPath) {
 52 | 					if (current && typeof current === "object" && segment in current) {
 53 | 						current = current[segment];
 54 | 					} else {
 55 | 						current = undefined;
 56 | 						break;
 57 | 					}
 58 | 				}
 59 | 				if (typeof current === "function") {
 60 | 					return current;
 61 | 				}
 62 | 				if (isAtom(current)) {
 63 | 					return current;
 64 | 				}
 65 | 				return createProxy(fullPath);
 66 | 			},
 67 | 			apply: async (_, __, args) => {
 68 | 				const routePath =
 69 | 					"/" +
 70 | 					path
 71 | 						.map((segment) =>
 72 | 							segment.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`),
 73 | 						)
 74 | 						.join("/");
 75 | 				const arg = (args[0] || {}) as ProxyRequest;
 76 | 				const fetchOptions = (args[1] || {}) as BetterFetchOption;
 77 | 				const { query, fetchOptions: argFetchOptions, ...body } = arg;
 78 | 				const options = {
 79 | 					...fetchOptions,
 80 | 					...argFetchOptions,
 81 | 				} as BetterFetchOption;
 82 | 				const method = getMethod(routePath, knownPathMethods, arg);
 83 | 				return await client(routePath, {
 84 | 					...options,
 85 | 					body:
 86 | 						method === "GET"
 87 | 							? undefined
 88 | 							: {
 89 | 									...body,
 90 | 									...(options?.body || {}),
 91 | 								},
 92 | 					query: query || options?.query,
 93 | 					method,
 94 | 					async onSuccess(context) {
 95 | 						await options?.onSuccess?.(context);
 96 | 						if (!atomListeners) return;
 97 | 						/**
 98 | 						 * We trigger listeners
 99 | 						 */
100 | 						const matches = atomListeners.filter((s) => s.matcher(routePath));
101 | 						if (!matches.length) return;
102 | 						for (const match of matches) {
103 | 							const signal = atoms[match.signal as any];
104 | 							if (!signal) return;
105 | 							/**
106 | 							 * To avoid race conditions we set the signal in a setTimeout
107 | 							 */
108 | 							const val = signal.get();
109 | 							setTimeout(() => {
110 | 								//@ts-expect-error
111 | 								signal.set(!val);
112 | 							}, 10);
113 | 						}
114 | 					},
115 | 				});
116 | 			},
117 | 		});
118 | 	}
119 | 	return createProxy() as T;
120 | }
121 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/package.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "name": "@better-auth/demo",
  3 |   "version": "0.1.0",
  4 |   "private": true,
  5 |   "scripts": {
  6 |     "dev": "next dev",
  7 |     "dev:secure": "next dev --experimental-https",
  8 |     "typecheck": "tsc --noEmit",
  9 |     "build": "next build",
 10 |     "start": "next start",
 11 |     "lint": "next lint"
 12 |   },
 13 |   "dependencies": {
 14 |     "@better-auth/sso": "workspace:*",
 15 |     "@better-auth/stripe": "workspace:*",
 16 |     "@better-fetch/fetch": "catalog:",
 17 |     "@hookform/resolvers": "^5.2.1",
 18 |     "@libsql/client": "^0.8.1",
 19 |     "@libsql/kysely-libsql": "^0.4.1",
 20 |     "@number-flow/react": "^0.5.10",
 21 |     "@prisma/adapter-libsql": "^5.22.0",
 22 |     "@prisma/client": "^5.22.0",
 23 |     "@radix-ui/react-accordion": "^1.2.12",
 24 |     "@radix-ui/react-alert-dialog": "^1.1.15",
 25 |     "@radix-ui/react-aspect-ratio": "^1.1.7",
 26 |     "@radix-ui/react-avatar": "^1.1.10",
 27 |     "@radix-ui/react-checkbox": "^1.3.3",
 28 |     "@radix-ui/react-collapsible": "^1.1.12",
 29 |     "@radix-ui/react-context-menu": "^2.2.16",
 30 |     "@radix-ui/react-dialog": "^1.1.15",
 31 |     "@radix-ui/react-dropdown-menu": "^2.1.16",
 32 |     "@radix-ui/react-hover-card": "^1.1.15",
 33 |     "@radix-ui/react-icons": "^1.3.2",
 34 |     "@radix-ui/react-label": "^2.1.7",
 35 |     "@radix-ui/react-menubar": "^1.1.16",
 36 |     "@radix-ui/react-navigation-menu": "^1.2.14",
 37 |     "@radix-ui/react-popover": "^1.1.15",
 38 |     "@radix-ui/react-progress": "^1.1.7",
 39 |     "@radix-ui/react-radio-group": "^1.3.8",
 40 |     "@radix-ui/react-scroll-area": "^1.2.10",
 41 |     "@radix-ui/react-select": "^2.2.6",
 42 |     "@radix-ui/react-separator": "^1.1.7",
 43 |     "@radix-ui/react-slider": "^1.3.6",
 44 |     "@radix-ui/react-slot": "^1.2.3",
 45 |     "@radix-ui/react-switch": "^1.2.6",
 46 |     "@radix-ui/react-tabs": "^1.1.13",
 47 |     "@radix-ui/react-toast": "^1.2.15",
 48 |     "@radix-ui/react-toggle": "^1.1.10",
 49 |     "@radix-ui/react-toggle-group": "^1.1.11",
 50 |     "@radix-ui/react-tooltip": "^1.2.8",
 51 |     "@react-email/components": "^0.5.1",
 52 |     "@tanstack/react-query": "^5.85.9",
 53 |     "@types/better-sqlite3": "^7.6.13",
 54 |     "better-auth": "workspace:*",
 55 |     "better-call": "catalog:",
 56 |     "better-sqlite3": "^12.2.0",
 57 |     "canvas-confetti": "^1.9.3",
 58 |     "class-variance-authority": "^0.7.1",
 59 |     "clsx": "^2.1.1",
 60 |     "cmdk": "1.1.1",
 61 |     "consola": "^3.4.2",
 62 |     "date-fns": "^4.1.0",
 63 |     "embla-carousel-react": "^8.6.0",
 64 |     "framer-motion": "^12.23.12",
 65 |     "geist": "^1.4.2",
 66 |     "input-otp": "^1.4.2",
 67 |     "kysely": "^0.28.5",
 68 |     "lucide-react": "^0.542.0",
 69 |     "mini-svg-data-uri": "^1.4.4",
 70 |     "mysql2": "^3.14.4",
 71 |     "next": "16.0.0-beta.0",
 72 |     "next-themes": "^0.4.6",
 73 |     "prisma": "^5.22.0",
 74 |     "react": "19.2.0",
 75 |     "react-day-picker": "9.9.0",
 76 |     "react-dom": "19.2.0",
 77 |     "react-hook-form": "^7.62.0",
 78 |     "react-qr-code": "^2.0.18",
 79 |     "react-resizable-panels": "^3.0.5",
 80 |     "recharts": "^3.1.2",
 81 |     "resend": "^6.0.2",
 82 |     "server-only": "^0.0.1",
 83 |     "shiki": "^3.12.2",
 84 |     "sonner": "^2.0.7",
 85 |     "stripe": "^19.1.0",
 86 |     "tailwind-merge": "^3.3.1",
 87 |     "ua-parser-js": "^2.0.4",
 88 |     "vaul": "^1.1.2",
 89 |     "zod": "^4.1.5"
 90 |   },
 91 |   "devDependencies": {
 92 |     "@tailwindcss/postcss": "^4.1.13",
 93 |     "@types/canvas-confetti": "^1.9.0",
 94 |     "@types/react": "^19.2.2",
 95 |     "@types/react-dom": "^19.2.2",
 96 |     "@types/ua-parser-js": "^0.7.39",
 97 |     "postcss": "^8.5.6",
 98 |     "tailwindcss": "^4.1.13",
 99 |     "tw-animate-css": "^1.3.8"
100 |   }
101 | }
102 | 
```

--------------------------------------------------------------------------------
/packages/core/src/env/logger.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { getColorDepth } from "./color-depth";
  2 | 
  3 | export const TTY_COLORS = {
  4 | 	reset: "\x1b[0m",
  5 | 	bright: "\x1b[1m",
  6 | 	dim: "\x1b[2m",
  7 | 	undim: "\x1b[22m",
  8 | 	underscore: "\x1b[4m",
  9 | 	blink: "\x1b[5m",
 10 | 	reverse: "\x1b[7m",
 11 | 	hidden: "\x1b[8m",
 12 | 	fg: {
 13 | 		black: "\x1b[30m",
 14 | 		red: "\x1b[31m",
 15 | 		green: "\x1b[32m",
 16 | 		yellow: "\x1b[33m",
 17 | 		blue: "\x1b[34m",
 18 | 		magenta: "\x1b[35m",
 19 | 		cyan: "\x1b[36m",
 20 | 		white: "\x1b[37m",
 21 | 	},
 22 | 	bg: {
 23 | 		black: "\x1b[40m",
 24 | 		red: "\x1b[41m",
 25 | 		green: "\x1b[42m",
 26 | 		yellow: "\x1b[43m",
 27 | 		blue: "\x1b[44m",
 28 | 		magenta: "\x1b[45m",
 29 | 		cyan: "\x1b[46m",
 30 | 		white: "\x1b[47m",
 31 | 	},
 32 | } as const;
 33 | 
 34 | export type LogLevel = "info" | "success" | "warn" | "error" | "debug";
 35 | 
 36 | export const levels = ["info", "success", "warn", "error", "debug"] as const;
 37 | 
 38 | export function shouldPublishLog(
 39 | 	currentLogLevel: LogLevel,
 40 | 	logLevel: LogLevel,
 41 | ): boolean {
 42 | 	return levels.indexOf(logLevel) <= levels.indexOf(currentLogLevel);
 43 | }
 44 | 
 45 | export interface Logger {
 46 | 	disabled?: boolean;
 47 | 	disableColors?: boolean;
 48 | 	level?: Exclude<LogLevel, "success">;
 49 | 	log?: (
 50 | 		level: Exclude<LogLevel, "success">,
 51 | 		message: string,
 52 | 		...args: any[]
 53 | 	) => void;
 54 | }
 55 | 
 56 | export type LogHandlerParams = Parameters<NonNullable<Logger["log"]>> extends [
 57 | 	LogLevel,
 58 | 	...infer Rest,
 59 | ]
 60 | 	? Rest
 61 | 	: never;
 62 | 
 63 | const levelColors: Record<LogLevel, string> = {
 64 | 	info: TTY_COLORS.fg.blue,
 65 | 	success: TTY_COLORS.fg.green,
 66 | 	warn: TTY_COLORS.fg.yellow,
 67 | 	error: TTY_COLORS.fg.red,
 68 | 	debug: TTY_COLORS.fg.magenta,
 69 | };
 70 | 
 71 | const formatMessage = (
 72 | 	level: LogLevel,
 73 | 	message: string,
 74 | 	colorsEnabled: boolean,
 75 | ): string => {
 76 | 	const timestamp = new Date().toISOString();
 77 | 
 78 | 	if (colorsEnabled) {
 79 | 		return `${TTY_COLORS.dim}${timestamp}${TTY_COLORS.reset} ${
 80 | 			levelColors[level]
 81 | 		}${level.toUpperCase()}${TTY_COLORS.reset} ${TTY_COLORS.bright}[Better Auth]:${
 82 | 			TTY_COLORS.reset
 83 | 		} ${message}`;
 84 | 	}
 85 | 
 86 | 	return `${timestamp} ${level.toUpperCase()} [Better Auth]: ${message}`;
 87 | };
 88 | 
 89 | export type InternalLogger = {
 90 | 	[K in LogLevel]: (...params: LogHandlerParams) => void;
 91 | } & {
 92 | 	get level(): LogLevel;
 93 | };
 94 | 
 95 | export const createLogger = (options?: Logger): InternalLogger => {
 96 | 	const enabled = options?.disabled !== true;
 97 | 	const logLevel = options?.level ?? "error";
 98 | 
 99 | 	const isDisableColorsSpecified = options?.disableColors !== undefined;
100 | 	const colorsEnabled = isDisableColorsSpecified
101 | 		? !options.disableColors
102 | 		: getColorDepth() !== 1;
103 | 
104 | 	const LogFunc = (
105 | 		level: LogLevel,
106 | 		message: string,
107 | 		args: any[] = [],
108 | 	): void => {
109 | 		if (!enabled || !shouldPublishLog(logLevel, level)) {
110 | 			return;
111 | 		}
112 | 
113 | 		const formattedMessage = formatMessage(level, message, colorsEnabled);
114 | 
115 | 		if (!options || typeof options.log !== "function") {
116 | 			if (level === "error") {
117 | 				console.error(formattedMessage, ...args);
118 | 			} else if (level === "warn") {
119 | 				console.warn(formattedMessage, ...args);
120 | 			} else {
121 | 				console.log(formattedMessage, ...args);
122 | 			}
123 | 			return;
124 | 		}
125 | 
126 | 		options.log(level === "success" ? "info" : level, message, ...args);
127 | 	};
128 | 
129 | 	const logger = Object.fromEntries(
130 | 		levels.map((level) => [
131 | 			level,
132 | 			(...[message, ...args]: LogHandlerParams) =>
133 | 				LogFunc(level, message, args),
134 | 		]),
135 | 	) as Record<LogLevel, (...params: LogHandlerParams) => void>;
136 | 
137 | 	return {
138 | 		...logger,
139 | 		get level() {
140 | 			return logLevel;
141 | 		},
142 | 	};
143 | };
144 | 
145 | export const logger = createLogger();
146 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/plugins/oauth-proxy.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: OAuth Proxy
 3 | description: OAuth Proxy plugin for Better Auth
 4 | ---
 5 | 
 6 | A proxy plugin, that allows you to proxy OAuth requests. Useful for development and preview deployments where the redirect URL can't be known in advance to add to the OAuth provider. 
 7 | 
 8 | ## Installation
 9 | 
10 | <Steps>
11 |     <Step>
12 |     ### Add the plugin to your **auth** config
13 |     ```ts title="auth.ts"
14 |     import { betterAuth } from "better-auth"
15 |     import { oAuthProxy } from "better-auth/plugins"
16 | 
17 |     export const auth = betterAuth({
18 |         plugins: [ // [!code highlight]
19 |             oAuthProxy({ // [!code highlight]
20 |                 productionURL: "https://my-main-app.com", // Optional - if the URL isn't inferred correctly // [!code highlight]
21 |                 currentURL: "http://localhost:3000", // Optional - if the URL isn't inferred correctly // [!code highlight] 
22 |             }), // [!code highlight]
23 |         ] // [!code highlight]
24 |     })
25 |     ```
26 |     </Step>
27 |     <Step>
28 |     ### Add redirect URL to your OAuth provider
29 | 
30 |    For the proxy server to work properly, you’ll need to pass the redirect URL of your main production app registered with the OAuth provider in your social provider config. This needs to be done for each social provider you want to proxy requests for.
31 | 
32 |     ```ts
33 |     export const auth = betterAuth({
34 |        plugins: [
35 |            oAuthProxy(),
36 |        ], 
37 |        socialProviders: {
38 |             github: {
39 |                 clientId: "your-client-id",
40 |                 clientSecret: "your-client-secret",
41 |                 redirectURI: "https://my-main-app.com/api/auth/callback/github" // [!code highlight]
42 |             }
43 |        }
44 |     })
45 |     ```
46 |     </Step>
47 | </Steps>    
48 | 
49 | 
50 | ## How it works
51 | 
52 | The plugin adds an endpoint to your server that proxies OAuth requests. When you initiate a social sign-in, it sets the redirect URL to this proxy endpoint. After the OAuth provider redirects back to your server, the plugin then forwards the user to the original callback URL.
53 | 
54 | ```ts
55 | await authClient.signIn.social({
56 |     provider: "github",
57 |     callbackURL: "/dashboard" // the plugin will override this to something like "http://localhost:3000/api/auth/oauth-proxy?callbackURL=/dashboard"
58 | })
59 | ```
60 | 
61 | When the OAuth provider returns the user to your server, the plugin automatically redirects them to the intended callback URL.
62 | 
63 | To share cookies between the proxy server and your main server it uses URL query parameters to pass the cookies encrypted in the URL. This is secure as the cookies are encrypted and can only be decrypted by the server.
64 | 
65 | <Callout type="warn">
66 | This plugin requires skipping the state cookie check. This has security implications and should only be used in dev or staging environments. If `baseURL` and `productionURL` are the same, the plugin will not proxy the request.
67 | </Callout>
68 | 
69 | ## Options
70 | 
71 | **currentURL**: The application's current URL is automatically determined by the plugin. It first checks for the request URL if invoked by a client, then it checks the base URL from popular hosting providers, and finally falls back to the `baseURL` in your auth config. If the URL isn’t inferred correctly, you can specify it manually here.
72 | 
73 | **productionURL**: If this value matches the `baseURL` in your auth config, requests will not be proxied. Defaults to the `BETTER_AUTH_URL` environment variable.
74 | 
```

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

```markdown
 1 | ---
 2 | title: Express Integration
 3 | description: Integrate Better Auth with Express.
 4 | ---
 5 | 
 6 | This guide will show you how to integrate Better Auth with [express.js](https://expressjs.com/).
 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 | <Callout>
11 | Note that CommonJS (cjs) isn't supported. Use ECMAScript Modules (ESM) by setting `"type": "module"` in your `package.json` or configuring your `tsconfig.json` to use ES modules.
12 | </Callout>
13 | 
14 | ### Mount the handler
15 | 
16 | To enable Better Auth to handle requests, we need to mount the handler to an API route. Create a catch-all route to manage all requests to `/api/auth/*` in case of ExpressJS v4 or `/api/auth/*splat` in case of ExpressJS v5 (or any other path specified in your Better Auth options).
17 | 
18 | <Callout type="warn">
19 | Don’t use `express.json()` before the Better Auth handler. Use it only for other routes, or the client API will get stuck on "pending".
20 | </Callout>
21 | 
22 | ```ts title="server.ts"
23 | import express from "express";
24 | import { toNodeHandler } from "better-auth/node";
25 | import { auth } from "./auth";
26 | 
27 | const app = express();
28 | const port = 3005;
29 | 
30 | app.all("/api/auth/*", toNodeHandler(auth)); // For ExpressJS v4
31 | // app.all("/api/auth/*splat", toNodeHandler(auth)); For ExpressJS v5 
32 | 
33 | // Mount express json middleware after Better Auth handler
34 | // or only apply it to routes that don't interact with Better Auth
35 | app.use(express.json());
36 | 
37 | app.listen(port, () => {
38 | 	console.log(`Example app listening on port ${port}`);
39 | });
40 | ```
41 | 
42 | After completing the setup, start your server. Better Auth will be ready to use. You can send a `GET` request to the `/ok` endpoint (`/api/auth/ok`) to verify that the server is running.
43 | 
44 | 
45 | ### Cors Configuration
46 | 
47 | To add CORS (Cross-Origin Resource Sharing) support to your Express server when integrating Better Auth, you can use the `cors` middleware. Below is an updated example showing how to configure CORS for your server:
48 | 
49 | ```ts
50 | import express from "express";
51 | import cors from "cors"; // Import the CORS middleware
52 | import { toNodeHandler, fromNodeHeaders } from "better-auth/node";
53 | import { auth } from "./auth";
54 | 
55 | const app = express();
56 | const port = 3005;
57 | 
58 | // Configure CORS middleware
59 | app.use(
60 |   cors({
61 |     origin: "http://your-frontend-domain.com", // Replace with your frontend's origin
62 |     methods: ["GET", "POST", "PUT", "DELETE"], // Specify allowed HTTP methods
63 |     credentials: true, // Allow credentials (cookies, authorization headers, etc.)
64 |   })
65 | );
66 | ```
67 | 
68 | ### Getting the User Session
69 | 
70 | To retrieve the user's session, you can use the `getSession` method provided by the `auth` object. This method requires the request headers to be passed in a specific format. To simplify this process, Better Auth provides a `fromNodeHeaders` helper function that converts Node.js request headers to the format expected by Better Auth (a `Headers` object).
71 | 
72 | Here's an example of how to use `getSession` in an Express route:
73 | 
74 | ```ts title="server.ts"
75 | import { fromNodeHeaders } from "better-auth/node";
76 | import { auth } from "./auth"; // Your Better Auth instance
77 | 
78 | app.get("/api/me", async (req, res) => {
79 |  	const session = await auth.api.getSession({
80 |       headers: fromNodeHeaders(req.headers),
81 |     });
82 | 	return res.json(session);
83 | });
84 | ```
85 | 
```

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

```markdown
  1 | ---
  2 | title: API
  3 | description: Better Auth API.
  4 | ---
  5 | 
  6 | When you create a new Better Auth instance, it provides you with an `api` object. This object exposes every endpoint that exists in your Better Auth instance. And you can use this to interact with Better Auth server side.
  7 | 
  8 | Any endpoint added to Better Auth, whether from plugins or the core, will be accessible through the `api` object.
  9 | 
 10 | ## Calling API Endpoints on the Server
 11 | 
 12 | To call an API endpoint on the server, import your `auth` instance and call the endpoint using the `api` object.
 13 | 
 14 | ```ts title="server.ts"
 15 | import { betterAuth } from "better-auth";
 16 | import { headers } from "next/headers";
 17 | 
 18 | export const auth = betterAuth({
 19 |     //...
 20 | })
 21 | 
 22 | // calling get session on the server
 23 | await auth.api.getSession({
 24 |     headers: await headers() // some endpoints might require headers
 25 | })
 26 | ```
 27 | 
 28 | ### Body, Headers, Query
 29 | 
 30 | Unlike the client, the server needs the values to be passed as an object with the key `body` for the body, `headers` for the headers, and `query` for query parameters.
 31 | 
 32 | ```ts title="server.ts"
 33 | await auth.api.getSession({
 34 |     headers: await headers()
 35 | })
 36 | 
 37 | await auth.api.signInEmail({
 38 |     body: {
 39 |         email: "john@doe.com",
 40 |         password: "password"
 41 |     },
 42 |     headers: await headers() // optional but would be useful to get the user IP, user agent, etc.
 43 | })
 44 | 
 45 | await auth.api.verifyEmail({
 46 |     query: {
 47 |         token: "my_token"
 48 |     }
 49 | })
 50 | ```
 51 | 
 52 | <Callout>
 53 | Better Auth API endpoints are built on top of [better-call](https://github.com/bekacru/better-call), a tiny web framework that lets you call REST API endpoints as if they were regular functions and allows us to easily infer client types from the server.
 54 | </Callout>
 55 | 
 56 | ### Getting `headers` and `Response` Object
 57 | 
 58 | When you invoke an API endpoint on the server, it will return a standard JavaScript object or array directly as it's just a regular function call.
 59 | 
 60 | But there are times when you might want to get the `headers` or the `Response` object instead. For example, if you need to get the cookies or the headers.
 61 | 
 62 | #### Getting `headers`
 63 | 
 64 | To get the `headers`, you can pass the `returnHeaders` option to the endpoint.
 65 | 
 66 | ```ts
 67 | const { headers, response } = await auth.api.signUpEmail({
 68 | 	returnHeaders: true,
 69 | 	body: {
 70 | 		email: "john@doe.com",
 71 | 		password: "password",
 72 | 		name: "John Doe",
 73 | 	},
 74 | });
 75 | ```
 76 | 
 77 | The `headers` will be a `Headers` object, which you can use to get the cookies or the headers.
 78 | 
 79 | ```ts
 80 | const cookies = headers.get("set-cookie");
 81 | const headers = headers.get("x-custom-header");
 82 | ```
 83 | 
 84 | #### Getting `Response` Object
 85 | 
 86 | To get the `Response` object, you can pass the `asResponse` option to the endpoint.
 87 | 
 88 | ```ts title="server.ts"
 89 | const response = await auth.api.signInEmail({
 90 |     body: {
 91 |         email: "",
 92 |         password: ""
 93 |     },
 94 |     asResponse: true
 95 | })
 96 | ```
 97 | 
 98 | ### Error Handling
 99 | 
100 | When you call an API endpoint on the server, it will throw an error if the request fails. You can catch the error and handle it as you see fit. The error instance is an instance of `APIError`.
101 | 
102 | ```ts title="server.ts"
103 | import { APIError } from "better-auth/api";
104 | 
105 | try {
106 |     await auth.api.signInEmail({
107 |         body: {
108 |             email: "",
109 |             password: ""
110 |         }
111 |     })
112 | } catch (error) {
113 |     if (error instanceof APIError) {
114 |         console.log(error.message, error.status)
115 |     }
116 | }
117 | ```
118 | 
```

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

```markdown
 1 | ---
 2 | title: Notion
 3 | description: Notion provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |     <Step> 
 8 |         ### Get your Notion credentials
 9 |         To use Notion as a social provider, you need to get your Notion OAuth credentials. You can get them by creating a new integration in the [Notion Developers Portal](https://www.notion.so/my-integrations).
10 | 
11 |         In the Notion integration settings > OAuth Domain & URIs, make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/notion` for local development. For production, make sure to set the redirect URL as your application domain, e.g. `https://example.com/api/auth/callback/notion`. If you change the base path of the auth routes, you should update the redirect URL accordingly.
12 | 
13 |         <Callout>
14 |         Make sure your Notion integration has the appropriate capabilities enabled. For user authentication, you'll need the "Read user information including email addresses" capability.
15 |         </Callout>
16 |     </Step>
17 | 
18 |   <Step>
19 |         ### Configure the provider
20 |         To configure the provider, you need to pass the `clientId` and `clientSecret` to `socialProviders.notion` in your auth configuration.
21 | 
22 |         ```ts title="auth.ts"   
23 |         import { betterAuth } from "better-auth"
24 |         
25 |         export const auth = betterAuth({
26 |             socialProviders: {
27 |                 notion: { // [!code highlight]
28 |                     clientId: process.env.NOTION_CLIENT_ID as string, // [!code highlight]
29 |                     clientSecret: process.env.NOTION_CLIENT_SECRET as string, // [!code highlight]
30 |                 }, // [!code highlight]
31 |             },
32 |         })
33 |         ```
34 |     </Step>
35 | </Steps>
36 | 
37 | ## Usage
38 | 
39 | ### Sign In with Notion
40 | 
41 | To sign in with Notion, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
42 | - `provider`: The provider to use. It should be set to `notion`.
43 | 
44 | ```ts title="auth-client.ts"  
45 | import { createAuthClient } from "better-auth/client"
46 | const authClient =  createAuthClient()
47 | 
48 | const signIn = async () => {
49 |     const data = await authClient.signIn.social({
50 |         provider: "notion"
51 |     })
52 | }
53 | ```
54 | 
55 | ### Notion Integration Types
56 | 
57 | Notion supports different integration types. When creating your integration, you can choose between:
58 | 
59 | - **Public integrations**: Can be installed by any Notion workspace
60 | - **Internal integrations**: Limited to your own workspace
61 | 
62 | For most authentication use cases, you'll want to create a public integration to allow users from different workspaces to sign in.
63 | 
64 | ### Requesting Additional Notion Scopes
65 | 
66 | If your application needs additional Notion capabilities after the user has already signed up, you can request them using the `linkSocial` method with the same Notion provider and additional scopes.
67 | 
68 | ```ts title="auth-client.ts"
69 | const requestNotionAccess = async () => {
70 |     await authClient.linkSocial({
71 |         provider: "notion",
72 |         // Notion automatically provides access based on integration capabilities
73 |     });
74 | };
75 | 
76 | // Example usage in a React component
77 | return <button onClick={requestNotionAccess}>Connect Notion Workspace</button>;
78 | ```
79 | 
80 | <Callout>
81 | After authentication, you can use the access token to interact with the Notion API to read and write pages, databases, and other content that the user has granted access to.
82 | </Callout>
```

--------------------------------------------------------------------------------
/packages/cli/test/__snapshots__/auth-schema-sqlite.txt:
--------------------------------------------------------------------------------

```
 1 | import { sql } from "drizzle-orm";
 2 | import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
 3 | 
 4 | export const custom_user = sqliteTable("custom_user", {
 5 |   id: text("id").primaryKey(),
 6 |   name: text("name").notNull(),
 7 |   email: text("email").notNull().unique(),
 8 |   emailVerified: integer("email_verified", { mode: "boolean" })
 9 |     .default(false)
10 |     .notNull(),
11 |   image: text("image"),
12 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
13 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
14 |     .notNull(),
15 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
16 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
17 |     .$onUpdate(() => /* @__PURE__ */ new Date())
18 |     .notNull(),
19 |   twoFactorEnabled: integer("two_factor_enabled", { mode: "boolean" }).default(
20 |     false,
21 |   ),
22 |   username: text("username").unique(),
23 |   displayUsername: text("display_username"),
24 | });
25 | 
26 | export const custom_session = sqliteTable("custom_session", {
27 |   id: text("id").primaryKey(),
28 |   expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
29 |   token: text("token").notNull().unique(),
30 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
31 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
32 |     .notNull(),
33 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
34 |     .$onUpdate(() => /* @__PURE__ */ new Date())
35 |     .notNull(),
36 |   ipAddress: text("ip_address"),
37 |   userAgent: text("user_agent"),
38 |   userId: text("user_id")
39 |     .notNull()
40 |     .references(() => custom_user.id, { onDelete: "cascade" }),
41 | });
42 | 
43 | export const custom_account = sqliteTable("custom_account", {
44 |   id: text("id").primaryKey(),
45 |   accountId: text("account_id").notNull(),
46 |   providerId: text("provider_id").notNull(),
47 |   userId: text("user_id")
48 |     .notNull()
49 |     .references(() => custom_user.id, { onDelete: "cascade" }),
50 |   accessToken: text("access_token"),
51 |   refreshToken: text("refresh_token"),
52 |   idToken: text("id_token"),
53 |   accessTokenExpiresAt: integer("access_token_expires_at", {
54 |     mode: "timestamp_ms",
55 |   }),
56 |   refreshTokenExpiresAt: integer("refresh_token_expires_at", {
57 |     mode: "timestamp_ms",
58 |   }),
59 |   scope: text("scope"),
60 |   password: text("password"),
61 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
62 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
63 |     .notNull(),
64 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
65 |     .$onUpdate(() => /* @__PURE__ */ new Date())
66 |     .notNull(),
67 | });
68 | 
69 | export const custom_verification = sqliteTable("custom_verification", {
70 |   id: text("id").primaryKey(),
71 |   identifier: text("identifier").notNull(),
72 |   value: text("value").notNull(),
73 |   expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
74 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
75 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
76 |     .notNull(),
77 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
78 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
79 |     .$onUpdate(() => /* @__PURE__ */ new Date())
80 |     .notNull(),
81 | });
82 | 
83 | export const twoFactor = sqliteTable("two_factor", {
84 |   id: text("id").primaryKey(),
85 |   secret: text("secret").notNull(),
86 |   backupCodes: text("backup_codes").notNull(),
87 |   userId: text("user_id")
88 |     .notNull()
89 |     .references(() => custom_user.id, { onDelete: "cascade" }),
90 | });
91 | 
```

--------------------------------------------------------------------------------
/packages/core/src/db/type.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { ZodType } from "zod";
  2 | import type { LiteralString } from "../types";
  3 | 
  4 | export type DBPreservedModels =
  5 | 	| "user"
  6 | 	| "account"
  7 | 	| "session"
  8 | 	| "verification"
  9 | 	| "rate-limit"
 10 | 	| "organization"
 11 | 	| "member"
 12 | 	| "invitation"
 13 | 	| "jwks"
 14 | 	| "passkey"
 15 | 	| "two-factor";
 16 | 
 17 | export type DBFieldType =
 18 | 	| "string"
 19 | 	| "number"
 20 | 	| "boolean"
 21 | 	| "date"
 22 | 	| "json"
 23 | 	| `${"string" | "number"}[]`
 24 | 	| Array<LiteralString>;
 25 | 
 26 | export type DBPrimitive =
 27 | 	| string
 28 | 	| number
 29 | 	| boolean
 30 | 	| Date
 31 | 	| null
 32 | 	| undefined
 33 | 	| string[]
 34 | 	| number[];
 35 | 
 36 | export type DBFieldAttributeConfig = {
 37 | 	/**
 38 | 	 * If the field should be required on a new record.
 39 | 	 * @default true
 40 | 	 */
 41 | 	required?: boolean;
 42 | 	/**
 43 | 	 * If the value should be returned on a response body.
 44 | 	 * @default true
 45 | 	 */
 46 | 	returned?: boolean;
 47 | 	/**
 48 | 	 * If a value should be provided when creating a new record.
 49 | 	 * @default true
 50 | 	 */
 51 | 	input?: boolean;
 52 | 	/**
 53 | 	 * Default value for the field
 54 | 	 *
 55 | 	 * Note: This will not create a default value on the database level. It will only
 56 | 	 * be used when creating a new record.
 57 | 	 */
 58 | 	defaultValue?: DBPrimitive | (() => DBPrimitive);
 59 | 	/**
 60 | 	 * Update value for the field
 61 | 	 *
 62 | 	 * Note: This will create an onUpdate trigger on the database level for supported adapters.
 63 | 	 * It will be called when updating a record.
 64 | 	 */
 65 | 	onUpdate?: () => DBPrimitive;
 66 | 	/**
 67 | 	 * transform the value before storing it.
 68 | 	 */
 69 | 	transform?: {
 70 | 		input?: (value: DBPrimitive) => DBPrimitive | Promise<DBPrimitive>;
 71 | 		output?: (value: DBPrimitive) => DBPrimitive | Promise<DBPrimitive>;
 72 | 	};
 73 | 	/**
 74 | 	 * Reference to another model.
 75 | 	 */
 76 | 	references?: {
 77 | 		/**
 78 | 		 * The model to reference.
 79 | 		 */
 80 | 		model: string;
 81 | 		/**
 82 | 		 * The field on the referenced model.
 83 | 		 */
 84 | 		field: string;
 85 | 		/**
 86 | 		 * The action to perform when the reference is deleted.
 87 | 		 * @default "cascade"
 88 | 		 */
 89 | 		onDelete?:
 90 | 			| "no action"
 91 | 			| "restrict"
 92 | 			| "cascade"
 93 | 			| "set null"
 94 | 			| "set default";
 95 | 	};
 96 | 	unique?: boolean;
 97 | 	/**
 98 | 	 * If the field should be a bigint on the database instead of integer.
 99 | 	 */
100 | 	bigint?: boolean;
101 | 	/**
102 | 	 * A zod schema to validate the value.
103 | 	 */
104 | 	validator?: {
105 | 		input?: ZodType;
106 | 		output?: ZodType;
107 | 	};
108 | 	/**
109 | 	 * The name of the field on the database.
110 | 	 */
111 | 	fieldName?: string;
112 | 	/**
113 | 	 * If the field should be sortable.
114 | 	 *
115 | 	 * applicable only for `text` type.
116 | 	 * It's useful to mark fields varchar instead of text.
117 | 	 */
118 | 	sortable?: boolean;
119 | };
120 | 
121 | export type DBFieldAttribute<T extends DBFieldType = DBFieldType> = {
122 | 	type: T;
123 | } & DBFieldAttributeConfig;
124 | 
125 | export type BetterAuthDBSchema = Record<
126 | 	string,
127 | 	{
128 | 		/**
129 | 		 * The name of the table in the database
130 | 		 */
131 | 		modelName: string;
132 | 		/**
133 | 		 * The fields of the table
134 | 		 */
135 | 		fields: Record<string, DBFieldAttribute>;
136 | 		/**
137 | 		 * Whether to disable migrations for this table
138 | 		 * @default false
139 | 		 */
140 | 		disableMigrations?: boolean;
141 | 		/**
142 | 		 * The order of the table
143 | 		 */
144 | 		order?: number;
145 | 	}
146 | >;
147 | 
148 | export interface SecondaryStorage {
149 | 	/**
150 | 	 *
151 | 	 * @param key - Key to get
152 | 	 * @returns - Value of the key
153 | 	 */
154 | 	get: (key: string) => Promise<unknown> | unknown;
155 | 	set: (
156 | 		/**
157 | 		 * Key to store
158 | 		 */
159 | 		key: string,
160 | 		/**
161 | 		 * Value to store
162 | 		 */
163 | 		value: string,
164 | 		/**
165 | 		 * Time to live in seconds
166 | 		 */
167 | 		ttl?: number,
168 | 	) => Promise<void | null | unknown> | void;
169 | 	/**
170 | 	 *
171 | 	 * @param key - Key to delete
172 | 	 */
173 | 	delete: (key: string) => Promise<void | null | string> | void;
174 | }
175 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/plugins/one-time-token.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: One-Time Token Plugin
  3 | description: Generate and verify single-use token
  4 | ---
  5 | 
  6 | The One-Time Token (OTT) plugin provides functionality to generate and verify secure, single-use session tokens. These are commonly used for across domains authentication.
  7 | 
  8 | ## Installation
  9 | 
 10 | <Steps>
 11 |   <Step>
 12 |     ### Add the plugin to your auth config
 13 | 
 14 |     To use the One-Time Token plugin, add it to your auth config.
 15 | 
 16 |     ```ts title="auth.ts"
 17 |     import { betterAuth } from "better-auth";
 18 |     import { oneTimeToken } from "better-auth/plugins/one-time-token";
 19 |     
 20 |     export const auth = betterAuth({
 21 |         plugins: [
 22 |           oneTimeToken()
 23 |         ]
 24 |         // ... other auth config
 25 |     });
 26 |     ```
 27 |   </Step>
 28 | 
 29 |   <Step>
 30 |     ### Add the client plugin
 31 | 
 32 |     Next, include the one-time-token client plugin in your authentication client instance.
 33 | 
 34 |     ```ts title="auth-client.ts"
 35 |     import { createAuthClient } from "better-auth/client"
 36 |     import { oneTimeTokenClient } from "better-auth/client/plugins"
 37 |     
 38 |     export const authClient = createAuthClient({
 39 |         plugins: [
 40 |             oneTimeTokenClient()
 41 |         ]
 42 |     })
 43 |     ```
 44 |   </Step>
 45 | </Steps>
 46 | 
 47 | ## Usage
 48 | 
 49 | ### 1. Generate a Token
 50 | 
 51 | Generate a token using `auth.api.generateOneTimeToken` or `authClient.oneTimeToken.generate`
 52 | 
 53 | <APIMethod
 54 |   path="/one-time-token/generate"
 55 |   method="GET"
 56 |   requireSession
 57 | >
 58 | ```ts
 59 | type generateOneTimeToken = {
 60 | }
 61 | ```
 62 | </APIMethod>
 63 | 
 64 | This will return a `token` that is attached to the current session which can be used to verify the one-time token. By default, the token will expire in 3 minutes.
 65 | 
 66 | ### 2. Verify the Token
 67 | 
 68 | When the user clicks the link or submits the token, use the `auth.api.verifyOneTimeToken` or `authClient.oneTimeToken.verify` method in another API route to validate it.
 69 | 
 70 | <APIMethod path="/one-time-token/verify" method="POST">
 71 | ```ts
 72 | type verifyOneTimeToken = {
 73 |     /**
 74 |      * The token to verify. 
 75 |      */
 76 |     token: string = "some-token"
 77 | }
 78 | ```
 79 | </APIMethod>
 80 | 
 81 | This will return the session that was attached to the token.
 82 | 
 83 | ## Options
 84 | 
 85 | These options can be configured when adding the `oneTimeToken` plugin:
 86 | 
 87 | *   **`disableClientRequest`** (boolean): Optional. If `true`, the token will only be generated on the server side. Default: `false`.
 88 | *   **`expiresIn`** (number): Optional. The duration for which the token is valid in minutes. Default: `3`.
 89 | 
 90 | ```ts
 91 | oneTimeToken({
 92 |     expiresIn: 10 // 10 minutes
 93 | })
 94 | ```
 95 | * **`generateToken`**: A custom token generator function that takes `session` object and a `ctx` as paramters.
 96 | 
 97 | * **`storeToken`**: Optional. This option allows you to configure how the token is stored in your database.
 98 | 
 99 |     * **`plain`**: The token is stored in plain text. (Default)
100 |     * **`hashed`**: The token is hashed using the default hasher.
101 |     * **`custom-hasher`**: A custom hasher function that takes a token and returns a hashed token.
102 | 
103 | <Callout type="info">
104 |     Note: It will not affect the token that's sent, it will only affect the token stored in your database.
105 | </Callout>
106 | 
107 | Examples:
108 | 
109 | ```ts title="No hashing (default)"
110 | oneTimeToken({
111 |     storeToken: "plain"
112 | })
113 | ```
114 | ```ts title="built-in hasher"
115 | oneTimeToken({
116 |     storeToken: "hashed"
117 | })
118 | ```
119 | ```ts title="custom hasher"
120 | oneTimeToken({
121 |     storeToken: {
122 |         type: "custom-hasher",
123 |         hash: async (token) => {
124 |             return myCustomHasher(token);
125 |         }
126 |     }
127 | })
128 | ```
129 | 
```

--------------------------------------------------------------------------------
/.github/workflows/e2e.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: E2E
  2 | on:
  3 |   push:
  4 |     branches: [ main, canary ]
  5 |   merge_group:
  6 |   pull_request:
  7 | jobs:
  8 |   smoke:
  9 |     name: Smoke test
 10 |     timeout-minutes: 30
 11 |     runs-on: ubuntu-latest
 12 |     steps:
 13 |       - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 14 |         with:
 15 |           fetch-depth: 0
 16 | 
 17 |       - name: Cache turbo build setup
 18 |         uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
 19 |         with:
 20 |           path: .turbo
 21 |           key: ${{ runner.os }}-turbo-${{ github.sha }}
 22 |           restore-keys: |
 23 |             ${{ runner.os }}-turbo-
 24 | 
 25 |       - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
 26 | 
 27 |       - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
 28 |         with:
 29 |           node-version: 22.x
 30 |           registry-url: 'https://registry.npmjs.org'
 31 |           cache: pnpm
 32 | 
 33 |       - name: Install
 34 |         run: pnpm install
 35 | 
 36 |       - name: Build
 37 |         env:
 38 |           TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
 39 |           TURBO_TEAM: ${{ vars.TURBO_TEAM || github.repository_owner }}
 40 |           TURBO_CACHE: remote:rw
 41 |         run: pnpm build
 42 | 
 43 |       - name: Start Docker Containers
 44 |         run: |
 45 |           docker compose up -d
 46 |           # Wait for services to be ready (optional)
 47 |           sleep 10
 48 | 
 49 |       - uses: oven-sh/setup-bun@v2
 50 | 
 51 |       - uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
 52 |         with:
 53 |           deno-version: v2.x
 54 | 
 55 |       - name: Smoke
 56 |         env:
 57 |           TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
 58 |           TURBO_TEAM: ${{ vars.TURBO_TEAM || github.repository_owner }}
 59 |           TURBO_CACHE: remote:rw
 60 |         run: pnpm e2e:smoke
 61 | 
 62 |       - name: Stop Docker Containers
 63 |         run: docker compose down
 64 |   integration:
 65 |     name: Integration test
 66 |     timeout-minutes: 60
 67 |     runs-on: ubuntu-latest
 68 |     steps:
 69 |       - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 70 |         with:
 71 |           fetch-depth: 0
 72 | 
 73 |       - name: Cache turbo build setup
 74 |         uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
 75 |         with:
 76 |           path: .turbo
 77 |           key: ${{ runner.os }}-turbo-${{ github.sha }}
 78 |           restore-keys: |
 79 |             ${{ runner.os }}-turbo-
 80 | 
 81 |       - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
 82 | 
 83 |       - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
 84 |         with:
 85 |           node-version: 22.x
 86 |           registry-url: 'https://registry.npmjs.org'
 87 |           cache: pnpm
 88 | 
 89 |       - name: Install
 90 |         run: pnpm install
 91 | 
 92 |       - name: Install Playwright Browsers
 93 |         run: pnpm exec playwright install --with-deps
 94 |         working-directory:  e2e/integration
 95 | 
 96 |       - name: Build
 97 |         env:
 98 |           TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
 99 |           TURBO_TEAM: ${{ vars.TURBO_TEAM || github.repository_owner }}
100 |           TURBO_CACHE: remote:rw
101 |         run: pnpm build
102 | 
103 |       - name: Start Docker Containers
104 |         run: |
105 |           docker compose up -d
106 |           # Wait for services to be ready (optional)
107 |           sleep 10
108 | 
109 |       - name: Integration
110 |         env:
111 |           TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
112 |           TURBO_TEAM: ${{ vars.TURBO_TEAM || github.repository_owner }}
113 |           TURBO_CACHE: remote:rw
114 |         run: pnpm e2e:integration
115 | 
116 |       - name: Stop Docker Containers
117 |         run: docker compose down
```

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

```markdown
 1 | ---
 2 | title: Cognito
 3 | description: Amazon Cognito provider setup and usage.
 4 | ---
 5 | 
 6 | <Steps>
 7 |   <Step>
 8 |     ### Get your Cognito Credentials
 9 |     To integrate with Cognito, you need to set up a **User Pool** and an **App client** in the [Amazon Cognito Console](https://console.aws.amazon.com/cognito/).
10 | 
11 |     Follow these steps:
12 |     1. Go to the **Cognito Console** and create a **User Pool**.
13 |     2. Under **App clients**, create a new **App client** (note the Client ID and Client Secret if enabled).
14 |     3. Go to **Domain** and set a Cognito Hosted UI domain (e.g., `your-app.auth.us-east-1.amazoncognito.com`).
15 |     4. In **App client settings**, enable:
16 |        - Allowed OAuth flows: `Authorization code grant`
17 |        - Allowed OAuth scopes: `openid`, `profile`, `email`
18 |     5. Add your callback URL (e.g., `http://localhost:3000/api/auth/callback/cognito`).
19 | 
20 |     <Callout type="info">
21 |       - **User Pool is required** for Cognito authentication.  
22 |       - Make sure the callback URL matches exactly what you configure in Cognito.   
23 |     </Callout>
24 |   </Step>
25 | 
26 |   <Step>
27 |     ### Configure the provider
28 |     Configure the `cognito` key in `socialProviders` key of your `auth` instance.
29 | 
30 |     ```ts title="auth.ts"
31 |     import { betterAuth } from "better-auth";
32 | 
33 |     export const auth = betterAuth({
34 |       socialProviders: {
35 |         cognito: {
36 |           clientId: process.env.COGNITO_CLIENT_ID as string, // [!code highlight]
37 |           clientSecret: process.env.COGNITO_CLIENT_SECRET as string, // [!code highlight]
38 |           domain: process.env.COGNITO_DOMAIN as string, // e.g. "your-app.auth.us-east-1.amazoncognito.com" [!code highlight]
39 |           region: process.env.COGNITO_REGION as string, // e.g. "us-east-1" [!code highlight]
40 |           userPoolId: process.env.COGNITO_USERPOOL_ID as string, // [!code highlight]
41 |         },
42 |       },
43 |     })
44 |     ```
45 |   </Step>
46 | 
47 |   <Step>
48 |     ### Sign In with Cognito
49 |     To sign in with Cognito, use the `signIn.social` function from the client.  
50 | 
51 |     ```ts title="auth-client.ts"
52 |     import { createAuthClient } from "better-auth/client"
53 | 
54 |     const authClient = createAuthClient()
55 | 
56 |     const signIn = async () => {
57 |       const data = await authClient.signIn.social({
58 |         provider: "cognito"
59 |       })
60 |     }
61 |     ```
62 |     
63 |       ### Additional Options:
64 |         - `scope`: Additional OAuth2 scopes to request (combined with default permissions).
65 |             - Default: `"openid" "profile" "email"`
66 |             - Common Cognito scopes:
67 |               - `openid`: Required for OpenID Connect authentication
68 |               - `profile`: Access to basic profile info
69 |               - `email`: Access to user’s email
70 |               - `phone`: Access to user’s phone number
71 |               - `aws.cognito.signin.user.admin`: Grants access to Cognito-specific APIs
72 |         - Note: You must configure the scopes in your Cognito App Client settings. [available scopes](https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html#token-endpoint-userinfo)
73 |         - `getUserInfo`: Custom function to retrieve user information from the Cognito UserInfo endpoint.  
74 |        <Callout type="info">
75 |         For more information about Amazon Cognito's scopes and API capabilities, refer to the [official documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-define-resource-servers.html?utm_source).
76 |         </Callout>
77 |   </Step>
78 | </Steps>
79 | 
```

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

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | import type { OAuthProvider, ProviderOptions } from "../oauth2";
  3 | import { validateAuthorizationCode, refreshAccessToken } from "../oauth2";
  4 | 
  5 | export interface SlackProfile extends Record<string, any> {
  6 | 	ok: boolean;
  7 | 	sub: string;
  8 | 	"https://slack.com/user_id": string;
  9 | 	"https://slack.com/team_id": string;
 10 | 	email: string;
 11 | 	email_verified: boolean;
 12 | 	date_email_verified: number;
 13 | 	name: string;
 14 | 	picture: string;
 15 | 	given_name: string;
 16 | 	family_name: string;
 17 | 	locale: string;
 18 | 	"https://slack.com/team_name": string;
 19 | 	"https://slack.com/team_domain": string;
 20 | 	"https://slack.com/user_image_24": string;
 21 | 	"https://slack.com/user_image_32": string;
 22 | 	"https://slack.com/user_image_48": string;
 23 | 	"https://slack.com/user_image_72": string;
 24 | 	"https://slack.com/user_image_192": string;
 25 | 	"https://slack.com/user_image_512": string;
 26 | 	"https://slack.com/team_image_34": string;
 27 | 	"https://slack.com/team_image_44": string;
 28 | 	"https://slack.com/team_image_68": string;
 29 | 	"https://slack.com/team_image_88": string;
 30 | 	"https://slack.com/team_image_102": string;
 31 | 	"https://slack.com/team_image_132": string;
 32 | 	"https://slack.com/team_image_230": string;
 33 | 	"https://slack.com/team_image_default": boolean;
 34 | }
 35 | 
 36 | export interface SlackOptions extends ProviderOptions<SlackProfile> {
 37 | 	clientId: string;
 38 | }
 39 | 
 40 | export const slack = (options: SlackOptions) => {
 41 | 	return {
 42 | 		id: "slack",
 43 | 		name: "Slack",
 44 | 		createAuthorizationURL({ state, scopes, redirectURI }) {
 45 | 			const _scopes = options.disableDefaultScope
 46 | 				? []
 47 | 				: ["openid", "profile", "email"];
 48 | 			scopes && _scopes.push(...scopes);
 49 | 			options.scope && _scopes.push(...options.scope);
 50 | 			const url = new URL("https://slack.com/openid/connect/authorize");
 51 | 			url.searchParams.set("scope", _scopes.join(" "));
 52 | 			url.searchParams.set("response_type", "code");
 53 | 			url.searchParams.set("client_id", options.clientId);
 54 | 			url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
 55 | 			url.searchParams.set("state", state);
 56 | 			return url;
 57 | 		},
 58 | 		validateAuthorizationCode: async ({ code, redirectURI }) => {
 59 | 			return validateAuthorizationCode({
 60 | 				code,
 61 | 				redirectURI,
 62 | 				options,
 63 | 				tokenEndpoint: "https://slack.com/api/openid.connect.token",
 64 | 			});
 65 | 		},
 66 | 		refreshAccessToken: options.refreshAccessToken
 67 | 			? options.refreshAccessToken
 68 | 			: async (refreshToken) => {
 69 | 					return refreshAccessToken({
 70 | 						refreshToken,
 71 | 						options: {
 72 | 							clientId: options.clientId,
 73 | 							clientKey: options.clientKey,
 74 | 							clientSecret: options.clientSecret,
 75 | 						},
 76 | 						tokenEndpoint: "https://slack.com/api/openid.connect.token",
 77 | 					});
 78 | 				},
 79 | 		async getUserInfo(token) {
 80 | 			if (options.getUserInfo) {
 81 | 				return options.getUserInfo(token);
 82 | 			}
 83 | 			const { data: profile, error } = await betterFetch<SlackProfile>(
 84 | 				"https://slack.com/api/openid.connect.userInfo",
 85 | 				{
 86 | 					headers: {
 87 | 						authorization: `Bearer ${token.accessToken}`,
 88 | 					},
 89 | 				},
 90 | 			);
 91 | 
 92 | 			if (error) {
 93 | 				return null;
 94 | 			}
 95 | 
 96 | 			const userMap = await options.mapProfileToUser?.(profile);
 97 | 			return {
 98 | 				user: {
 99 | 					id: profile["https://slack.com/user_id"],
100 | 					name: profile.name || "",
101 | 					email: profile.email,
102 | 					emailVerified: profile.email_verified,
103 | 					image: profile.picture || profile["https://slack.com/user_image_512"],
104 | 					...userMap,
105 | 				},
106 | 				data: profile,
107 | 			};
108 | 		},
109 | 		options,
110 | 	} satisfies OAuthProvider<SlackProfile>;
111 | };
112 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/client/vue/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { useStore } from "./vue-store";
  2 | import type { DeepReadonly, Ref } from "vue";
  3 | import { getClientConfig } from "../config";
  4 | import { capitalizeFirstLetter } from "../../utils/misc";
  5 | import type {
  6 | 	InferActions,
  7 | 	InferClientAPI,
  8 | 	InferErrorCodes,
  9 | 	IsSignal,
 10 | } from "../types";
 11 | import type {
 12 | 	BetterAuthClientPlugin,
 13 | 	BetterAuthClientOptions,
 14 | } from "@better-auth/core";
 15 | import { createDynamicPathProxy } from "../proxy";
 16 | import type { PrettifyDeep, UnionToIntersection } from "../../types/helper";
 17 | import type {
 18 | 	BetterFetchError,
 19 | 	BetterFetchResponse,
 20 | } from "@better-fetch/fetch";
 21 | import type { BASE_ERROR_CODES } from "@better-auth/core/error";
 22 | 
 23 | function getAtomKey(str: string) {
 24 | 	return `use${capitalizeFirstLetter(str)}`;
 25 | }
 26 | 
 27 | type InferResolvedHooks<O extends BetterAuthClientOptions> = O extends {
 28 | 	plugins: Array<infer Plugin>;
 29 | }
 30 | 	? UnionToIntersection<
 31 | 			Plugin extends BetterAuthClientPlugin
 32 | 				? Plugin["getAtoms"] extends (fetch: any) => infer Atoms
 33 | 					? Atoms extends Record<string, any>
 34 | 						? {
 35 | 								[key in keyof Atoms as IsSignal<key> extends true
 36 | 									? never
 37 | 									: key extends string
 38 | 										? `use${Capitalize<key>}`
 39 | 										: never]: () => DeepReadonly<
 40 | 									Ref<ReturnType<Atoms[key]["get"]>>
 41 | 								>;
 42 | 							}
 43 | 						: {}
 44 | 					: {}
 45 | 				: {}
 46 | 		>
 47 | 	: {};
 48 | 
 49 | export function createAuthClient<Option extends BetterAuthClientOptions>(
 50 | 	options?: Option,
 51 | ) {
 52 | 	const {
 53 | 		baseURL,
 54 | 		pluginPathMethods,
 55 | 		pluginsActions,
 56 | 		pluginsAtoms,
 57 | 		$fetch,
 58 | 		$store,
 59 | 		atomListeners,
 60 | 	} = getClientConfig(options, false);
 61 | 	let resolvedHooks: Record<string, any> = {};
 62 | 	for (const [key, value] of Object.entries(pluginsAtoms)) {
 63 | 		resolvedHooks[getAtomKey(key)] = () => useStore(value);
 64 | 	}
 65 | 
 66 | 	type ClientAPI = InferClientAPI<Option>;
 67 | 	type Session = ClientAPI extends {
 68 | 		getSession: () => Promise<infer Res>;
 69 | 	}
 70 | 		? Res extends BetterFetchResponse<infer S>
 71 | 			? S
 72 | 			: Res extends Record<string, any>
 73 | 				? Res
 74 | 				: never
 75 | 		: never;
 76 | 
 77 | 	function useSession(): DeepReadonly<
 78 | 		Ref<{
 79 | 			data: Session;
 80 | 			isPending: boolean;
 81 | 			isRefetching: boolean;
 82 | 			error: BetterFetchError | null;
 83 | 		}>
 84 | 	>;
 85 | 	function useSession<F extends (...args: any) => any>(
 86 | 		useFetch: F,
 87 | 	): Promise<{
 88 | 		data: Ref<Session>;
 89 | 		isPending: false; //this is just to be consistent with the default hook
 90 | 		error: Ref<{
 91 | 			message?: string;
 92 | 			status: number;
 93 | 			statusText: string;
 94 | 		}>;
 95 | 	}>;
 96 | 	function useSession<UseFetch extends <T>(...args: any) => any>(
 97 | 		useFetch?: UseFetch,
 98 | 	) {
 99 | 		if (useFetch) {
100 | 			const ref = useStore(pluginsAtoms.$sessionSignal!);
101 | 			return useFetch(`${baseURL}/get-session`, {
102 | 				ref,
103 | 			}).then((res: any) => {
104 | 				return {
105 | 					data: res.data,
106 | 					isPending: false,
107 | 					error: res.error,
108 | 				};
109 | 			});
110 | 		}
111 | 		return resolvedHooks.useSession();
112 | 	}
113 | 
114 | 	const routes = {
115 | 		...pluginsActions,
116 | 		...resolvedHooks,
117 | 		useSession,
118 | 		$fetch,
119 | 		$store,
120 | 	};
121 | 
122 | 	const proxy = createDynamicPathProxy(
123 | 		routes,
124 | 		$fetch,
125 | 		pluginPathMethods,
126 | 		pluginsAtoms,
127 | 		atomListeners,
128 | 	);
129 | 
130 | 	return proxy as UnionToIntersection<InferResolvedHooks<Option>> &
131 | 		InferClientAPI<Option> &
132 | 		InferActions<Option> & {
133 | 			useSession: typeof useSession;
134 | 			$Infer: {
135 | 				Session: NonNullable<Session>;
136 | 			};
137 | 			$fetch: typeof $fetch;
138 | 			$store: typeof $store;
139 | 			$ERROR_CODES: PrettifyDeep<
140 | 				InferErrorCodes<Option> & typeof BASE_ERROR_CODES
141 | 			>;
142 | 		};
143 | }
144 | 
145 | export type * from "@better-fetch/fetch";
146 | export type * from "nanostores";
147 | 
```

--------------------------------------------------------------------------------
/docs/app/blog/_components/_layout.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import { useId } from "react";
  2 | 
  3 | import { Intro, IntroFooter } from "./changelog-layout";
  4 | import { StarField } from "./stat-field";
  5 | 
  6 | function Timeline() {
  7 | 	let id = useId();
  8 | 
  9 | 	return (
 10 | 		<div className="pointer-events-none absolute inset-0 z-50 overflow-hidden lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem] lg:overflow-visible">
 11 | 			<svg
 12 | 				className="absolute left-[max(0px,calc(50%-18.125rem))] top-0 h-full w-1.5 lg:left-full lg:ml-1 xl:left-auto xl:right-1 xl:ml-0"
 13 | 				aria-hidden="true"
 14 | 			>
 15 | 				<defs>
 16 | 					<pattern id={id} width="6" height="8" patternUnits="userSpaceOnUse">
 17 | 						<path
 18 | 							d="M0 0H6M0 8H6"
 19 | 							className="stroke-sky-900/10 xl:stroke-white/10 dark:stroke-white/10"
 20 | 							fill="none"
 21 | 						/>
 22 | 					</pattern>
 23 | 				</defs>
 24 | 				<rect width="100%" height="100%" fill={`url(#${id})`} />
 25 | 			</svg>
 26 | 			someone is
 27 | 		</div>
 28 | 	);
 29 | }
 30 | 
 31 | function Glow() {
 32 | 	let id = useId();
 33 | 
 34 | 	return (
 35 | 		<div className="absolute inset-0  overflow-hidden  lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem]">
 36 | 			<svg
 37 | 				className="absolute -bottom-48 left-[-40%] h-[80rem] w-[180%] lg:-right-40 lg:bottom-auto lg:left-auto lg:top-[-40%] lg:h-[180%] lg:w-[80rem]"
 38 | 				aria-hidden="true"
 39 | 			>
 40 | 				<defs>
 41 | 					<radialGradient id={`${id}-desktop`} cx="100%">
 42 | 						<stop offset="0%" stopColor="rgba(214, 211, 209, 0.6)" />
 43 | 						<stop offset="53.95%" stopColor="rgba(214, 200, 209, 0.09)" />
 44 | 						<stop offset="100%" stopColor="rgba(10, 14, 23, 0)" />
 45 | 					</radialGradient>
 46 | 					<radialGradient id={`${id}-mobile`} cy="100%">
 47 | 						<stop offset="0%" stopColor="rgba(56, 189, 248, 0.3)" />
 48 | 						<stop offset="53.95%" stopColor="rgba(0, 71, 255, 0.09)" />
 49 | 						<stop offset="100%" stopColor="rgba(10, 14, 23, 0)" />
 50 | 					</radialGradient>
 51 | 				</defs>
 52 | 				<rect
 53 | 					width="100%"
 54 | 					height="100%"
 55 | 					fill={`url(#${id}-desktop)`}
 56 | 					className="hidden lg:block"
 57 | 				/>
 58 | 				<rect
 59 | 					width="100%"
 60 | 					height="100%"
 61 | 					fill={`url(#${id}-mobile)`}
 62 | 					className="lg:hidden"
 63 | 				/>
 64 | 			</svg>
 65 | 			<div className="absolute inset-x-0 bottom-0 right-0 h-px bg-white mix-blend-overlay lg:left-auto lg:top-0 lg:h-auto lg:w-px" />
 66 | 		</div>
 67 | 	);
 68 | }
 69 | 
 70 | function FixedSidebar({
 71 | 	main,
 72 | 	footer,
 73 | }: {
 74 | 	main: React.ReactNode;
 75 | 	footer: React.ReactNode;
 76 | }) {
 77 | 	return (
 78 | 		<div className="relative   flex-none overflow-hidden px-10 lg:pointer-events-none lg:fixed lg:inset-0 lg:z-40 lg:flex lg:px-0">
 79 | 			<Glow />
 80 | 			<div className="relative flex w-full lg:pointer-events-auto lg:mr-[calc(max(2rem,50%-35rem)+40rem)] lg:min-w-[32rem] lg:overflow-y-auto lg:overflow-x-hidden lg:pl-[max(4rem,calc(50%-38rem))]">
 81 | 				<div className="mx-auto max-w-lg lg:mx-auto  lg:flex  lg:max-w-4xl  lg:flex-col lg:before:flex-1 lg:before:pt-6">
 82 | 					<div className="pb-16  pt-20 sm:pb-20 sm:pt-32 lg:py-20">
 83 | 						<div className="relative pr-10">
 84 | 							<StarField className="-right-44 top-14" />
 85 | 							{main}
 86 | 						</div>
 87 | 					</div>
 88 | 					<div className="flex flex-1 items-end justify-center pb-4 lg:justify-start lg:pb-6">
 89 | 						{footer}
 90 | 					</div>
 91 | 				</div>
 92 | 			</div>
 93 | 		</div>
 94 | 	);
 95 | }
 96 | 
 97 | export function Layout({ children }: { children: React.ReactNode }) {
 98 | 	return (
 99 | 		<>
100 | 			<FixedSidebar main={<Intro />} footer={<IntroFooter />} />
101 | 			<div />
102 | 			<div className="relative flex-auto">
103 | 				<Timeline />
104 | 				<main className="grid grid-cols-12 col-span-5 ml-auto space-y-20 py-20 sm:space-y-32 sm:py-32">
105 | 					{children}
106 | 				</main>
107 | 			</div>
108 | 		</>
109 | 	);
110 | }
111 | 
```

--------------------------------------------------------------------------------
/docs/app/changelogs/_components/_layout.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import { useId } from "react";
  2 | 
  3 | import { Intro, IntroFooter } from "./changelog-layout";
  4 | import { StarField } from "./stat-field";
  5 | 
  6 | function Timeline() {
  7 | 	let id = useId();
  8 | 
  9 | 	return (
 10 | 		<div className="pointer-events-none absolute inset-0 z-50 overflow-hidden lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem] lg:overflow-visible">
 11 | 			<svg
 12 | 				className="absolute left-[max(0px,calc(50%-18.125rem))] top-0 h-full w-1.5 lg:left-full lg:ml-1 xl:left-auto xl:right-1 xl:ml-0"
 13 | 				aria-hidden="true"
 14 | 			>
 15 | 				<defs>
 16 | 					<pattern id={id} width="6" height="8" patternUnits="userSpaceOnUse">
 17 | 						<path
 18 | 							d="M0 0H6M0 8H6"
 19 | 							className="stroke-sky-900/10 xl:stroke-white/10 dark:stroke-white/10"
 20 | 							fill="none"
 21 | 						/>
 22 | 					</pattern>
 23 | 				</defs>
 24 | 				<rect width="100%" height="100%" fill={`url(#${id})`} />
 25 | 			</svg>
 26 | 			someone is
 27 | 		</div>
 28 | 	);
 29 | }
 30 | 
 31 | function Glow() {
 32 | 	let id = useId();
 33 | 
 34 | 	return (
 35 | 		<div className="absolute inset-0  overflow-hidden  lg:right-[calc(max(2rem,50%-38rem)+40rem)] lg:min-w-[32rem]">
 36 | 			<svg
 37 | 				className="absolute -bottom-48 left-[-40%] h-[80rem] w-[180%] lg:-right-40 lg:bottom-auto lg:left-auto lg:top-[-40%] lg:h-[180%] lg:w-[80rem]"
 38 | 				aria-hidden="true"
 39 | 			>
 40 | 				<defs>
 41 | 					<radialGradient id={`${id}-desktop`} cx="100%">
 42 | 						<stop offset="0%" stopColor="rgba(214, 211, 209, 0.6)" />
 43 | 						<stop offset="53.95%" stopColor="rgba(214, 200, 209, 0.09)" />
 44 | 						<stop offset="100%" stopColor="rgba(10, 14, 23, 0)" />
 45 | 					</radialGradient>
 46 | 					<radialGradient id={`${id}-mobile`} cy="100%">
 47 | 						<stop offset="0%" stopColor="rgba(56, 189, 248, 0.3)" />
 48 | 						<stop offset="53.95%" stopColor="rgba(0, 71, 255, 0.09)" />
 49 | 						<stop offset="100%" stopColor="rgba(10, 14, 23, 0)" />
 50 | 					</radialGradient>
 51 | 				</defs>
 52 | 				<rect
 53 | 					width="100%"
 54 | 					height="100%"
 55 | 					fill={`url(#${id}-desktop)`}
 56 | 					className="hidden lg:block"
 57 | 				/>
 58 | 				<rect
 59 | 					width="100%"
 60 | 					height="100%"
 61 | 					fill={`url(#${id}-mobile)`}
 62 | 					className="lg:hidden"
 63 | 				/>
 64 | 			</svg>
 65 | 			<div className="absolute inset-x-0 bottom-0 right-0 h-px bg-white mix-blend-overlay lg:left-auto lg:top-0 lg:h-auto lg:w-px" />
 66 | 		</div>
 67 | 	);
 68 | }
 69 | 
 70 | function FixedSidebar({
 71 | 	main,
 72 | 	footer,
 73 | }: {
 74 | 	main: React.ReactNode;
 75 | 	footer: React.ReactNode;
 76 | }) {
 77 | 	return (
 78 | 		<div className="relative   flex-none overflow-hidden px-10 lg:pointer-events-none lg:fixed lg:inset-0 lg:z-40 lg:flex lg:px-0">
 79 | 			<Glow />
 80 | 			<div className="relative flex w-full lg:pointer-events-auto lg:mr-[calc(max(2rem,50%-35rem)+40rem)] lg:min-w-[32rem] lg:overflow-y-auto lg:overflow-x-hidden lg:pl-[max(4rem,calc(50%-38rem))]">
 81 | 				<div className="mx-auto max-w-lg lg:mx-auto  lg:flex  lg:max-w-4xl  lg:flex-col lg:before:flex-1 lg:before:pt-6">
 82 | 					<div className="pb-16  pt-20 sm:pb-20 sm:pt-32 lg:py-20">
 83 | 						<div className="relative pr-10">
 84 | 							<StarField className="-right-44 top-14" />
 85 | 							{main}
 86 | 						</div>
 87 | 					</div>
 88 | 					<div className="flex flex-1 items-end justify-center pb-4 lg:justify-start lg:pb-6">
 89 | 						{footer}
 90 | 					</div>
 91 | 				</div>
 92 | 			</div>
 93 | 		</div>
 94 | 	);
 95 | }
 96 | 
 97 | export function Layout({ children }: { children: React.ReactNode }) {
 98 | 	return (
 99 | 		<>
100 | 			<FixedSidebar main={<Intro />} footer={<IntroFooter />} />
101 | 			<div />
102 | 			<div className="relative flex-auto">
103 | 				<Timeline />
104 | 				<main className="grid grid-cols-12 col-span-5 ml-auto space-y-20 py-20 sm:space-y-32 sm:py-32">
105 | 					{children}
106 | 				</main>
107 | 			</div>
108 | 		</>
109 | 	);
110 | }
111 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/tailwind.config.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { Config } from "tailwindcss";
  2 | 
  3 | const svgToDataUri = require("mini-svg-data-uri");
  4 | 
  5 | const colors = require("tailwindcss/colors");
  6 | const {
  7 | 	default: flattenColorPalette,
  8 | } = require("tailwindcss/lib/util/flattenColorPalette");
  9 | 
 10 | const config = {
 11 | 	darkMode: ["class", '[data-theme="dark"]'],
 12 | 	content: [
 13 | 		"./pages/**/*.{ts,tsx}",
 14 | 		"./components/**/*.{ts,tsx}",
 15 | 		"./app/**/*.{ts,tsx}",
 16 | 		"./src/**/*.{ts,tsx}",
 17 | 	],
 18 | 	prefix: "",
 19 | 	theme: {
 20 | 		container: {
 21 | 			center: true,
 22 | 			padding: "2rem",
 23 | 			screens: {
 24 | 				"2xl": "1400px",
 25 | 			},
 26 | 		},
 27 | 		extend: {
 28 | 			colors: {
 29 | 				border: "var(--border)",
 30 | 				input: "var(--input)",
 31 | 				ring: "var(--ring)",
 32 | 				background: "var(--background)",
 33 | 				foreground: "var(--foreground)",
 34 | 				primary: {
 35 | 					DEFAULT: "var(--primary)",
 36 | 					foreground: "var(--primary-foreground)",
 37 | 				},
 38 | 				secondary: {
 39 | 					DEFAULT: "var(--secondary)",
 40 | 					foreground: "var(--secondary-foreground)",
 41 | 				},
 42 | 				destructive: {
 43 | 					DEFAULT: "var(--destructive)",
 44 | 					foreground: "var(--destructive-foreground)",
 45 | 				},
 46 | 				muted: {
 47 | 					DEFAULT: "var(--muted)",
 48 | 					foreground: "var(--muted-foreground)",
 49 | 				},
 50 | 				accent: {
 51 | 					DEFAULT: "var(--accent)",
 52 | 					foreground: "var(--accent-foreground)",
 53 | 				},
 54 | 				popover: {
 55 | 					DEFAULT: "var(--popover)",
 56 | 					foreground: "var(--popover-foreground)",
 57 | 				},
 58 | 				card: {
 59 | 					DEFAULT: "var(--card)",
 60 | 					foreground: "var(--card-foreground)",
 61 | 				},
 62 | 			},
 63 | 			borderRadius: {
 64 | 				lg: "var(--radius)",
 65 | 				md: "calc(var(--radius) - 2px)",
 66 | 				sm: "calc(var(--radius) - 4px)",
 67 | 			},
 68 | 			keyframes: {
 69 | 				"accordion-down": {
 70 | 					from: { height: "0" },
 71 | 					to: { height: "var(--radix-accordion-content-height)" },
 72 | 				},
 73 | 				"accordion-up": {
 74 | 					from: { height: "var(--radix-accordion-content-height)" },
 75 | 					to: { height: "0" },
 76 | 				},
 77 | 			},
 78 | 			animation: {
 79 | 				"accordion-down": "accordion-down 0.2s ease-out",
 80 | 				"accordion-up": "accordion-up 0.2s ease-out",
 81 | 			},
 82 | 			boxShadow: {
 83 | 				input: `0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)`,
 84 | 			},
 85 | 		},
 86 | 	},
 87 | 	plugins: [
 88 | 		addVariablesForColors,
 89 | 		function ({ matchUtilities, theme }: any) {
 90 | 			matchUtilities(
 91 | 				{
 92 | 					"bg-grid": (value: any) => ({
 93 | 						backgroundImage: `url("${svgToDataUri(
 94 | 							`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`,
 95 | 						)}")`,
 96 | 					}),
 97 | 					"bg-grid-small": (value: any) => ({
 98 | 						backgroundImage: `url("${svgToDataUri(
 99 | 							`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="8" height="8" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`,
100 | 						)}")`,
101 | 					}),
102 | 					"bg-dot": (value: any) => ({
103 | 						backgroundImage: `url("${svgToDataUri(
104 | 							`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" fill="none"><circle fill="${value}" id="pattern-circle" cx="10" cy="10" r="1.6257413380501518"></circle></svg>`,
105 | 						)}")`,
106 | 					}),
107 | 				},
108 | 				{
109 | 					values: flattenColorPalette(theme("backgroundColor")),
110 | 					type: "color",
111 | 				},
112 | 			);
113 | 		},
114 | 	],
115 | } satisfies Config;
116 | 
117 | function addVariablesForColors({ addBase, theme }: any) {
118 | 	let allColors = flattenColorPalette(theme("colors"));
119 | 	let newVars = Object.fromEntries(
120 | 		Object.entries(allColors).map(([key, val]) => [`--${key}`, val]),
121 | 	);
122 | 
123 | 	addBase({
124 | 		":root": newVars,
125 | 	});
126 | }
127 | 
128 | export default config;
129 | 
```

--------------------------------------------------------------------------------
/docs/lib/inkeep-analytics.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { betterFetch } from "@better-fetch/fetch";
  2 | 
  3 | const INKEEP_ANALYTICS_BASE_URL = "https://api.analytics.inkeep.com";
  4 | 
  5 | export interface InkeepMessage {
  6 | 	id?: string;
  7 | 	role: "user" | "assistant" | "system";
  8 | 	content: string;
  9 | }
 10 | 
 11 | export interface InkeepConversation {
 12 | 	id?: string;
 13 | 	type: "openai";
 14 | 	messages: InkeepMessage[];
 15 | 	properties?: Record<string, any>;
 16 | 	userProperties?: Record<string, any>;
 17 | }
 18 | 
 19 | export interface InkeepFeedback {
 20 | 	type: "positive" | "negative";
 21 | 	messageId: string;
 22 | 	reasons?: Array<{
 23 | 		label: string;
 24 | 		details?: string;
 25 | 	}>;
 26 | }
 27 | 
 28 | export interface InkeepEvent {
 29 | 	type: string;
 30 | 	entityType: "message" | "conversation";
 31 | 	messageId?: string;
 32 | 	conversationId?: string;
 33 | }
 34 | 
 35 | function getApiKey(): string {
 36 | 	const apiKey =
 37 | 		process.env.INKEEP_ANALYTICS_API_KEY || process.env.INKEEP_API_KEY;
 38 | 	if (!apiKey) {
 39 | 		throw new Error(
 40 | 			"INKEEP_ANALYTICS_API_KEY or INKEEP_API_KEY environment variable is required",
 41 | 		);
 42 | 	}
 43 | 	return apiKey;
 44 | }
 45 | 
 46 | async function makeAnalyticsRequest(endpoint: string, data: any) {
 47 | 	const apiKey = getApiKey();
 48 | 
 49 | 	const { data: result, error } = await betterFetch(
 50 | 		`${INKEEP_ANALYTICS_BASE_URL}${endpoint}`,
 51 | 		{
 52 | 			method: "POST",
 53 | 			headers: {
 54 | 				Authorization: `Bearer ${apiKey}`,
 55 | 				"Content-Type": "application/json",
 56 | 			},
 57 | 			body: JSON.stringify(data),
 58 | 		},
 59 | 	);
 60 | 
 61 | 	if (error) {
 62 | 		throw new Error(
 63 | 			`Inkeep Analytics API error: ${error.status} ${error.message}`,
 64 | 		);
 65 | 	}
 66 | 
 67 | 	return result;
 68 | }
 69 | 
 70 | export async function logConversationToAnalytics(
 71 | 	conversation: InkeepConversation,
 72 | ) {
 73 | 	return await makeAnalyticsRequest("/conversations", conversation);
 74 | }
 75 | 
 76 | export async function submitFeedbackToAnalytics(feedback: InkeepFeedback) {
 77 | 	return await makeAnalyticsRequest("/feedback", feedback);
 78 | }
 79 | 
 80 | export async function logEventToAnalytics(event: InkeepEvent) {
 81 | 	return await makeAnalyticsRequest("/events", event);
 82 | }
 83 | 
 84 | export async function logConversationToInkeep(messages: InkeepMessage[]) {
 85 | 	try {
 86 | 		const { data, error } = await betterFetch("/api/analytics/conversation", {
 87 | 			method: "POST",
 88 | 			headers: {
 89 | 				"Content-Type": "application/json",
 90 | 			},
 91 | 			body: JSON.stringify({ messages }),
 92 | 		});
 93 | 
 94 | 		if (error) {
 95 | 			throw new Error(
 96 | 				`Failed to log conversation: ${error.status} - ${error.message}`,
 97 | 			);
 98 | 		}
 99 | 
100 | 		return data;
101 | 	} catch (error) {
102 | 		return null;
103 | 	}
104 | }
105 | 
106 | export async function submitFeedbackToInkeep(
107 | 	messageId: string,
108 | 	type: "positive" | "negative",
109 | 	reasons?: Array<{ label: string; details?: string }>,
110 | ) {
111 | 	try {
112 | 		const { data, error } = await betterFetch("/api/analytics/feedback", {
113 | 			method: "POST",
114 | 			headers: {
115 | 				"Content-Type": "application/json",
116 | 			},
117 | 			body: JSON.stringify({ messageId, type, reasons }),
118 | 		});
119 | 
120 | 		if (error) {
121 | 			throw new Error(
122 | 				`Failed to submit feedback: ${error.status} - ${error.message}`,
123 | 			);
124 | 		}
125 | 
126 | 		return data;
127 | 	} catch (error) {
128 | 		console.error("Error in submitFeedbackToInkeep:", error);
129 | 		return null;
130 | 	}
131 | }
132 | 
133 | export async function logEventToInkeep(
134 | 	type: string,
135 | 	entityType: "message" | "conversation",
136 | 	messageId?: string,
137 | 	conversationId?: string,
138 | ) {
139 | 	try {
140 | 		const { data, error } = await betterFetch("/api/analytics/event", {
141 | 			method: "POST",
142 | 			headers: {
143 | 				"Content-Type": "application/json",
144 | 			},
145 | 			body: JSON.stringify({ type, entityType, messageId, conversationId }),
146 | 		});
147 | 
148 | 		if (error) {
149 | 			throw new Error(
150 | 				`Failed to log event: ${error.status} - ${error.message}`,
151 | 			);
152 | 		}
153 | 
154 | 		return data;
155 | 	} catch (error) {
156 | 		return null;
157 | 	}
158 | }
159 | 
```

--------------------------------------------------------------------------------
/packages/cli/test/__snapshots__/auth-schema-sqlite-passkey.txt:
--------------------------------------------------------------------------------

```
 1 | import { sql } from "drizzle-orm";
 2 | import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
 3 | 
 4 | export const custom_user = sqliteTable("custom_user", {
 5 |   id: text("id").primaryKey(),
 6 |   name: text("name").notNull(),
 7 |   email: text("email").notNull().unique(),
 8 |   emailVerified: integer("email_verified", { mode: "boolean" })
 9 |     .default(false)
10 |     .notNull(),
11 |   image: text("image"),
12 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
13 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
14 |     .notNull(),
15 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
16 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
17 |     .$onUpdate(() => /* @__PURE__ */ new Date())
18 |     .notNull(),
19 | });
20 | 
21 | export const custom_session = sqliteTable("custom_session", {
22 |   id: text("id").primaryKey(),
23 |   expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
24 |   token: text("token").notNull().unique(),
25 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
26 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
27 |     .notNull(),
28 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
29 |     .$onUpdate(() => /* @__PURE__ */ new Date())
30 |     .notNull(),
31 |   ipAddress: text("ip_address"),
32 |   userAgent: text("user_agent"),
33 |   userId: text("user_id")
34 |     .notNull()
35 |     .references(() => custom_user.id, { onDelete: "cascade" }),
36 | });
37 | 
38 | export const custom_account = sqliteTable("custom_account", {
39 |   id: text("id").primaryKey(),
40 |   accountId: text("account_id").notNull(),
41 |   providerId: text("provider_id").notNull(),
42 |   userId: text("user_id")
43 |     .notNull()
44 |     .references(() => custom_user.id, { onDelete: "cascade" }),
45 |   accessToken: text("access_token"),
46 |   refreshToken: text("refresh_token"),
47 |   idToken: text("id_token"),
48 |   accessTokenExpiresAt: integer("access_token_expires_at", {
49 |     mode: "timestamp_ms",
50 |   }),
51 |   refreshTokenExpiresAt: integer("refresh_token_expires_at", {
52 |     mode: "timestamp_ms",
53 |   }),
54 |   scope: text("scope"),
55 |   password: text("password"),
56 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
57 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
58 |     .notNull(),
59 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
60 |     .$onUpdate(() => /* @__PURE__ */ new Date())
61 |     .notNull(),
62 | });
63 | 
64 | export const custom_verification = sqliteTable("custom_verification", {
65 |   id: text("id").primaryKey(),
66 |   identifier: text("identifier").notNull(),
67 |   value: text("value").notNull(),
68 |   expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
69 |   createdAt: integer("created_at", { mode: "timestamp_ms" })
70 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
71 |     .notNull(),
72 |   updatedAt: integer("updated_at", { mode: "timestamp_ms" })
73 |     .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
74 |     .$onUpdate(() => /* @__PURE__ */ new Date())
75 |     .notNull(),
76 | });
77 | 
78 | export const passkey = sqliteTable("passkey", {
79 |   id: text("id").primaryKey(),
80 |   name: text("name"),
81 |   publicKey: text("public_key").notNull(),
82 |   userId: text("user_id")
83 |     .notNull()
84 |     .references(() => custom_user.id, { onDelete: "cascade" }),
85 |   credentialID: text("credential_id").notNull(),
86 |   counter: integer("counter").notNull(),
87 |   deviceType: text("device_type").notNull(),
88 |   backedUp: integer("backed_up", { mode: "boolean" }).notNull(),
89 |   transports: text("transports"),
90 |   createdAt: integer("created_at", { mode: "timestamp_ms" }),
91 |   aaguid: text("aaguid"),
92 | });
93 | 
```

--------------------------------------------------------------------------------
/docs/public/branding/better-auth-logo-wordmark-dark.svg:
--------------------------------------------------------------------------------

```
 1 | <svg width="1024" height="256" viewBox="0 0 1024 256" fill="none" xmlns="http://www.w3.org/2000/svg">
 2 | <rect width="1024" height="256" fill="black"/>
 3 | <rect x="96" y="79" width="34.6988" height="97.5904" fill="white"/>
 4 | <rect x="203.133" y="79" width="36.8675" height="97.5904" fill="white"/>
 5 | <rect x="238.916" y="79" width="31.4458" height="69.6144" transform="rotate(90 238.916 79)" fill="white"/>
 6 | <rect x="240" y="145.145" width="31.4458" height="70.6988" transform="rotate(90 240 145.145)" fill="white"/>
 7 | <rect x="169.301" y="110.446" width="34.6988" height="38.6024" transform="rotate(90 169.301 110.446)" fill="white"/>
 8 | <path d="M281.832 162V93.84H305.256C313.32 93.84 319.368 95.312 323.4 98.256C327.432 101.2 329.448 105.84 329.448 112.176C329.448 116.016 328.36 119.248 326.184 121.872C324.072 124.432 321.128 126.064 317.352 126.768C322.024 127.408 325.672 129.232 328.296 132.24C330.984 135.184 332.328 138.864 332.328 143.28C332.328 149.488 330.312 154.16 326.28 157.296C322.248 160.432 316.52 162 309.096 162H281.832ZM290.088 123.312H305.256C310.248 123.312 314.088 122.384 316.776 120.528C319.464 118.608 320.808 115.952 320.808 112.56C320.808 105.456 315.624 101.904 305.256 101.904H290.088V123.312ZM290.088 153.936H309.096C313.768 153.936 317.352 152.976 319.848 151.056C322.408 149.136 323.688 146.384 323.688 142.8C323.688 139.216 322.408 136.432 319.848 134.448C317.352 132.4 313.768 131.376 309.096 131.376H290.088V153.936ZM345.301 162V93.84H388.117V101.904H353.557V123.888H386.965V131.76H353.557V153.936H388.885V162H345.301ZM416.681 162V101.904H395.465V93.84H446.153V101.904H424.937V162H416.681ZM470.587 162V101.904H449.371V93.84H500.059V101.904H478.843V162H470.587ZM507.113 162V93.84H549.929V101.904H515.369V123.888H548.777V131.76H515.369V153.936H550.697V162H507.113ZM564.02 162V93.84H589.844C597.012 93.84 602.676 95.696 606.836 99.408C610.996 103.12 613.076 108.144 613.076 114.48C613.076 117.104 612.532 119.504 611.444 121.68C610.356 123.792 608.948 125.584 607.22 127.056C605.492 128.528 603.604 129.552 601.556 130.128C604.564 130.64 606.932 131.856 608.66 133.776C610.452 135.696 611.508 138.416 611.828 141.936L613.748 162H605.396L603.667 142.8C603.412 139.984 602.388 137.904 600.596 136.56C598.868 135.216 596.02 134.544 592.052 134.544H572.276V162H564.02ZM572.276 126.48H590.9C595.06 126.48 598.356 125.424 600.788 123.312C603.22 121.2 604.436 118.192 604.436 114.288C604.436 110.32 603.188 107.28 600.692 105.168C598.196 102.992 594.58 101.904 589.844 101.904H572.276V126.48ZM623.912 137.808V130.224H655.688V137.808H623.912ZM661.826 162L686.402 93.84H697.538L722.114 162H713.09L706.274 142.608H677.666L670.85 162H661.826ZM680.45 134.544H703.49L691.97 101.04L680.45 134.544ZM755.651 163.536C750.403 163.536 745.827 162.512 741.923 160.464C738.083 158.416 735.107 155.504 732.995 151.728C730.947 147.888 729.923 143.376 729.923 138.192V93.744H738.179V138.192C738.179 143.696 739.683 147.952 742.691 150.96C745.763 153.968 750.083 155.472 755.651 155.472C761.155 155.472 765.411 153.968 768.419 150.96C771.491 147.952 773.027 143.696 773.027 138.192V93.744H781.283V138.192C781.283 143.376 780.227 147.888 778.115 151.728C776.067 155.504 773.123 158.416 769.283 160.464C765.443 162.512 760.899 163.536 755.651 163.536ZM811.087 162V101.904H789.871V93.84H840.559V101.904H819.343V162H811.087ZM847.613 162V93.84H855.869V123.696H890.141V93.84H898.397V162H890.141V131.76H855.869V162H847.613ZM911.443 162V151.152H922.291V162H911.443Z" fill="white"/>
 9 | </svg>
10 | 
```

--------------------------------------------------------------------------------
/docs/public/branding/better-auth-logo-wordmark-light.svg:
--------------------------------------------------------------------------------

```
 1 | <svg width="1024" height="256" viewBox="0 0 1024 256" fill="none" xmlns="http://www.w3.org/2000/svg">
 2 | <rect width="1024" height="256" fill="#FFEAEA"/>
 3 | <rect x="96" y="79" width="34.6988" height="97.5904" fill="black"/>
 4 | <rect x="203.133" y="79" width="36.8675" height="97.5904" fill="black"/>
 5 | <rect x="238.916" y="79" width="31.4458" height="69.6144" transform="rotate(90 238.916 79)" fill="black"/>
 6 | <rect x="240" y="145.145" width="31.4458" height="70.6988" transform="rotate(90 240 145.145)" fill="black"/>
 7 | <rect x="169.301" y="110.446" width="34.6988" height="38.6024" transform="rotate(90 169.301 110.446)" fill="black"/>
 8 | <path d="M281.832 162V93.84H305.256C313.32 93.84 319.368 95.312 323.4 98.256C327.432 101.2 329.448 105.84 329.448 112.176C329.448 116.016 328.36 119.248 326.184 121.872C324.072 124.432 321.128 126.064 317.352 126.768C322.024 127.408 325.672 129.232 328.296 132.24C330.984 135.184 332.328 138.864 332.328 143.28C332.328 149.488 330.312 154.16 326.28 157.296C322.248 160.432 316.52 162 309.096 162H281.832ZM290.088 123.312H305.256C310.248 123.312 314.088 122.384 316.776 120.528C319.464 118.608 320.808 115.952 320.808 112.56C320.808 105.456 315.624 101.904 305.256 101.904H290.088V123.312ZM290.088 153.936H309.096C313.768 153.936 317.352 152.976 319.848 151.056C322.408 149.136 323.688 146.384 323.688 142.8C323.688 139.216 322.408 136.432 319.848 134.448C317.352 132.4 313.768 131.376 309.096 131.376H290.088V153.936ZM345.301 162V93.84H388.117V101.904H353.557V123.888H386.965V131.76H353.557V153.936H388.885V162H345.301ZM416.681 162V101.904H395.465V93.84H446.153V101.904H424.937V162H416.681ZM470.587 162V101.904H449.371V93.84H500.059V101.904H478.843V162H470.587ZM507.113 162V93.84H549.929V101.904H515.369V123.888H548.777V131.76H515.369V153.936H550.697V162H507.113ZM564.02 162V93.84H589.844C597.012 93.84 602.676 95.696 606.836 99.408C610.996 103.12 613.076 108.144 613.076 114.48C613.076 117.104 612.532 119.504 611.444 121.68C610.356 123.792 608.948 125.584 607.22 127.056C605.492 128.528 603.604 129.552 601.556 130.128C604.564 130.64 606.932 131.856 608.66 133.776C610.452 135.696 611.508 138.416 611.828 141.936L613.748 162H605.396L603.667 142.8C603.412 139.984 602.388 137.904 600.596 136.56C598.868 135.216 596.02 134.544 592.052 134.544H572.276V162H564.02ZM572.276 126.48H590.9C595.06 126.48 598.356 125.424 600.788 123.312C603.22 121.2 604.436 118.192 604.436 114.288C604.436 110.32 603.188 107.28 600.692 105.168C598.196 102.992 594.58 101.904 589.844 101.904H572.276V126.48ZM623.912 137.808V130.224H655.688V137.808H623.912ZM661.826 162L686.402 93.84H697.538L722.114 162H713.09L706.274 142.608H677.666L670.85 162H661.826ZM680.45 134.544H703.49L691.97 101.04L680.45 134.544ZM755.651 163.536C750.403 163.536 745.827 162.512 741.923 160.464C738.083 158.416 735.107 155.504 732.995 151.728C730.947 147.888 729.923 143.376 729.923 138.192V93.744H738.179V138.192C738.179 143.696 739.683 147.952 742.691 150.96C745.763 153.968 750.083 155.472 755.651 155.472C761.155 155.472 765.411 153.968 768.419 150.96C771.491 147.952 773.027 143.696 773.027 138.192V93.744H781.283V138.192C781.283 143.376 780.227 147.888 778.115 151.728C776.067 155.504 773.123 158.416 769.283 160.464C765.443 162.512 760.899 163.536 755.651 163.536ZM811.087 162V101.904H789.871V93.84H840.559V101.904H819.343V162H811.087ZM847.613 162V93.84H855.869V123.696H890.141V93.84H898.397V162H890.141V131.76H855.869V162H847.613ZM911.443 162V151.152H922.291V162H911.443Z" fill="black"/>
 9 | </svg>
10 | 
```

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

```typescript
  1 | import * as z from "zod";
  2 | import {
  3 | 	createAuthEndpoint,
  4 | 	createAuthMiddleware,
  5 | } from "@better-auth/core/api";
  6 | import { getSession } from "../../api";
  7 | import type { InferSession, InferUser } from "../../types";
  8 | import type { BetterAuthOptions, BetterAuthPlugin } from "@better-auth/core";
  9 | import { getEndpointResponse } from "../../utils/plugin-helper";
 10 | import type { GenericEndpointContext } from "@better-auth/core";
 11 | 
 12 | const getSessionQuerySchema = z.optional(
 13 | 	z.object({
 14 | 		/**
 15 | 		 * If cookie cache is enabled, it will disable the cache
 16 | 		 * and fetch the session from the database
 17 | 		 */
 18 | 		disableCookieCache: z
 19 | 			.boolean()
 20 | 			.meta({
 21 | 				description: "Disable cookie cache and fetch session from database",
 22 | 			})
 23 | 			.or(z.string().transform((v) => v === "true"))
 24 | 			.optional(),
 25 | 		disableRefresh: z
 26 | 			.boolean()
 27 | 			.meta({
 28 | 				description:
 29 | 					"Disable session refresh. Useful for checking session status, without updating the session",
 30 | 			})
 31 | 			.optional(),
 32 | 	}),
 33 | );
 34 | 
 35 | export type CustomSessionPluginOptions = {
 36 | 	/**
 37 | 	 * This option is used to determine if the list-device-sessions endpoint should be mutated to the custom session data.
 38 | 	 * @default false
 39 | 	 */
 40 | 	shouldMutateListDeviceSessionsEndpoint?: boolean;
 41 | };
 42 | 
 43 | export const customSession = <
 44 | 	Returns extends Record<string, any>,
 45 | 	O extends BetterAuthOptions = BetterAuthOptions,
 46 | >(
 47 | 	fn: (
 48 | 		session: {
 49 | 			user: InferUser<O>;
 50 | 			session: InferSession<O>;
 51 | 		},
 52 | 		ctx: GenericEndpointContext,
 53 | 	) => Promise<Returns>,
 54 | 	options?: O,
 55 | 	pluginOptions?: CustomSessionPluginOptions,
 56 | ) => {
 57 | 	return {
 58 | 		id: "custom-session",
 59 | 		hooks: {
 60 | 			after: [
 61 | 				{
 62 | 					matcher: (ctx) =>
 63 | 						ctx.path === "/multi-session/list-device-sessions" &&
 64 | 						(pluginOptions?.shouldMutateListDeviceSessionsEndpoint ?? false),
 65 | 					handler: createAuthMiddleware(async (ctx) => {
 66 | 						const response = await getEndpointResponse<[]>(ctx);
 67 | 						if (!response) return;
 68 | 						const newResponse = await Promise.all(
 69 | 							response.map(async (v) => await fn(v, ctx)),
 70 | 						);
 71 | 						return ctx.json(newResponse);
 72 | 					}),
 73 | 				},
 74 | 			],
 75 | 		},
 76 | 		endpoints: {
 77 | 			getSession: createAuthEndpoint(
 78 | 				"/get-session",
 79 | 				{
 80 | 					method: "GET",
 81 | 					query: getSessionQuerySchema,
 82 | 					metadata: {
 83 | 						CUSTOM_SESSION: true,
 84 | 						openapi: {
 85 | 							description: "Get custom session data",
 86 | 							responses: {
 87 | 								"200": {
 88 | 									description: "Success",
 89 | 									content: {
 90 | 										"application/json": {
 91 | 											schema: {
 92 | 												type: "array",
 93 | 												nullable: true,
 94 | 												items: {
 95 | 													$ref: "#/components/schemas/Session",
 96 | 												},
 97 | 											},
 98 | 										},
 99 | 									},
100 | 								},
101 | 							},
102 | 						},
103 | 					},
104 | 					requireHeaders: true,
105 | 				},
106 | 				async (ctx): Promise<Returns | null> => {
107 | 					const session = await getSession()({
108 | 						...ctx,
109 | 						asResponse: false,
110 | 						headers: ctx.headers,
111 | 						returnHeaders: true,
112 | 					}).catch((e) => {
113 | 						return null;
114 | 					});
115 | 					if (!session?.response) {
116 | 						return ctx.json(null);
117 | 					}
118 | 					const fnResult = await fn(session.response as any, ctx);
119 | 
120 | 					const setCookie = session.headers.get("set-cookie");
121 | 					if (setCookie) {
122 | 						ctx.setHeader("set-cookie", setCookie);
123 | 						session.headers.delete("set-cookie");
124 | 					}
125 | 
126 | 					session.headers.forEach((value, key) => {
127 | 						ctx.setHeader(key, value);
128 | 					});
129 | 					return ctx.json(fnResult);
130 | 				},
131 | 			),
132 | 		},
133 | 		$Infer: {
134 | 			Session: {} as Awaited<ReturnType<typeof fn>>,
135 | 		},
136 | 	} satisfies BetterAuthPlugin;
137 | };
138 | 
```

