#
tokens: 49197/50000 17/1099 files (page 15/51)
lines: off (toggle) GitHub
raw markdown copy
This is page 15 of 51. Use http://codebase.md/better-auth/better-auth?lines=false&page={x} to view the full context.

# Directory Structure

```
├── .gitattributes
├── .github
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   ├── renovate.json5
│   └── workflows
│       ├── ci.yml
│       ├── e2e.yml
│       ├── preview.yml
│       └── release.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .vscode
│   └── settings.json
├── banner-dark.png
├── banner.png
├── biome.json
├── bump.config.ts
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── demo
│   ├── expo-example
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── app.config.ts
│   │   ├── assets
│   │   │   ├── bg-image.jpeg
│   │   │   ├── fonts
│   │   │   │   └── SpaceMono-Regular.ttf
│   │   │   ├── icon.png
│   │   │   └── images
│   │   │       ├── adaptive-icon.png
│   │   │       ├── favicon.png
│   │   │       ├── logo.png
│   │   │       ├── partial-react-logo.png
│   │   │       ├── react-logo.png
│   │   │       ├── [email protected]
│   │   │       ├── [email protected]
│   │   │       └── splash.png
│   │   ├── babel.config.js
│   │   ├── components.json
│   │   ├── expo-env.d.ts
│   │   ├── index.ts
│   │   ├── metro.config.js
│   │   ├── nativewind-env.d.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── app
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── api
│   │   │   │   │   └── auth
│   │   │   │   │       └── [...route]+api.ts
│   │   │   │   ├── dashboard.tsx
│   │   │   │   ├── forget-password.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   └── sign-up.tsx
│   │   │   ├── components
│   │   │   │   ├── icons
│   │   │   │   │   └── google.tsx
│   │   │   │   └── ui
│   │   │   │       ├── avatar.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       └── text.tsx
│   │   │   ├── global.css
│   │   │   └── lib
│   │   │       ├── auth-client.ts
│   │   │       ├── auth.ts
│   │   │       ├── icons
│   │   │       │   ├── iconWithClassName.ts
│   │   │       │   └── X.tsx
│   │   │       └── utils.ts
│   │   ├── tailwind.config.js
│   │   └── tsconfig.json
│   └── nextjs
│       ├── .env.example
│       ├── .gitignore
│       ├── app
│       │   ├── (auth)
│       │   │   ├── forget-password
│       │   │   │   └── page.tsx
│       │   │   ├── reset-password
│       │   │   │   └── page.tsx
│       │   │   ├── sign-in
│       │   │   │   ├── loading.tsx
│       │   │   │   └── page.tsx
│       │   │   └── two-factor
│       │   │       ├── otp
│       │   │       │   └── page.tsx
│       │   │       └── page.tsx
│       │   ├── accept-invitation
│       │   │   └── [id]
│       │   │       ├── invitation-error.tsx
│       │   │       └── page.tsx
│       │   ├── admin
│       │   │   └── page.tsx
│       │   ├── api
│       │   │   └── auth
│       │   │       └── [...all]
│       │   │           └── route.ts
│       │   ├── apps
│       │   │   └── register
│       │   │       └── page.tsx
│       │   ├── client-test
│       │   │   └── page.tsx
│       │   ├── dashboard
│       │   │   ├── change-plan.tsx
│       │   │   ├── client.tsx
│       │   │   ├── organization-card.tsx
│       │   │   ├── page.tsx
│       │   │   ├── upgrade-button.tsx
│       │   │   └── user-card.tsx
│       │   ├── device
│       │   │   ├── approve
│       │   │   │   └── page.tsx
│       │   │   ├── denied
│       │   │   │   └── page.tsx
│       │   │   ├── layout.tsx
│       │   │   ├── page.tsx
│       │   │   └── success
│       │   │       └── page.tsx
│       │   ├── favicon.ico
│       │   ├── features.tsx
│       │   ├── fonts
│       │   │   ├── GeistMonoVF.woff
│       │   │   └── GeistVF.woff
│       │   ├── globals.css
│       │   ├── layout.tsx
│       │   ├── oauth
│       │   │   └── authorize
│       │   │       ├── concet-buttons.tsx
│       │   │       └── page.tsx
│       │   ├── page.tsx
│       │   └── pricing
│       │       └── page.tsx
│       ├── components
│       │   ├── account-switch.tsx
│       │   ├── blocks
│       │   │   └── pricing.tsx
│       │   ├── logo.tsx
│       │   ├── one-tap.tsx
│       │   ├── sign-in-btn.tsx
│       │   ├── sign-in.tsx
│       │   ├── sign-up.tsx
│       │   ├── theme-provider.tsx
│       │   ├── theme-toggle.tsx
│       │   ├── tier-labels.tsx
│       │   ├── ui
│       │   │   ├── accordion.tsx
│       │   │   ├── alert-dialog.tsx
│       │   │   ├── alert.tsx
│       │   │   ├── aspect-ratio.tsx
│       │   │   ├── avatar.tsx
│       │   │   ├── badge.tsx
│       │   │   ├── breadcrumb.tsx
│       │   │   ├── button.tsx
│       │   │   ├── calendar.tsx
│       │   │   ├── card.tsx
│       │   │   ├── carousel.tsx
│       │   │   ├── chart.tsx
│       │   │   ├── checkbox.tsx
│       │   │   ├── collapsible.tsx
│       │   │   ├── command.tsx
│       │   │   ├── context-menu.tsx
│       │   │   ├── copy-button.tsx
│       │   │   ├── dialog.tsx
│       │   │   ├── drawer.tsx
│       │   │   ├── dropdown-menu.tsx
│       │   │   ├── form.tsx
│       │   │   ├── hover-card.tsx
│       │   │   ├── input-otp.tsx
│       │   │   ├── input.tsx
│       │   │   ├── label.tsx
│       │   │   ├── menubar.tsx
│       │   │   ├── navigation-menu.tsx
│       │   │   ├── pagination.tsx
│       │   │   ├── password-input.tsx
│       │   │   ├── popover.tsx
│       │   │   ├── progress.tsx
│       │   │   ├── radio-group.tsx
│       │   │   ├── resizable.tsx
│       │   │   ├── scroll-area.tsx
│       │   │   ├── select.tsx
│       │   │   ├── separator.tsx
│       │   │   ├── sheet.tsx
│       │   │   ├── skeleton.tsx
│       │   │   ├── slider.tsx
│       │   │   ├── sonner.tsx
│       │   │   ├── switch.tsx
│       │   │   ├── table.tsx
│       │   │   ├── tabs.tsx
│       │   │   ├── tabs2.tsx
│       │   │   ├── textarea.tsx
│       │   │   ├── toast.tsx
│       │   │   ├── toaster.tsx
│       │   │   ├── toggle-group.tsx
│       │   │   ├── toggle.tsx
│       │   │   └── tooltip.tsx
│       │   └── wrapper.tsx
│       ├── components.json
│       ├── hooks
│       │   └── use-toast.ts
│       ├── lib
│       │   ├── auth-client.ts
│       │   ├── auth-types.ts
│       │   ├── auth.ts
│       │   ├── email
│       │   │   ├── invitation.tsx
│       │   │   ├── resend.ts
│       │   │   └── reset-password.tsx
│       │   ├── metadata.ts
│       │   ├── shared.ts
│       │   └── utils.ts
│       ├── 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

--------------------------------------------------------------------------------
/docs/app/api/og-release/route.tsx:
--------------------------------------------------------------------------------

```typescript
import { ImageResponse } from "@vercel/og";
import { z } from "zod";
export const runtime = "edge";

const ogSchema = z.object({
	heading: z.string(),
	description: z.string().optional(),
	date: z.string().optional(),
});

export async function GET(req: Request) {
	try {
		const geist = await fetch(
			new URL("../../../assets/Geist.ttf", import.meta.url),
		).then((res) => res.arrayBuffer());

		const url = new URL(req.url);
		const urlParamsValues = Object.fromEntries(url.searchParams);
		const validParams = ogSchema.parse(urlParamsValues);

		const { heading, description, date } = validParams;
		const trueHeading =
			heading.length > 140 ? `${heading.substring(0, 140)}...` : heading;

		return new ImageResponse(
			<div
				tw="flex w-full h-full relative flex-col"
				style={{
					background:
						"radial-gradient(circle 230px at 0% 0%, #000000, #000000)",
					fontFamily: "Geist",
					color: "white",
				}}
			>
				<div
					tw="flex w-full h-full relative"
					style={{
						borderRadius: "10px",
						border: "1px solid rgba(32, 34, 34, 0.5)",
					}}
				>
					<div
						tw="absolute"
						style={{
							width: "350px",
							height: "120px",
							borderRadius: "100px",
							background: "#c7c7c7",
							opacity: 0.21,
							filter: "blur(35px)",
							transform: "rotate(50deg)",
							top: "18%",
							left: "0%",
						}}
					/>

					<div
						tw="flex flex-col w-full relative h-full p-8"
						style={{
							gap: "14px",
							position: "relative",
							zIndex: 999,
						}}
					>
						<div
							tw="absolute bg-repeat w-full h-full"
							style={{
								width: "100%",
								height: "100%",
								zIndex: 999,

								background:
									"url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnN2Z2pzPSJodHRwOi8vc3ZnanMuZGV2L3N2Z2pzIiB2aWV3Qm94PSIwIDAgODAwIDgwMCIgd2lkdGg9IjgwMCIgaGVpZ2h0PSI4MDAiPjxnIHN0cm9rZS13aWR0aD0iMy41IiBzdHJva2U9ImhzbGEoMCwgMCUsIDEwMCUsIDEuMDApIiBmaWxsPSJub25lIiBvcGFjaXR5PSIwLjUiPjxyZWN0IHdpZHRoPSI0MDAiIGhlaWdodD0iNDAwIiB4PSIwIiB5PSIwIiBvcGFjaXR5PSIwLjE1Ij48L3JlY3Q+PGNpcmNsZSByPSIxMC44NTUyNjMxNTc4OTQ3MzYiIGN4PSIwIiBjeT0iMCIgZmlsbD0iaHNsYSgwLCAwJSwgMTAwJSwgMS4wMCkiIHN0cm9rZT0ibm9uZSI+PC9jaXJjbGU+PHJlY3Qgd2lkdGg9IjQwMCIgaGVpZ2h0PSI0MDAiIHg9IjQwMCIgeT0iMCIgb3BhY2l0eT0iMC4xNSI+PC9yZWN0PjxjaXJjbGUgcj0iMTAuODU1MjYzMTU3ODk0NzM2IiBjeD0iNDAwIiBjeT0iMCIgZmlsbD0iaHNsYSgwLCAwJSwgMTAwJSwgMS4wMCkiIHN0cm9rZT0ibm9uZSI+PC9jaXJjbGU+PHJlY3Qgd2lkdGg9IjQwMCIgaGVpZ2h0PSI0MDAiIHg9IjgwMCIgeT0iMCIgb3BhY2l0eT0iMC4xNSI+PC9yZWN0PjxjaXJjbGUgcj0iMTAuODU1MjYzMTU3ODk0NzM2IiBjeD0iODAwIiBjeT0iMCIgZmlsbD0iaHNsYSgwLCAwJSwgMTAwJSwgMS4wMCkiIHN0cm9rZT0ibm9uZSI+PC9jaXJjbGU+PHJlY3Qgd2lkdGg9IjQwMCIgaGVpZ2h0PSI0MDAiIHg9IjAiIHk9IjQwMCIgb3BhY2l0eT0iMC4xNSI+PC9yZWN0PjxjaXJjbGUgcj0iMTAuODU1MjYzMTU3ODk0NzM2IiBjeD0iMCIgY3k9IjQwMCIgZmlsbD0iaHNsYSgwLCAwJSwgMTAwJSwgMS4wMCkiIHN0cm9rZT0ibm9uZSI+PC9jaXJjbGU+PHJlY3Qgd2lkdGg9IjQwMCIgaGVpZ2h0PSI0MDAiIHg9IjQwMCIgeT0iNDAwIiBvcGFjaXR5PSIwLjE1Ij48L3JlY3Q+PGNpcmNsZSByPSIxMC44NTUyNjMxNTc4OTQ3MzYiIGN4PSI0MDAiIGN5PSI0MDAiIGZpbGw9ImhzbGEoMCwgMCUsIDEwMCUsIDEuMDApIiBzdHJva2U9Im5vbmUiPjwvY2lyY2xlPjxyZWN0IHdpZHRoPSI0MDAiIGhlaWdodD0iNDAwIiB4PSI4MDAiIHk9IjQwMCIgb3BhY2l0eT0iMC4xNSI+PC9yZWN0PjxjaXJjbGUgcj0iMTAuODU1MjYzMTU3ODk0NzM2IiBjeD0iODAwIiBjeT0iNDAwIiBmaWxsPSJoc2xhKDAsIDAlLCAxMDAlLCAxLjAwKSIgc3Ryb2tlPSJub25lIj48L2NpcmNsZT48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgeD0iMCIgeT0iODAwIiBvcGFjaXR5PSIwLjE1Ij48L3JlY3Q+PGNpcmNsZSByPSIxMC44NTUyNjMxNTc4OTQ3MzYiIGN4PSIwIiBjeT0iODAwIiBmaWxsPSJoc2xhKDAsIDAlLCAxMDAlLCAxLjAwKSIgc3Ryb2tlPSJub25lIj48L2NpcmNsZT48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgeD0iNDAwIiB5PSI4MDAiIG9wYWNpdHk9IjAuMTUiPjwvcmVjdD48Y2lyY2xlIHI9IjEwLjg1NTI2MzE1Nzg5NDczNiIgY3g9IjQwMCIgY3k9IjgwMCIgZmlsbD0iaHNsYSgwLCAwJSwgMTAwJSwgMS4wMCkiIHN0cm9rZT0ibm9uZSI+PC9jaXJjbGU+PHJlY3Qgd2lkdGg9IjQwMCIgaGVpZ2h0PSI0MDAiIHg9IjgwMCIgeT0iODAwIiBvcGFjaXR5PSIwLjE1Ij48L3JlY3Q+PGNpcmNsZSByPSIxMC44NTUyNjM1NTc4OTQ3MzYiIGN4PSI4MDAiIGN5PSI4MDAiIGZpbGw9ImhzbGEoMCwgMCUsIDEwMCUsIDEuMDApIiBzdHJva2U9Im5vbmUiPjwvY2lyY2xlPjwvZz48L3N2Zz4=')",
								backgroundSize: "25px 25px",
								display: "flex",
								alignItems: "flex-start",
								justifyContent: "flex-start",
								position: "relative",
								flexDirection: "column",
								textAlign: "left",
								paddingLeft: "170px",
								gap: "14px",
							}}
						/>
						<div
							tw="flex text-6xl absolute bottom-56 isolate font-bold"
							style={{
								paddingLeft: "170px",
								paddingTop: "200px",
								background: "linear-gradient(45deg, #000000 4%, #fff, #000)",
								backgroundClip: "text",
								color: "transparent",
							}}
						>
							{trueHeading}
						</div>

						<div
							tw="flex absolute bottom-44 z-[999] text-2xl"
							style={{
								paddingLeft: "170px",
								background:
									"linear-gradient(10deg, #d4d4d8, 04%, #fff, #d4d4d8)",
								backgroundClip: "text",
								opacity: 0.7,
								color: "transparent",
							}}
						>
							{description}
						</div>

						<div
							tw="flex text-2xl absolute bottom-28 z-[999]"
							style={{
								paddingLeft: "170px",
								background:
									"linear-gradient(10deg, #d4d4d8, 04%, #fff, #d4d4d8)",
								backgroundClip: "text",
								opacity: 0.8,
								color: "transparent",
							}}
						>
							{date}
						</div>
					</div>

					{/* Lines */}
					<div
						tw="absolute top-10% w-full h-px"
						style={{
							background: "linear-gradient(90deg, #888888 30%, #1d1f1f 70%)",
						}}
					/>
					<div
						tw="absolute bottom-10% w-full h-px"
						style={{
							background: "#2c2c2c",
						}}
					/>
					<div
						tw="absolute left-10% h-full w-px"
						style={{
							background: "linear-gradient(180deg, #747474 30%, #222424 70%)",
						}}
					/>
					<div
						tw="absolute right-10% h-full w-px"
						style={{
							background: "#2c2c2c",
						}}
					/>
				</div>
			</div>,
			{
				width: 1200,
				height: 630,
				fonts: [
					{
						name: "Geist",
						data: geist,
						weight: 400,
						style: "normal",
					},
				],
			},
		);
	} catch (err) {
		console.log({ err });
		return new Response("Failed to generate the OG image", { status: 500 });
	}
}

```

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

```typescript
import { betterFetch } from "@better-fetch/fetch";
import { APIError } from "better-call";
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
import type { OAuthProvider, ProviderOptions } from "../oauth2";
import {
	refreshAccessToken,
	createAuthorizationURL,
	validateAuthorizationCode,
} from "../oauth2";
export interface AppleProfile {
	/**
	 * The subject registered claim identifies the principal that’s the subject
	 * of the identity token. Because this token is for your app, the value is
	 * the unique identifier for the user.
	 */
	sub: string;
	/**
	 * A String value representing the user's email address.
	 * The email address is either the user's real email address or the proxy
	 * address, depending on their status private email relay service.
	 */
	email: string;
	/**
	 * A string or Boolean value that indicates whether the service verifies
	 * the email. The value can either be a string ("true" or "false") or a
	 * Boolean (true or false). The system may not verify email addresses for
	 * Sign in with Apple at Work & School users, and this claim is "false" or
	 * false for those users.
	 */
	email_verified: true | "true";
	/**
	 * A string or Boolean value that indicates whether the email that the user
	 * shares is the proxy address. The value can either be a string ("true" or
	 * "false") or a Boolean (true or false).
	 */
	is_private_email: boolean;
	/**
	 * An Integer value that indicates whether the user appears to be a real
	 * person. Use the value of this claim to mitigate fraud. The possible
	 * values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal). For
	 * more information, see ASUserDetectionStatus. This claim is present only
	 * in iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14
	 * and later. The claim isn’t present or supported for web-based apps.
	 */
	real_user_status: number;
	/**
	 * The user’s full name in the format provided during the authorization
	 * process.
	 */
	name: string;
	/**
	 * The URL to the user's profile picture.
	 */
	picture: string;
	user?: AppleNonConformUser;
}

/**
 * This is the shape of the `user` query parameter that Apple sends the first
 * time the user consents to the app.
 * @see https://developer.apple.com/documentation/signinwithapplerestapi/request-an-authorization-to-the-sign-in-with-apple-server./
 */
export interface AppleNonConformUser {
	name: {
		firstName: string;
		lastName: string;
	};
	email: string;
}

export interface AppleOptions extends ProviderOptions<AppleProfile> {
	clientId: string;
	appBundleIdentifier?: string;
	audience?: string | string[];
}

export const apple = (options: AppleOptions) => {
	const tokenEndpoint = "https://appleid.apple.com/auth/token";
	return {
		id: "apple",
		name: "Apple",
		async createAuthorizationURL({ state, scopes, redirectURI }) {
			const _scope = options.disableDefaultScope ? [] : ["email", "name"];
			options.scope && _scope.push(...options.scope);
			scopes && _scope.push(...scopes);
			const url = await createAuthorizationURL({
				id: "apple",
				options,
				authorizationEndpoint: "https://appleid.apple.com/auth/authorize",
				scopes: _scope,
				state,
				redirectURI,
				responseMode: "form_post",
				responseType: "code id_token",
			});
			return url;
		},
		validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
			return validateAuthorizationCode({
				code,
				codeVerifier,
				redirectURI,
				options,
				tokenEndpoint,
			});
		},
		async verifyIdToken(token, nonce) {
			if (options.disableIdTokenSignIn) {
				return false;
			}
			if (options.verifyIdToken) {
				return options.verifyIdToken(token, nonce);
			}
			const decodedHeader = decodeProtectedHeader(token);
			const { kid, alg: jwtAlg } = decodedHeader;
			if (!kid || !jwtAlg) return false;
			const publicKey = await getApplePublicKey(kid);
			const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
				algorithms: [jwtAlg],
				issuer: "https://appleid.apple.com",
				audience:
					options.audience && options.audience.length
						? options.audience
						: options.appBundleIdentifier
							? options.appBundleIdentifier
							: options.clientId,
				maxTokenAge: "1h",
			});
			["email_verified", "is_private_email"].forEach((field) => {
				if (jwtClaims[field] !== undefined) {
					jwtClaims[field] = Boolean(jwtClaims[field]);
				}
			});
			if (nonce && jwtClaims.nonce !== nonce) {
				return false;
			}
			return !!jwtClaims;
		},
		refreshAccessToken: options.refreshAccessToken
			? options.refreshAccessToken
			: async (refreshToken) => {
					return refreshAccessToken({
						refreshToken,
						options: {
							clientId: options.clientId,
							clientKey: options.clientKey,
							clientSecret: options.clientSecret,
						},
						tokenEndpoint: "https://appleid.apple.com/auth/token",
					});
				},
		async getUserInfo(token) {
			if (options.getUserInfo) {
				return options.getUserInfo(token);
			}
			if (!token.idToken) {
				return null;
			}
			const profile = decodeJwt<AppleProfile>(token.idToken);
			if (!profile) {
				return null;
			}
			const name = token.user
				? `${token.user.name?.firstName} ${token.user.name?.lastName}`
				: profile.name || profile.email;
			const emailVerified =
				typeof profile.email_verified === "boolean"
					? profile.email_verified
					: profile.email_verified === "true";
			const enrichedProfile = {
				...profile,
				name,
			};
			const userMap = await options.mapProfileToUser?.(enrichedProfile);
			return {
				user: {
					id: profile.sub,
					name: enrichedProfile.name,
					emailVerified: emailVerified,
					email: profile.email,
					...userMap,
				},
				data: enrichedProfile,
			};
		},
		options,
	} satisfies OAuthProvider<AppleProfile>;
};

export const getApplePublicKey = async (kid: string) => {
	const APPLE_BASE_URL = "https://appleid.apple.com";
	const JWKS_APPLE_URI = "/auth/keys";
	const { data } = await betterFetch<{
		keys: Array<{
			kid: string;
			alg: string;
			kty: string;
			use: string;
			n: string;
			e: string;
		}>;
	}>(`${APPLE_BASE_URL}${JWKS_APPLE_URI}`);
	if (!data?.keys) {
		throw new APIError("BAD_REQUEST", {
			message: "Keys not found",
		});
	}
	const jwk = data.keys.find((key) => key.kid === kid);
	if (!jwk) {
		throw new Error(`JWK with kid ${kid} not found`);
	}
	return await importJWK(jwk, jwk.alg);
};

```

--------------------------------------------------------------------------------
/packages/better-auth/src/test-utils/index.ts:
--------------------------------------------------------------------------------

```typescript
import { afterAll } from "vitest";
import { betterAuth } from "../auth";
import { createAuthClient } from "../client/vanilla";
import type { BetterAuthOptions, Session, User } from "../types";
import { getMigrations } from "../db/get-migration";
import { parseSetCookieHeader, setCookieToHeader } from "../cookies";
import type { SuccessContext } from "@better-fetch/fetch";
import { getAdapter } from "../db/utils";
import { getBaseURL } from "../utils/url";
import { Kysely, MysqlDialect, PostgresDialect, sql } from "kysely";
import { Pool } from "pg";
import { MongoClient } from "mongodb";
import { mongodbAdapter } from "../adapters/mongodb-adapter";
import { createPool } from "mysql2/promise";
import { bearer } from "../plugins";
import type { BetterAuthClientOptions } from "@better-auth/core";

export async function getTestInstanceMemory<
	O extends Partial<BetterAuthOptions>,
	C extends BetterAuthClientOptions,
>(
	options?: O,
	config?: {
		clientOptions?: C;
		port?: number;
		disableTestUser?: boolean;
		testUser?: Partial<User>;
		testWith?: "sqlite" | "postgres" | "mongodb" | "mysql" | "memory";
	},
) {
	const testWith = config?.testWith || "memory";
	const postgres = new Kysely({
		dialect: new PostgresDialect({
			pool: new Pool({
				connectionString: "postgres://user:password@localhost:5432/better_auth",
			}),
		}),
	});

	const mysql = new Kysely({
		dialect: new MysqlDialect(
			createPool("mysql://user:password@localhost:3306/better_auth"),
		),
	});

	async function mongodbClient() {
		const dbClient = async (connectionString: string, dbName: string) => {
			const client = new MongoClient(connectionString);
			await client.connect();
			const db = client.db(dbName);
			return db;
		};
		const db = await dbClient("mongodb://127.0.0.1:27017", "better-auth");
		return db;
	}

	const opts = {
		socialProviders: {
			github: {
				clientId: "test",
				clientSecret: "test",
			},
			google: {
				clientId: "test",
				clientSecret: "test",
			},
		},
		secret: "better-auth.secret",
		database:
			testWith === "postgres"
				? { db: postgres, type: "postgres" }
				: testWith === "mongodb"
					? mongodbAdapter(await mongodbClient())
					: testWith === "mysql"
						? { db: mysql, type: "mysql" }
						: undefined,
		emailAndPassword: {
			enabled: true,
		},
		rateLimit: {
			enabled: false,
		},
		advanced: {
			cookies: {},
		},
	} satisfies BetterAuthOptions;

	const auth = betterAuth({
		baseURL: "http://localhost:" + (config?.port || 3000),
		...opts,
		...options,
		advanced: {
			disableCSRFCheck: true,
			...options?.advanced,
		},
		plugins: [bearer(), ...(options?.plugins || [])],
	} as unknown as O extends undefined ? typeof opts : O & typeof opts);

	const testUser = {
		email: "[email protected]",
		password: "test123456",
		name: "test user",
		...config?.testUser,
	};
	async function createTestUser() {
		if (config?.disableTestUser) {
			return;
		}
		//@ts-expect-error
		const res = await auth.api.signUpEmail({
			body: testUser,
		});
	}

	if (testWith !== "mongodb" && testWith !== "memory") {
		const { runMigrations } = await getMigrations({
			...auth.options,
			database: opts.database,
		});
		await runMigrations();
	}

	await createTestUser();

	afterAll(async () => {
		if (testWith === "mongodb") {
			const db = await mongodbClient();
			await db.dropDatabase();
			return;
		}
		if (testWith === "postgres") {
			await sql`DROP SCHEMA public CASCADE; CREATE SCHEMA public;`.execute(
				postgres,
			);
			await postgres.destroy();
			return;
		}

		if (testWith === "mysql") {
			await sql`SET FOREIGN_KEY_CHECKS = 0;`.execute(mysql);
			const tables = await mysql.introspection.getTables();
			for (const table of tables) {
				// @ts-expect-error
				await mysql.deleteFrom(table.name).execute();
			}
			await sql`SET FOREIGN_KEY_CHECKS = 1;`.execute(mysql);
			return;
		}
	});

	async function signInWithTestUser() {
		if (config?.disableTestUser) {
			throw new Error("Test user is disabled");
		}
		let headers = new Headers();
		const setCookie = (name: string, value: string) => {
			const current = headers.get("cookie");
			headers.set("cookie", `${current || ""}; ${name}=${value}`);
		};
		//@ts-expect-error
		const { data, error } = await client.signIn.email({
			email: testUser.email,
			password: testUser.password,
			fetchOptions: {
				//@ts-expect-error
				onSuccess(context) {
					const header = context.response.headers.get("set-cookie");
					const cookies = parseSetCookieHeader(header || "");
					const signedCookie = cookies.get("better-auth.session_token")?.value;
					headers.set("cookie", `better-auth.session_token=${signedCookie}`);
				},
			},
		});
		return {
			session: data.session as Session,
			user: data.user as User,
			headers,
			setCookie,
		};
	}
	async function signInWithUser(email: string, password: string) {
		let headers = new Headers();
		//@ts-expect-error
		const { data } = await client.signIn.email({
			email,
			password,
			fetchOptions: {
				//@ts-expect-error
				onSuccess(context) {
					const header = context.response.headers.get("set-cookie");
					const cookies = parseSetCookieHeader(header || "");
					const signedCookie = cookies.get("better-auth.session_token")?.value;
					headers.set("cookie", `better-auth.session_token=${signedCookie}`);
				},
			},
		});
		return {
			res: data as {
				user: User;
				session: Session;
			},
			headers,
		};
	}

	const customFetchImpl = async (
		url: string | URL | Request,
		init?: RequestInit,
	) => {
		return auth.handler(new Request(url, init));
	};

	function sessionSetter(headers: Headers) {
		return (context: SuccessContext) => {
			const header = context.response.headers.get("set-cookie");
			if (header) {
				const cookies = parseSetCookieHeader(header || "");
				const signedCookie = cookies.get("better-auth.session_token")?.value;
				headers.set("cookie", `better-auth.session_token=${signedCookie}`);
			}
		};
	}

	const client = createAuthClient({
		...(config?.clientOptions as C extends undefined ? {} : C),
		baseURL: getBaseURL(
			options?.baseURL || "http://localhost:" + (config?.port || 3000),
			options?.basePath || "/api/auth",
		),
		fetchOptions: {
			customFetchImpl,
		},
	});
	return {
		auth,
		client,
		testUser,
		signInWithTestUser,
		signInWithUser,
		cookieSetter: setCookieToHeader,
		customFetchImpl,
		sessionSetter,
		db: await getAdapter(auth.options),
	};
}

```

--------------------------------------------------------------------------------
/docs/components/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------

```typescript
"use client";

import * as React from "react";
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import { cva } from "class-variance-authority";
import { ChevronDownIcon } from "lucide-react";

import { cn } from "@/lib/utils";

function NavigationMenu({
	className,
	children,
	viewport = true,
	...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
	viewport?: boolean;
}) {
	return (
		<NavigationMenuPrimitive.Root
			data-slot="navigation-menu"
			data-viewport={viewport}
			className={cn(
				"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
				className,
			)}
			{...props}
		>
			{children}
			{viewport && <NavigationMenuViewport />}
		</NavigationMenuPrimitive.Root>
	);
}

function NavigationMenuList({
	className,
	...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
	return (
		<NavigationMenuPrimitive.List
			data-slot="navigation-menu-list"
			className={cn(
				"group flex flex-1 list-none items-center justify-center gap-1",
				className,
			)}
			{...props}
		/>
	);
}

function NavigationMenuItem({
	className,
	...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
	return (
		<NavigationMenuPrimitive.Item
			data-slot="navigation-menu-item"
			className={cn("relative", className)}
			{...props}
		/>
	);
}

const navigationMenuTriggerStyle = cva(
	"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1",
);

function NavigationMenuTrigger({
	className,
	children,
	...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
	return (
		<NavigationMenuPrimitive.Trigger
			data-slot="navigation-menu-trigger"
			className={cn(navigationMenuTriggerStyle(), "group", className)}
			{...props}
		>
			{children}{" "}
			<ChevronDownIcon
				className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
				aria-hidden="true"
			/>
		</NavigationMenuPrimitive.Trigger>
	);
}

function NavigationMenuContent({
	className,
	...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
	return (
		<NavigationMenuPrimitive.Content
			data-slot="navigation-menu-content"
			className={cn(
				"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
				"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
				className,
			)}
			{...props}
		/>
	);
}

function NavigationMenuViewport({
	className,
	...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
	return (
		<div
			className={cn(
				"absolute top-full left-0 isolate z-50 flex justify-center",
			)}
		>
			<NavigationMenuPrimitive.Viewport
				data-slot="navigation-menu-viewport"
				className={cn(
					"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
					className,
				)}
				{...props}
			/>
		</div>
	);
}

function NavigationMenuLink({
	className,
	...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
	return (
		<NavigationMenuPrimitive.Link
			data-slot="navigation-menu-link"
			className={cn(
				"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
				className,
			)}
			{...props}
		/>
	);
}

function NavigationMenuIndicator({
	className,
	...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
	return (
		<NavigationMenuPrimitive.Indicator
			data-slot="navigation-menu-indicator"
			className={cn(
				"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
				className,
			)}
			{...props}
		>
			<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
		</NavigationMenuPrimitive.Indicator>
	);
}

export {
	NavigationMenu,
	NavigationMenuList,
	NavigationMenuItem,
	NavigationMenuContent,
	NavigationMenuTrigger,
	NavigationMenuLink,
	NavigationMenuIndicator,
	NavigationMenuViewport,
	navigationMenuTriggerStyle,
};

```

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

```markdown
---
title: Email
description: Learn how to use email with Better Auth.
---

Email is a key part of Better Auth, required for all users regardless of their authentication method. Better Auth provides email and password authentication out of the box, and a lot of utilities to help you manage email verification, password reset, and more.


## Email Verification

Email verification is a security feature that ensures users provide a valid email address. It helps prevent spam and abuse by confirming that the email address belongs to the user. In this guide, you'll get a walk through of how to implement token based email verification in your app.
To use otp based email verification, check out the [OTP Verification](/docs/plugins/email-otp) guide.

### Adding Email Verification to Your App

To enable email verification, you need to pass a function that sends a verification email with a link.

- **sendVerificationEmail**: This function is triggered when email verification starts. It accepts a data object with the following properties:
  - `user`: The user object containing the email address.
  - `url`: The verification URL the user must click to verify their email.
  - `token`: The verification token used to complete the email verification to be used when implementing a custom verification URL.

and a `request` object as the second parameter.

```ts title="auth.ts"
import { betterAuth } from 'better-auth';
import { sendEmail } from './email'; // your email sending function

export const auth = betterAuth({
    emailVerification: {
        sendVerificationEmail: async ({ user, url, token }, request) => {
            await sendEmail({
                to: user.email,
                subject: 'Verify your email address',
                text: `Click the link to verify your email: ${url}`
            })
        }
    }
})
```

### Triggering Email Verification

You can initiate email verification in several ways:

#### 1. During Sign-up

To automatically send a verification email at signup, set `emailVerification.sendOnSignUp` to `true`. 

```ts title="auth.ts"
import { betterAuth } from 'better-auth';

export const auth = betterAuth({
    emailVerification: {
        sendOnSignUp: true
    }
})
```

This sends a verification email when a user signs up. For social logins, email verification status is read from the SSO.

<Callout>
    With `sendOnSignUp` enabled, when the user logs in with an SSO that does not claim the email as verified, Better Auth will dispatch a verification email, but the verification is not required to login even when `requireEmailVerification` is enabled.
</Callout>

#### 2. Require Email Verification

If you enable require email verification, users must verify their email before they can log in. And every time a user tries to sign in, `sendVerificationEmail` is called.

<Callout>
    This only works if you have `sendVerificationEmail` implemented and if the user is trying to sign in with email and password.
</Callout>

```ts title="auth.ts"
export const auth = betterAuth({
    emailAndPassword: {
        requireEmailVerification: true
    }
})
```

if a user tries to sign in without verifying their email, you can handle the error and show a message to the user.

```ts title="auth-client.ts"
await authClient.signIn.email({
    email: "[email protected]",
    password: "password"
}, {
    onError: (ctx) => {
        // Handle the error
        if(ctx.error.status === 403) {
            alert("Please verify your email address")
        }
        //you can also show the original error message
        alert(ctx.error.message)
    }
})
```

#### 3. Manually

You can also manually trigger email verification by calling `sendVerificationEmail`.

```ts
await authClient.sendVerificationEmail({
    email: "[email protected]",
    callbackURL: "/" // The redirect URL after verification
})
```

### Verifying the Email

If the user clicks the provided verification URL, their email is automatically verified, and they are redirected to the `callbackURL`.

For manual verification, you can send the user a custom link with the `token` and call the `verifyEmail` function.

```ts
await authClient.verifyEmail({
    query: {
        token: "" // Pass the token here
    }
})
```

### Auto Sign In After Verification

To sign in the user automatically after they successfully verify their email, set the `autoSignInAfterVerification` option to `true`:

```ts
const auth = betterAuth({
    //...your other options
    emailVerification: {
        autoSignInAfterVerification: true
    }
})
```

### Callback after successful email verification

You can run custom code immediately after a user verifies their email using the `afterEmailVerification` callback. This is useful for any side-effects you want to trigger, like granting access to special features or logging the event.

The `afterEmailVerification` function runs automatically when a user's email is confirmed, receiving the `user` object and `request` details so you can perform actions for that specific user.

Here's how you can set it up:

```ts title="auth.ts"
import { betterAuth } from 'better-auth';

export const auth = betterAuth({
    emailVerification: {
        async afterEmailVerification(user, request) {
            // Your custom logic here, e.g., grant access to premium features
            console.log(`${user.email} has been successfully verified!`);
        }
    }
})
```

## Password Reset Email

Password reset allows users to reset their password if they forget it. Better Auth provides a simple way to implement password reset functionality.

You can enable password reset by passing a function that sends a password reset email with a link.

```ts title="auth.ts"
import { betterAuth } from 'better-auth';
import { sendEmail } from './email'; // your email sending function

export const auth = betterAuth({
    emailAndPassword: {
        enabled: true,
        sendResetPassword: async ({ user, url, token }, request) => {
            await sendEmail({
                to: user.email,
                subject: 'Reset your password',
                text: `Click the link to reset your password: ${url}`
            })
        }
    }
})
```

Check out the [Email and Password](/docs/authentication/email-password#forget-password) guide for more details on how to implement password reset in your app.
Also you can check out the [Otp verification](/docs/plugins/email-otp#reset-password) guide for how to implement password reset with OTP in your app.

```

--------------------------------------------------------------------------------
/docs/components/docs/page.tsx:
--------------------------------------------------------------------------------

```typescript
import type { TableOfContents } from "fumadocs-core/server";
import {
	type AnchorHTMLAttributes,
	forwardRef,
	type HTMLAttributes,
	type ReactNode,
} from "react";
import { type AnchorProviderProps, AnchorProvider } from "fumadocs-core/toc";
import { replaceOrDefault } from "./shared";
import { cn } from "../../lib/utils";
import {
	Footer,
	type FooterProps,
	LastUpdate,
	TocPopoverHeader,
	type BreadcrumbProps,
	PageBody,
	PageArticle,
} from "./page.client";
import {
	Toc,
	TOCItems,
	TocPopoverTrigger,
	TocPopoverContent,
	type TOCProps,
	TOCScrollArea,
} from "./layout/toc";
import { buttonVariants } from "./ui/button";
import { Edit, Text } from "lucide-react";
import { I18nLabel } from "fumadocs-ui/provider";

type TableOfContentOptions = Omit<TOCProps, "items" | "children"> &
	Pick<AnchorProviderProps, "single"> & {
		enabled: boolean;
		component: ReactNode;
	};

type TableOfContentPopoverOptions = Omit<TableOfContentOptions, "single">;

interface EditOnGitHubOptions
	extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href" | "children"> {
	owner: string;
	repo: string;

	/**
	 * SHA or ref (branch or tag) name.
	 *
	 * @defaultValue main
	 */
	sha?: string;

	/**
	 * File path in the repo
	 */
	path: string;
}

interface BreadcrumbOptions extends BreadcrumbProps {
	enabled: boolean;
	component: ReactNode;

	/**
	 * Show the full path to the current page
	 *
	 * @defaultValue false
	 * @deprecated use `includePage` instead
	 */
	full?: boolean;
}

interface FooterOptions extends FooterProps {
	enabled: boolean;
	component: ReactNode;
}

export interface DocsPageProps {
	toc?: TableOfContents;

	/**
	 * Extend the page to fill all available space
	 *
	 * @defaultValue false
	 */
	full?: boolean;

	tableOfContent?: Partial<TableOfContentOptions>;
	tableOfContentPopover?: Partial<TableOfContentPopoverOptions>;

	/**
	 * Replace or disable breadcrumb
	 */
	breadcrumb?: Partial<BreadcrumbOptions>;

	/**
	 * Footer navigation, you can disable it by passing `false`
	 */
	footer?: Partial<FooterOptions>;

	editOnGithub?: EditOnGitHubOptions;
	lastUpdate?: Date | string | number;

	container?: HTMLAttributes<HTMLDivElement>;
	article?: HTMLAttributes<HTMLElement>;
	children: ReactNode;
}

export function DocsPage({
	toc = [],
	full = false,
	tableOfContentPopover: {
		enabled: tocPopoverEnabled,
		component: tocPopoverReplace,
		...tocPopoverOptions
	} = {},
	tableOfContent: {
		enabled: tocEnabled,
		component: tocReplace,
		...tocOptions
	} = {},
	...props
}: DocsPageProps) {
	const isTocRequired =
		toc.length > 0 ||
		tocOptions.footer !== undefined ||
		tocOptions.header !== undefined;

	// disable TOC on full mode, you can still enable it with `enabled` option.
	tocEnabled ??= !full && isTocRequired;

	tocPopoverEnabled ??=
		toc.length > 0 ||
		tocPopoverOptions.header !== undefined ||
		tocPopoverOptions.footer !== undefined;

	return (
		<AnchorProvider toc={toc} single={tocOptions.single}>
			<PageBody
				{...props.container}
				className={cn(props.container?.className)}
				style={
					{
						"--fd-tocnav-height": !tocPopoverEnabled ? "0px" : undefined,
						...props.container?.style,
					} as object
				}
			>
				{replaceOrDefault(
					{ enabled: tocPopoverEnabled, component: tocPopoverReplace },
					<TocPopoverHeader className="h-10">
						<TocPopoverTrigger className="w-full" items={toc} />
						<TocPopoverContent>
							{tocPopoverOptions.header}
							<TOCScrollArea isMenu>
								<TOCItems items={toc} />
							</TOCScrollArea>
							{tocPopoverOptions.footer}
						</TocPopoverContent>
					</TocPopoverHeader>,
					{
						items: toc,
						...tocPopoverOptions,
					},
				)}
				<PageArticle
					{...props.article}
					className={cn(
						full || !tocEnabled ? "max-w-[1120px]" : "max-w-[860px]",
						props.article?.className,
					)}
				>
					{props.children}
					<div role="none" className="flex-1" />
					<div className="flex flex-row flex-wrap items-center justify-between gap-4 empty:hidden">
						{props.editOnGithub ? (
							<EditOnGitHub {...props.editOnGithub} />
						) : null}
						{props.lastUpdate ? (
							<LastUpdate date={new Date(props.lastUpdate)} />
						) : null}
					</div>
					{replaceOrDefault(
						props.footer,
						<Footer items={props.footer?.items} />,
					)}
				</PageArticle>
			</PageBody>
			{replaceOrDefault(
				{ enabled: tocEnabled, component: tocReplace },
				<Toc>
					{tocOptions.header}
					<h3 className="inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground">
						<Text className="size-4" />
						<I18nLabel label="toc" />
					</h3>
					<TOCScrollArea>
						<TOCItems items={toc} />
					</TOCScrollArea>
					{tocOptions.footer}
				</Toc>,
				{
					items: toc,
					...tocOptions,
				},
			)}
		</AnchorProvider>
	);
}

function EditOnGitHub({
	owner,
	repo,
	sha,
	path,
	...props
}: EditOnGitHubOptions) {
	const href = `https://github.com/${owner}/${repo}/blob/${sha}/${path.startsWith("/") ? path.slice(1) : path}`;

	return (
		<a
			href={href}
			target="_blank"
			rel="noreferrer noopener"
			{...props}
			className={cn(
				buttonVariants({
					color: "secondary",
					className: "gap-1.5 text-fd-muted-foreground",
				}),
				props.className,
			)}
		>
			<Edit className="size-3.5" />
			<I18nLabel label="editOnGithub" />
		</a>
	);
}

/**
 * Add typography styles
 */
export const DocsBody = forwardRef<
	HTMLDivElement,
	HTMLAttributes<HTMLDivElement>
>((props, ref) => (
	<div ref={ref} {...props} className={cn("prose", props.className)}>
		{props.children}
	</div>
));

DocsBody.displayName = "DocsBody";

export const DocsDescription = forwardRef<
	HTMLParagraphElement,
	HTMLAttributes<HTMLParagraphElement>
>((props, ref) => {
	// don't render if no description provided
	if (props.children === undefined) return null;

	return (
		<p
			ref={ref}
			{...props}
			className={cn("mb-8 text-lg text-fd-muted-foreground", props.className)}
		>
			{props.children}
		</p>
	);
});

DocsDescription.displayName = "DocsDescription";

export const DocsTitle = forwardRef<
	HTMLHeadingElement,
	HTMLAttributes<HTMLHeadingElement>
>((props, ref) => {
	return (
		<h1
			ref={ref}
			{...props}
			className={cn("text-3xl font-semibold", props.className)}
		>
			{props.children}
		</h1>
	);
});

DocsTitle.displayName = "DocsTitle";

/**
 * For separate MDX page
 */
export function withArticle({ children }: { children: ReactNode }): ReactNode {
	return (
		<main className="container py-12">
			<article className="prose">{children}</article>
		</main>
	);
}

```

--------------------------------------------------------------------------------
/demo/nextjs/components/ui/context-menu.tsx:
--------------------------------------------------------------------------------

```typescript
"use client";

import * as React from "react";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
import {
	CheckIcon,
	ChevronRightIcon,
	DotFilledIcon,
} from "@radix-ui/react-icons";

import { cn } from "@/lib/utils";

const ContextMenu = ContextMenuPrimitive.Root;

const ContextMenuTrigger = ContextMenuPrimitive.Trigger;

const ContextMenuGroup = ContextMenuPrimitive.Group;

const ContextMenuPortal = ContextMenuPrimitive.Portal;

const ContextMenuSub = ContextMenuPrimitive.Sub;

const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;

const ContextMenuSubTrigger = ({
	ref,
	className,
	inset,
	children,
	...props
}) => (
	<ContextMenuPrimitive.SubTrigger
		ref={ref}
		className={cn(
			"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
			inset && "pl-8",
			className,
		)}
		{...props}
	>
		{children}
		<ChevronRightIcon className="ml-auto h-4 w-4" />
	</ContextMenuPrimitive.SubTrigger>
);
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;

const ContextMenuSubContent = ({
	ref,
	className,
	...props
}: React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent> & {
	ref: React.RefObject<
		React.ElementRef<typeof ContextMenuPrimitive.SubContent>
	>;
}) => (
	<ContextMenuPrimitive.SubContent
		ref={ref}
		className={cn(
			"z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
			className,
		)}
		{...props}
	/>
);
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;

const ContextMenuContent = ({
	ref,
	className,
	...props
}: React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content> & {
	ref: React.RefObject<React.ElementRef<typeof ContextMenuPrimitive.Content>>;
}) => (
	<ContextMenuPrimitive.Portal>
		<ContextMenuPrimitive.Content
			ref={ref}
			className={cn(
				"z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
				className,
			)}
			{...props}
		/>
	</ContextMenuPrimitive.Portal>
);
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;

const ContextMenuItem = ({ ref, className, inset, ...props }) => (
	<ContextMenuPrimitive.Item
		ref={ref}
		className={cn(
			"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
			inset && "pl-8",
			className,
		)}
		{...props}
	/>
);
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;

const ContextMenuCheckboxItem = ({
	ref,
	className,
	children,
	checked,
	...props
}: React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem> & {
	ref: React.RefObject<
		React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>
	>;
}) => (
	<ContextMenuPrimitive.CheckboxItem
		ref={ref}
		className={cn(
			"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
			className,
		)}
		checked={checked}
		{...props}
	>
		<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
			<ContextMenuPrimitive.ItemIndicator>
				<CheckIcon className="h-4 w-4" />
			</ContextMenuPrimitive.ItemIndicator>
		</span>
		{children}
	</ContextMenuPrimitive.CheckboxItem>
);
ContextMenuCheckboxItem.displayName =
	ContextMenuPrimitive.CheckboxItem.displayName;

const ContextMenuRadioItem = ({
	ref,
	className,
	children,
	...props
}: React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem> & {
	ref: React.RefObject<React.ElementRef<typeof ContextMenuPrimitive.RadioItem>>;
}) => (
	<ContextMenuPrimitive.RadioItem
		ref={ref}
		className={cn(
			"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
			className,
		)}
		{...props}
	>
		<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
			<ContextMenuPrimitive.ItemIndicator>
				<DotFilledIcon className="h-4 w-4 fill-current" />
			</ContextMenuPrimitive.ItemIndicator>
		</span>
		{children}
	</ContextMenuPrimitive.RadioItem>
);
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;

const ContextMenuLabel = ({ ref, className, inset, ...props }) => (
	<ContextMenuPrimitive.Label
		ref={ref}
		className={cn(
			"px-2 py-1.5 text-sm font-semibold text-foreground",
			inset && "pl-8",
			className,
		)}
		{...props}
	/>
);
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;

const ContextMenuSeparator = ({
	ref,
	className,
	...props
}: React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator> & {
	ref: React.RefObject<React.ElementRef<typeof ContextMenuPrimitive.Separator>>;
}) => (
	<ContextMenuPrimitive.Separator
		ref={ref}
		className={cn("-mx-1 my-1 h-px bg-border", className)}
		{...props}
	/>
);
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;

const ContextMenuShortcut = ({
	className,
	...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
	return (
		<span
			className={cn(
				"ml-auto text-xs tracking-widest text-muted-foreground",
				className,
			)}
			{...props}
		/>
	);
};
ContextMenuShortcut.displayName = "ContextMenuShortcut";

export {
	ContextMenu,
	ContextMenuTrigger,
	ContextMenuContent,
	ContextMenuItem,
	ContextMenuCheckboxItem,
	ContextMenuRadioItem,
	ContextMenuLabel,
	ContextMenuSeparator,
	ContextMenuShortcut,
	ContextMenuGroup,
	ContextMenuPortal,
	ContextMenuSub,
	ContextMenuSubContent,
	ContextMenuSubTrigger,
	ContextMenuRadioGroup,
};

```

--------------------------------------------------------------------------------
/packages/better-auth/src/adapters/memory-adapter/memory-adapter.ts:
--------------------------------------------------------------------------------

```typescript
import { logger } from "@better-auth/core/env";
import { createAdapterFactory } from "../adapter-factory";
import type { BetterAuthOptions } from "@better-auth/core";
import type {
	DBAdapterDebugLogOption,
	CleanedWhere,
} from "@better-auth/core/db/adapter";

export interface MemoryDB {
	[key: string]: any[];
}

export interface MemoryAdapterConfig {
	debugLogs?: DBAdapterDebugLogOption;
}

export const memoryAdapter = (db: MemoryDB, config?: MemoryAdapterConfig) => {
	let lazyOptions: BetterAuthOptions | null = null;
	let adapterCreator = createAdapterFactory({
		config: {
			adapterId: "memory",
			adapterName: "Memory Adapter",
			usePlural: false,
			debugLogs: config?.debugLogs || false,
			customTransformInput(props) {
				if (
					props.options.advanced?.database?.useNumberId &&
					props.field === "id" &&
					props.action === "create"
				) {
					return db[props.model]!.length + 1;
				}
				return props.data;
			},
			transaction: async (cb) => {
				let clone = structuredClone(db);
				try {
					const r = await cb(adapterCreator(lazyOptions!));
					return r;
				} catch (error) {
					// Rollback changes
					Object.keys(db).forEach((key) => {
						db[key] = clone[key]!;
					});
					throw error;
				}
			},
		},
		adapter: ({ getFieldName, options, debugLog }) => {
			function convertWhereClause(where: CleanedWhere[], model: string) {
				const table = db[model];
				if (!table) {
					logger.error(
						`[MemoryAdapter] Model ${model} not found in the DB`,
						Object.keys(db),
					);
					throw new Error(`Model ${model} not found`);
				}

				const evalClause = (record: any, clause: CleanedWhere): boolean => {
					const { field, value, operator } = clause;
					switch (operator) {
						case "in":
							if (!Array.isArray(value)) {
								throw new Error("Value must be an array");
							}
							// @ts-expect-error
							return value.includes(record[field]);
						case "not_in":
							if (!Array.isArray(value)) {
								throw new Error("Value must be an array");
							}
							// @ts-expect-error
							return !value.includes(record[field]);
						case "contains":
							return record[field].includes(value);
						case "starts_with":
							return record[field].startsWith(value);
						case "ends_with":
							return record[field].endsWith(value);
						case "ne":
							return record[field] !== value;
						case "gt":
							return value != null && Boolean(record[field] > value);
						case "gte":
							return value != null && Boolean(record[field] >= value);
						case "lt":
							return value != null && Boolean(record[field] < value);
						case "lte":
							return value != null && Boolean(record[field] <= value);
						default:
							return record[field] === value;
					}
				};

				return table.filter((record: any) => {
					if (!where.length || where.length === 0) {
						return true;
					}

					let result = evalClause(record, where[0]!);
					for (const clause of where) {
						const clauseResult = evalClause(record, clause);

						if (clause.connector === "OR") {
							result = result || clauseResult;
						} else {
							result = result && clauseResult;
						}
					}

					return result;
				});
			}
			return {
				create: async ({ model, data }) => {
					if (options.advanced?.database?.useNumberId) {
						// @ts-expect-error
						data.id = db[model]!.length + 1;
					}
					if (!db[model]) {
						db[model] = [];
					}
					db[model]!.push(data);
					return data;
				},
				findOne: async ({ model, where }) => {
					const res = convertWhereClause(where, model);
					const record = res[0] || null;
					return record;
				},
				findMany: async ({ model, where, sortBy, limit, offset }) => {
					let table = db[model];
					if (where) {
						table = convertWhereClause(where, model);
					}
					if (sortBy) {
						table = table!.sort((a, b) => {
							const field = getFieldName({ model, field: sortBy.field });
							const aValue = a[field];
							const bValue = b[field];

							let comparison = 0;

							// Handle null/undefined values
							if (aValue == null && bValue == null) {
								comparison = 0;
							} else if (aValue == null) {
								comparison = -1;
							} else if (bValue == null) {
								comparison = 1;
							}
							// Handle string comparison
							else if (
								typeof aValue === "string" &&
								typeof bValue === "string"
							) {
								comparison = aValue.localeCompare(bValue);
							}
							// Handle date comparison
							else if (aValue instanceof Date && bValue instanceof Date) {
								comparison = aValue.getTime() - bValue.getTime();
							}
							// Handle numeric comparison
							else if (
								typeof aValue === "number" &&
								typeof bValue === "number"
							) {
								comparison = aValue - bValue;
							}
							// Handle boolean comparison
							else if (
								typeof aValue === "boolean" &&
								typeof bValue === "boolean"
							) {
								comparison = aValue === bValue ? 0 : aValue ? 1 : -1;
							}
							// Fallback to string comparison
							else {
								comparison = String(aValue).localeCompare(String(bValue));
							}

							return sortBy.direction === "asc" ? comparison : -comparison;
						});
					}
					if (offset !== undefined) {
						table = table!.slice(offset);
					}
					if (limit !== undefined) {
						table = table!.slice(0, limit);
					}
					return table || [];
				},
				count: async ({ model, where }) => {
					if (where) {
						const filteredRecords = convertWhereClause(where, model);
						return filteredRecords.length;
					}
					return db[model]!.length;
				},
				update: async ({ model, where, update }) => {
					const res = convertWhereClause(where, model);
					res.forEach((record) => {
						Object.assign(record, update);
					});
					return res[0] || null;
				},
				delete: async ({ model, where }) => {
					const table = db[model]!;
					const res = convertWhereClause(where, model);
					db[model] = table.filter((record) => !res.includes(record));
				},
				deleteMany: async ({ model, where }) => {
					const table = db[model]!;
					const res = convertWhereClause(where, model);
					let count = 0;
					db[model] = table.filter((record) => {
						if (res.includes(record)) {
							count++;
							return false;
						}
						return !res.includes(record);
					});
					return count;
				},
				updateMany({ model, where, update }) {
					const res = convertWhereClause(where, model);
					res.forEach((record) => {
						Object.assign(record, update);
					});
					return res[0] || null;
				},
			};
		},
	});
	return (options: BetterAuthOptions) => {
		lazyOptions = options;
		return adapterCreator(options);
	};
};

```

--------------------------------------------------------------------------------
/demo/nextjs/components/blocks/pricing.tsx:
--------------------------------------------------------------------------------

```typescript
"use client";

import { Button, buttonVariants } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";

import { cn } from "@/lib/utils";
import { motion } from "framer-motion";
import { Star } from "lucide-react";
import { useState, useRef, useEffect } from "react";
import confetti from "canvas-confetti";
import NumberFlow from "@number-flow/react";
import { CheckIcon } from "@radix-ui/react-icons";
import { client } from "@/lib/auth-client";

function useMediaQuery(query: string) {
	const [matches, setMatches] = useState(false);

	useEffect(() => {
		const media = window.matchMedia(query);
		if (media.matches !== matches) {
			setMatches(media.matches);
		}

		const listener = () => setMatches(media.matches);
		media.addListener(listener);

		return () => media.removeListener(listener);
	}, [query]);

	return matches;
}

interface PricingPlan {
	name: string;
	price: string;
	yearlyPrice: string;
	period: string;
	features: string[];
	description: string;
	buttonText: string;
	href: string;
	isPopular: boolean;
}

interface PricingProps {
	plans: PricingPlan[];
	title?: string;
	description?: string;
}

export function Pricing({
	plans,
	title = "Simple, Transparent Pricing",
	description = "Choose the plan that works for you",
}: PricingProps) {
	const [isMonthly, setIsMonthly] = useState(true);
	const isDesktop = useMediaQuery("(min-width: 768px)");
	const switchRef = useRef<HTMLButtonElement>(null);

	const handleToggle = (checked: boolean) => {
		setIsMonthly(!checked);
		if (checked && switchRef.current) {
			const rect = switchRef.current.getBoundingClientRect();
			const x = rect.left + rect.width / 2;
			const y = rect.top + rect.height / 2;

			confetti({
				particleCount: 50,
				spread: 60,
				origin: {
					x: x / window.innerWidth,
					y: y / window.innerHeight,
				},
				colors: [
					"hsl(var(--primary))",
					"hsl(var(--accent))",
					"hsl(var(--secondary))",
					"hsl(var(--muted))",
				],
				ticks: 200,
				gravity: 1.2,
				decay: 0.94,
				startVelocity: 30,
				shapes: ["circle"],
			});
		}
	};

	return (
		<div className="container py-4">
			<div className="text-center space-y-4 mb-3">
				<h2 className="text-2xl font-bold tracking-tight sm:text-3xl">
					{title}
				</h2>
				<p className="text-muted-foreground  whitespace-pre-line">
					{description}
				</p>
			</div>

			<div className="flex justify-center mb-10">
				<label className="relative inline-flex items-center cursor-pointer">
					<Label>
						<Switch
							ref={switchRef as any}
							checked={!isMonthly}
							onCheckedChange={handleToggle}
							className="relative"
						/>
					</Label>
				</label>
				<span className="ml-2 font-semibold">
					Annual billing <span className="text-primary">(Save 20%)</span>
				</span>
			</div>

			<div className="grid grid-cols-1 md:grid-cols-3 sm:2 gap-4">
				{plans.map((plan, index) => (
					<motion.div
						key={index}
						initial={{ y: 50, opacity: 1 }}
						whileInView={
							isDesktop
								? {
										y: plan.isPopular ? -20 : 0,
										opacity: 1,
										x: index === 2 ? -30 : index === 0 ? 30 : 0,
										scale: index === 0 || index === 2 ? 0.94 : 1.0,
									}
								: {}
						}
						viewport={{ once: true }}
						transition={{
							duration: 1.6,
							type: "spring",
							stiffness: 100,
							damping: 30,
							delay: 0.4,
							opacity: { duration: 0.5 },
						}}
						className={cn(
							`rounded-sm border p-6 bg-background text-center lg:flex lg:flex-col lg:justify-center relative`,
							plan.isPopular ? "border-border border-2" : "border-border",
							"flex flex-col",
							!plan.isPopular && "mt-5",
							index === 0 || index === 2
								? "z-0 transform translate-x-0 translate-y-0 -translate-z-[50px] rotate-y-10"
								: "z-10",
							index === 0 && "origin-right",
							index === 2 && "origin-left",
						)}
					>
						{plan.isPopular && (
							<div className="absolute top-0 right-0 bg-primary py-0.5 px-2 rounded-bl-sm rounded-tr-sm flex items-center">
								<Star className="text-primary-foreground h-4 w-4 fill-current" />
								<span className="text-primary-foreground ml-1 font-sans font-semibold">
									Popular
								</span>
							</div>
						)}
						<div className="flex-1 flex flex-col">
							<p className="text-base font-semibold text-muted-foreground mt-2">
								{plan.name}
							</p>
							<div className="mt-6 flex items-center justify-center gap-x-2">
								<span className="text-5xl font-bold tracking-tight text-foreground">
									<NumberFlow
										value={
											isMonthly ? Number(plan.price) : Number(plan.yearlyPrice)
										}
										format={{
											style: "currency",
											currency: "USD",
											minimumFractionDigits: 0,
											maximumFractionDigits: 0,
										}}
										transformTiming={{
											duration: 500,
											easing: "ease-out",
										}}
										willChange
										className="font-variant-numeric: tabular-nums"
									/>
								</span>
								{plan.period !== "Next 3 months" && (
									<span className="text-sm font-semibold leading-6 tracking-wide text-muted-foreground">
										/ {plan.period}
									</span>
								)}
							</div>

							<p className="text-xs leading-5 text-muted-foreground">
								{isMonthly ? "billed monthly" : "billed annually"}
							</p>

							<ul className="mt-5 gap-2 flex flex-col">
								{plan.features.map((feature, idx) => (
									<li key={idx} className="flex items-start gap-2">
										<CheckIcon className="h-4 w-4 text-primary mt-1 shrink-0" />
										<span className="text-left">{feature}</span>
									</li>
								))}
							</ul>

							<hr className="w-full my-4" />
							<Button
								onClick={async () => {
									await client.subscription.upgrade({
										plan: plan.name.toLowerCase(),
										successUrl: "/dashboard",
									});
								}}
								className={cn(
									buttonVariants({
										variant: "outline",
									}),
									"group relative w-full gap-2 overflow-hidden text-lg font-semibold tracking-tighter",
									"transform-gpu ring-offset-current transition-all duration-300 ease-out hover:ring-2 hover:ring-primary hover:ring-offset-1 hover:bg-primary hover:text-primary-foreground",
									plan.isPopular
										? "bg-primary text-primary-foreground"
										: "bg-background text-foreground",
								)}
							>
								{plan.buttonText}
							</Button>
							<p className="mt-6 text-xs leading-5 text-muted-foreground">
								{plan.description}
							</p>
						</div>
					</motion.div>
				))}
			</div>
		</div>
	);
}

```

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

```typescript
import { betterFetch } from "@better-fetch/fetch";
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
import { BetterAuthError } from "../error";
import type { OAuthProvider, ProviderOptions } from "../oauth2";
import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
import { logger } from "../env";
import { refreshAccessToken } from "../oauth2";
import { APIError } from "better-call";

export interface CognitoProfile {
	sub: string;
	email: string;
	email_verified: boolean;
	name: string;
	given_name?: string;
	family_name?: string;
	picture?: string;
	username?: string;
	locale?: string;
	phone_number?: string;
	phone_number_verified?: boolean;
	aud: string;
	iss: string;
	exp: number;
	iat: number;
	// Custom attributes from Cognito can be added here
	[key: string]: any;
}

export interface CognitoOptions extends ProviderOptions<CognitoProfile> {
	clientId: string;
	/**
	 * The Cognito domain (e.g., "your-app.auth.us-east-1.amazoncognito.com")
	 */
	domain: string;
	/**
	 * AWS region where User Pool is hosted (e.g., "us-east-1")
	 */
	region: string;
	userPoolId: string;
	requireClientSecret?: boolean;
}

export const cognito = (options: CognitoOptions) => {
	if (!options.domain || !options.region || !options.userPoolId) {
		logger.error(
			"Domain, region and userPoolId are required for Amazon Cognito. Make sure to provide them in the options.",
		);
		throw new BetterAuthError("DOMAIN_AND_REGION_REQUIRED");
	}

	const cleanDomain = options.domain.replace(/^https?:\/\//, "");
	const authorizationEndpoint = `https://${cleanDomain}/oauth2/authorize`;
	const tokenEndpoint = `https://${cleanDomain}/oauth2/token`;
	const userInfoEndpoint = `https://${cleanDomain}/oauth2/userinfo`;

	return {
		id: "cognito",
		name: "Cognito",
		async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
			if (!options.clientId) {
				logger.error(
					"ClientId is required for Amazon Cognito. Make sure to provide them in the options.",
				);
				throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
			}

			if (options.requireClientSecret && !options.clientSecret) {
				logger.error(
					"Client Secret is required when requireClientSecret is true. Make sure to provide it in the options.",
				);
				throw new BetterAuthError("CLIENT_SECRET_REQUIRED");
			}
			const _scopes = options.disableDefaultScope
				? []
				: ["openid", "profile", "email"];
			options.scope && _scopes.push(...options.scope);
			scopes && _scopes.push(...scopes);

			const url = await createAuthorizationURL({
				id: "cognito",
				options: {
					...options,
				},
				authorizationEndpoint,
				scopes: _scopes,
				state,
				codeVerifier,
				redirectURI,
				prompt: options.prompt,
			});
			return url;
		},

		validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
			return validateAuthorizationCode({
				code,
				codeVerifier,
				redirectURI,
				options,
				tokenEndpoint,
			});
		},

		refreshAccessToken: options.refreshAccessToken
			? options.refreshAccessToken
			: async (refreshToken) => {
					return refreshAccessToken({
						refreshToken,
						options: {
							clientId: options.clientId,
							clientKey: options.clientKey,
							clientSecret: options.clientSecret,
						},
						tokenEndpoint,
					});
				},

		async verifyIdToken(token, nonce) {
			if (options.disableIdTokenSignIn) {
				return false;
			}
			if (options.verifyIdToken) {
				return options.verifyIdToken(token, nonce);
			}

			try {
				const decodedHeader = decodeProtectedHeader(token);
				const { kid, alg: jwtAlg } = decodedHeader;
				if (!kid || !jwtAlg) return false;

				const publicKey = await getCognitoPublicKey(
					kid,
					options.region,
					options.userPoolId,
				);
				const expectedIssuer = `https://cognito-idp.${options.region}.amazonaws.com/${options.userPoolId}`;

				const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
					algorithms: [jwtAlg],
					issuer: expectedIssuer,
					audience: options.clientId,
					maxTokenAge: "1h",
				});

				if (nonce && jwtClaims.nonce !== nonce) {
					return false;
				}
				return true;
			} catch (error) {
				logger.error("Failed to verify ID token:", error);
				return false;
			}
		},

		async getUserInfo(token) {
			if (options.getUserInfo) {
				return options.getUserInfo(token);
			}

			if (token.idToken) {
				try {
					const profile = decodeJwt<CognitoProfile>(token.idToken);
					if (!profile) {
						return null;
					}
					const name =
						profile.name ||
						profile.given_name ||
						profile.username ||
						profile.email;
					const enrichedProfile = {
						...profile,
						name,
					};
					const userMap = await options.mapProfileToUser?.(enrichedProfile);

					return {
						user: {
							id: profile.sub,
							name: enrichedProfile.name,
							email: profile.email,
							image: profile.picture,
							emailVerified: profile.email_verified,
							...userMap,
						},
						data: enrichedProfile,
					};
				} catch (error) {
					logger.error("Failed to decode ID token:", error);
				}
			}

			if (token.accessToken) {
				try {
					const { data: userInfo } = await betterFetch<CognitoProfile>(
						userInfoEndpoint,
						{
							headers: {
								Authorization: `Bearer ${token.accessToken}`,
							},
						},
					);

					if (userInfo) {
						const userMap = await options.mapProfileToUser?.(userInfo);
						return {
							user: {
								id: userInfo.sub,
								name: userInfo.name || userInfo.given_name || userInfo.username,
								email: userInfo.email,
								image: userInfo.picture,
								emailVerified: userInfo.email_verified,
								...userMap,
							},
							data: userInfo,
						};
					}
				} catch (error) {
					logger.error("Failed to fetch user info from Cognito:", error);
				}
			}

			return null;
		},

		options,
	} satisfies OAuthProvider<CognitoProfile>;
};

export const getCognitoPublicKey = async (
	kid: string,
	region: string,
	userPoolId: string,
) => {
	const COGNITO_JWKS_URI = `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`;

	try {
		const { data } = await betterFetch<{
			keys: Array<{
				kid: string;
				alg: string;
				kty: string;
				use: string;
				n: string;
				e: string;
			}>;
		}>(COGNITO_JWKS_URI);

		if (!data?.keys) {
			throw new APIError("BAD_REQUEST", {
				message: "Keys not found",
			});
		}

		const jwk = data.keys.find((key) => key.kid === kid);
		if (!jwk) {
			throw new Error(`JWK with kid ${kid} not found`);
		}

		return await importJWK(jwk, jwk.alg);
	} catch (error) {
		logger.error("Failed to fetch Cognito public key:", error);
		throw error;
	}
};

```

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

```typescript
import type { InferOptionSchema } from "../../types";
import type { Statements } from "../access";
import type { apiKeySchema } from "./schema";
import type {
	GenericEndpointContext,
	HookEndpointContext,
} from "@better-auth/core";
export interface ApiKeyOptions {
	/**
	 * The header name to check for API key
	 * @default "x-api-key"
	 */
	apiKeyHeaders?: string | string[];
	/**
	 * Disable hashing of the API key.
	 *
	 * ⚠️ Security Warning: It's strongly recommended to not disable hashing.
	 * Storing API keys in plaintext makes them vulnerable to database breaches, potentially exposing all your users' API keys.
	 *
	 * @default false
	 */
	disableKeyHashing?: boolean;
	/**
	 * The function to get the API key from the context
	 */
	customAPIKeyGetter?: (ctx: HookEndpointContext) => string | null;
	/**
	 * A custom function to validate the API key
	 */
	customAPIKeyValidator?: (options: {
		ctx: GenericEndpointContext;
		key: string;
	}) => boolean | Promise<boolean>;
	/**
	 * custom key generation function
	 */
	customKeyGenerator?: (options: {
		/**
		 * The length of the API key to generate
		 */
		length: number;
		/**
		 * The prefix of the API key to generate
		 */
		prefix: string | undefined;
	}) => string | Promise<string>;
	/**
	 * The configuration for storing the starting characters of the API key in the database.
	 *
	 * Useful if you want to display the starting characters of an API key in the UI.
	 */
	startingCharactersConfig?: {
		/**
		 * Whether to store the starting characters in the database. If false, we will set `start` to `null`.
		 *
		 * @default true
		 */
		shouldStore?: boolean;
		/**
		 * The length of the starting characters to store in the database.
		 *
		 * This includes the prefix length.
		 *
		 * @default 6
		 */
		charactersLength?: number;
	};
	/**
	 * The length of the API key. Longer is better. Default is 64. (Doesn't include the prefix length)
	 * @default 64
	 */
	defaultKeyLength?: number;
	/**
	 * The prefix of the API key.
	 *
	 * Note: We recommend you append an underscore to the prefix to make the prefix more identifiable. (eg `hello_`)
	 */
	defaultPrefix?: string;
	/**
	 * The maximum length of the prefix.
	 *
	 * @default 32
	 */
	maximumPrefixLength?: number;
	/**
	 * Whether to require a name for the API key.
	 *
	 * @default false
	 */
	requireName?: boolean;
	/**
	 * The minimum length of the prefix.
	 *
	 * @default 1
	 */
	minimumPrefixLength?: number;
	/**
	 * The maximum length of the name.
	 *
	 * @default 32
	 */
	maximumNameLength?: number;
	/**
	 * The minimum length of the name.
	 *
	 * @default 1
	 */
	minimumNameLength?: number;
	/**
	 * Whether to enable metadata for an API key.
	 *
	 * @default false
	 */
	enableMetadata?: boolean;
	/**
	 * Customize the key expiration.
	 */
	keyExpiration?: {
		/**
		 * The default expires time in milliseconds.
		 *
		 * If `null`, then there will be no expiration time.
		 *
		 * @default null
		 */
		defaultExpiresIn?: number | null;
		/**
		 * Whether to disable the expires time passed from the client.
		 *
		 * If `true`, the expires time will be based on the default values.
		 *
		 * @default false
		 */
		disableCustomExpiresTime?: boolean;
		/**
		 * The minimum expiresIn value allowed to be set from the client. in days.
		 *
		 * @default 1
		 */
		minExpiresIn?: number;
		/**
		 * The maximum expiresIn value allowed to be set from the client. in days.
		 *
		 * @default 365
		 */
		maxExpiresIn?: number;
	};
	/**
	 * Default rate limiting options.
	 */
	rateLimit?: {
		/**
		 * Whether to enable rate limiting.
		 *
		 * @default true
		 */
		enabled?: boolean;
		/**
		 * The duration in milliseconds where each request is counted.
		 *
		 * Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset.
		 *
		 * @default 1000 * 60 * 60 * 24 // 1 day
		 */
		timeWindow?: number;
		/**
		 * Maximum amount of requests allowed within a window
		 *
		 * Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset.
		 *
		 * @default 10 // 10 requests per day
		 */
		maxRequests?: number;
	};
	/**
	 * custom schema for the API key plugin
	 */
	schema?: InferOptionSchema<ReturnType<typeof apiKeySchema>>;
	/**
	 * An API Key can represent a valid session, so we automatically mock a session for the user if we find a valid API key in the request headers.
	 *
	 * ⚠︎ This is not recommended for production use, as it can lead to security issues.
	 * @default false
	 */
	enableSessionForAPIKeys?: boolean;
	/**
	 * Permissions for the API key.
	 */
	permissions?: {
		/**
		 * The default permissions for the API key.
		 */
		defaultPermissions?:
			| Statements
			| ((
					userId: string,
					ctx: GenericEndpointContext,
			  ) => Statements | Promise<Statements>);
	};
}

export type ApiKey = {
	/**
	 * ID
	 */
	id: string;
	/**
	 * The name of the key
	 */
	name: string | null;
	/**
	 * Shows the first few characters of the API key, including the prefix.
	 * This allows you to show those few characters in the UI to make it easier for users to identify the API key.
	 */
	start: string | null;
	/**
	 * The API Key prefix. Stored as plain text.
	 */
	prefix: string | null;
	/**
	 * The hashed API key value
	 */
	key: string;
	/**
	 * The owner of the user id
	 */
	userId: string;
	/**
	 * The interval in milliseconds between refills of the `remaining` count
	 *
	 * @example 3600000 // refill every hour (3600000ms = 1h)
	 */
	refillInterval: number | null;
	/**
	 * The amount to refill
	 */
	refillAmount: number | null;
	/**
	 * The last refill date
	 */
	lastRefillAt: Date | null;
	/**
	 * Sets if key is enabled or disabled
	 *
	 * @default true
	 */
	enabled: boolean;
	/**
	 * Whether the key has rate limiting enabled.
	 */
	rateLimitEnabled: boolean;
	/**
	 * The duration in milliseconds
	 */
	rateLimitTimeWindow: number | null;
	/**
	 * Maximum amount of requests allowed within a window
	 */
	rateLimitMax: number | null;
	/**
	 * The number of requests made within the rate limit time window
	 */
	requestCount: number;
	/**
	 * Remaining requests (every time API key is used this should updated and should be updated on refill as well)
	 */
	remaining: number | null;
	/**
	 * When last request occurred
	 */
	lastRequest: Date | null;
	/**
	 * Expiry date of a key
	 */
	expiresAt: Date | null;
	/**
	 * created at
	 */
	createdAt: Date;
	/**
	 * updated at
	 */
	updatedAt: Date;
	/**
	 * Extra metadata about the apiKey
	 */
	metadata: Record<string, any> | null;
	/**
	 * Permissions for the API key
	 */
	permissions?: {
		[key: string]: string[];
	} | null;
};

```

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

```typescript
import { betterFetch } from "@better-fetch/fetch";
import { BetterAuthError } from "../error";
import type { OAuthProvider, ProviderOptions } from "../oauth2";
import { createAuthorizationURL } from "../oauth2";
import { logger } from "../env";
import { decodeJwt } from "jose";
import { base64 } from "@better-auth/utils/base64";

export interface PayPalProfile {
	user_id: string;
	name: string;
	given_name: string;
	family_name: string;
	middle_name?: string;
	picture?: string;
	email: string;
	email_verified: boolean;
	gender?: string;
	birthdate?: string;
	zoneinfo?: string;
	locale?: string;
	phone_number?: string;
	address?: {
		street_address?: string;
		locality?: string;
		region?: string;
		postal_code?: string;
		country?: string;
	};
	verified_account?: boolean;
	account_type?: string;
	age_range?: string;
	payer_id?: string;
}

export interface PayPalTokenResponse {
	scope?: string;
	access_token: string;
	refresh_token?: string;
	token_type: "Bearer";
	id_token?: string;
	expires_in: number;
	nonce?: string;
}

export interface PayPalOptions extends ProviderOptions<PayPalProfile> {
	clientId: string;
	/**
	 * PayPal environment - 'sandbox' for testing, 'live' for production
	 * @default 'sandbox'
	 */
	environment?: "sandbox" | "live";
	/**
	 * Whether to request shipping address information
	 * @default false
	 */
	requestShippingAddress?: boolean;
}

export const paypal = (options: PayPalOptions) => {
	const environment = options.environment || "sandbox";
	const isSandbox = environment === "sandbox";

	const authorizationEndpoint = isSandbox
		? "https://www.sandbox.paypal.com/signin/authorize"
		: "https://www.paypal.com/signin/authorize";

	const tokenEndpoint = isSandbox
		? "https://api-m.sandbox.paypal.com/v1/oauth2/token"
		: "https://api-m.paypal.com/v1/oauth2/token";

	const userInfoEndpoint = isSandbox
		? "https://api-m.sandbox.paypal.com/v1/identity/oauth2/userinfo"
		: "https://api-m.paypal.com/v1/identity/oauth2/userinfo";

	return {
		id: "paypal",
		name: "PayPal",
		async createAuthorizationURL({ state, codeVerifier, redirectURI }) {
			if (!options.clientId || !options.clientSecret) {
				logger.error(
					"Client Id and Client Secret is required for PayPal. Make sure to provide them in the options.",
				);
				throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
			}

			/**
			 * Log in with PayPal doesn't use traditional OAuth2 scopes
			 * Instead, permissions are configured in the PayPal Developer Dashboard
			 * We don't pass any scopes to avoid "invalid scope" errors
			 **/

			const _scopes: string[] = [];

			const url = await createAuthorizationURL({
				id: "paypal",
				options,
				authorizationEndpoint,
				scopes: _scopes,
				state,
				codeVerifier,
				redirectURI,
				prompt: options.prompt,
			});
			return url;
		},

		validateAuthorizationCode: async ({ code, redirectURI }) => {
			/**
			 * PayPal requires Basic Auth for token exchange
			 **/

			const credentials = base64.encode(
				`${options.clientId}:${options.clientSecret}`,
			);

			try {
				const response = await betterFetch(tokenEndpoint, {
					method: "POST",
					headers: {
						Authorization: `Basic ${credentials}`,
						Accept: "application/json",
						"Accept-Language": "en_US",
						"Content-Type": "application/x-www-form-urlencoded",
					},
					body: new URLSearchParams({
						grant_type: "authorization_code",
						code: code,
						redirect_uri: redirectURI,
					}).toString(),
				});

				if (!response.data) {
					throw new BetterAuthError("FAILED_TO_GET_ACCESS_TOKEN");
				}

				const data = response.data as PayPalTokenResponse;

				const result = {
					accessToken: data.access_token,
					refreshToken: data.refresh_token,
					accessTokenExpiresAt: data.expires_in
						? new Date(Date.now() + data.expires_in * 1000)
						: undefined,
					idToken: data.id_token,
				};

				return result;
			} catch (error) {
				logger.error("PayPal token exchange failed:", error);
				throw new BetterAuthError("FAILED_TO_GET_ACCESS_TOKEN");
			}
		},

		refreshAccessToken: options.refreshAccessToken
			? options.refreshAccessToken
			: async (refreshToken) => {
					const credentials = base64.encode(
						`${options.clientId}:${options.clientSecret}`,
					);

					try {
						const response = await betterFetch(tokenEndpoint, {
							method: "POST",
							headers: {
								Authorization: `Basic ${credentials}`,
								Accept: "application/json",
								"Accept-Language": "en_US",
								"Content-Type": "application/x-www-form-urlencoded",
							},
							body: new URLSearchParams({
								grant_type: "refresh_token",
								refresh_token: refreshToken,
							}).toString(),
						});

						if (!response.data) {
							throw new BetterAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
						}

						const data = response.data as any;
						return {
							accessToken: data.access_token,
							refreshToken: data.refresh_token,
							accessTokenExpiresAt: data.expires_in
								? new Date(Date.now() + data.expires_in * 1000)
								: undefined,
						};
					} catch (error) {
						logger.error("PayPal token refresh failed:", error);
						throw new BetterAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
					}
				},

		async verifyIdToken(token, nonce) {
			if (options.disableIdTokenSignIn) {
				return false;
			}
			if (options.verifyIdToken) {
				return options.verifyIdToken(token, nonce);
			}
			try {
				const payload = decodeJwt(token);
				return !!payload.sub;
			} catch (error) {
				logger.error("Failed to verify PayPal ID token:", error);
				return false;
			}
		},

		async getUserInfo(token) {
			if (options.getUserInfo) {
				return options.getUserInfo(token);
			}

			if (!token.accessToken) {
				logger.error("Access token is required to fetch PayPal user info");
				return null;
			}

			try {
				const response = await betterFetch<PayPalProfile>(
					`${userInfoEndpoint}?schema=paypalv1.1`,
					{
						headers: {
							Authorization: `Bearer ${token.accessToken}`,
							Accept: "application/json",
						},
					},
				);

				if (!response.data) {
					logger.error("Failed to fetch user info from PayPal");
					return null;
				}

				const userInfo = response.data;
				const userMap = await options.mapProfileToUser?.(userInfo);

				const result = {
					user: {
						id: userInfo.user_id,
						name: userInfo.name,
						email: userInfo.email,
						image: userInfo.picture,
						emailVerified: userInfo.email_verified,
						...userMap,
					},
					data: userInfo,
				};

				return result;
			} catch (error) {
				logger.error("Failed to fetch user info from PayPal:", error);
				return null;
			}
		},

		options,
	} satisfies OAuthProvider<PayPalProfile>;
};

```

--------------------------------------------------------------------------------
/docs/components/logo-context-menu.tsx:
--------------------------------------------------------------------------------

```typescript
"use client";

import type React from "react";
import { useState, useRef, useEffect } from "react";
import { Code, Image, Type } from "lucide-react";
import { toast } from "sonner";
import { useTheme } from "next-themes";
import type { StaticImageData } from "next/image";

interface LogoAssets {
	darkSvg: string;
	whiteSvg: string;
	darkWordmark: string;
	whiteWordmark: string;
	darkPng: StaticImageData;
	whitePng: StaticImageData;
}

interface ContextMenuProps {
	logo: React.ReactNode;
	logoAssets: LogoAssets;
}

export default function LogoContextMenu({
	logo,
	logoAssets,
}: ContextMenuProps) {
	const [showMenu, setShowMenu] = useState<boolean>(false);
	const menuRef = useRef<HTMLDivElement>(null);
	const logoRef = useRef<HTMLDivElement>(null);
	const { theme } = useTheme();

	const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
		e.preventDefault();
		e.stopPropagation();
		const rect = logoRef.current?.getBoundingClientRect();
		if (rect) {
			setShowMenu(true);
		}
	};

	const copySvgToClipboard = (
		e: React.MouseEvent,
		svgContent: string,
		type: string,
	) => {
		e.preventDefault();
		e.stopPropagation();
		navigator.clipboard
			.writeText(svgContent)
			.then(() => {
				toast.success("", {
					description: `${type} copied to clipboard`,
				});
			})
			.catch((err) => {
				toast.error("", {
					description: `Failed to copy ${type} to clipboard`,
				});
			});
		setShowMenu(false);
	};

	const downloadPng = (
		e: React.MouseEvent,
		pngData: StaticImageData,
		fileName: string,
	) => {
		e.preventDefault();
		e.stopPropagation();
		const link = document.createElement("a");
		link.href = pngData.src;
		link.download = fileName;

		document.body.appendChild(link);
		link.click();
		document.body.removeChild(link);

		toast.success(`Downloading the asset...`);

		setShowMenu(false);
	};

	const downloadAllAssets = (e: React.MouseEvent) => {
		e.preventDefault();
		e.stopPropagation();
		const link = document.createElement("a");
		link.href = "/branding/better-auth-brand-assets.zip";
		link.download = "better-auth-branding-assets.zip";

		document.body.appendChild(link);
		link.click();
		document.body.removeChild(link);

		toast.success("Downloading all assets...");
		setShowMenu(false);
	};

	useEffect(() => {
		const handleClickOutside = (event: MouseEvent) => {
			if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
				setShowMenu(false);
			}
		};

		document.addEventListener("mousedown", handleClickOutside);
		return () => {
			document.removeEventListener("mousedown", handleClickOutside);
		};
	}, []);

	const getAsset = <T,>(darkAsset: T, lightAsset: T): T => {
		return theme === "dark" ? darkAsset : lightAsset;
	};

	return (
		<div className="relative">
			<div
				ref={logoRef}
				onContextMenu={handleContextMenu}
				className="cursor-pointer"
			>
				{logo}
			</div>

			{showMenu && (
				<div
					ref={menuRef}
					className="fixed mx-10 z-50 bg-white dark:bg-black border border-gray-200 dark:border-border p-1 rounded-sm shadow-xl w-56 overflow-hidden animate-fd-dialog-in duration-500"
				>
					<div className="">
						<div className="flex p-0 gap-1 flex-col text-xs">
							<button
								onClick={(e) =>
									copySvgToClipboard(
										e,
										getAsset(logoAssets.darkSvg, logoAssets.whiteSvg),
										"Logo SVG",
									)
								}
								className="flex items-center gap-3 w-full p-2 text-black dark:text-white hover:bg-gray-100 dark:hover:bg-zinc-900 rounded-md transition-colors cursor-pointer"
							>
								<div className="flex items-center">
									<span className="text-gray-400 dark:text-zinc-400/30">[</span>

									<Code className="h-[13.8px] w-[13.8px] mx-[3px]" />
									<span className="text-gray-400 dark:text-zinc-400/30">]</span>
								</div>
								<span>Copy Logo as SVG </span>
							</button>
							<hr className="border-border/[60%]" />
							<button
								onClick={(e) =>
									copySvgToClipboard(
										e,
										getAsset(logoAssets.darkWordmark, logoAssets.whiteWordmark),
										"Logo Wordmark",
									)
								}
								className="flex items-center gap-3 w-full p-2 text-black dark:text-white hover:bg-gray-100 dark:hover:bg-zinc-900 rounded-md transition-colors cursor-pointer"
							>
								<div className="flex items-center">
									<span className="text-gray-400 dark:text-zinc-400/30">[</span>

									<Type className="h-[13.8px] w-[13.8px] mx-[3px]" />
									<span className="text-gray-400 dark:text-zinc-400/30">]</span>
								</div>
								<span>Copy Logo as Wordmark </span>
							</button>

							<hr className="border-border/[60%]" />
							<button
								onClick={(e) =>
									downloadPng(
										e,
										getAsset(logoAssets.darkPng, logoAssets.whitePng),
										`better-auth-logo-${theme}.png`,
									)
								}
								className="flex items-center gap-3 w-full p-2 text-black dark:text-white hover:bg-gray-100 dark:hover:bg-zinc-900 rounded-md transition-colors cursor-pointer"
							>
								<div className="flex items-center">
									<span className="text-gray-400 dark:text-zinc-400/30">[</span>

									<Image className="h-[13.8px] w-[13.8px] mx-[3px]" />
									<span className="text-gray-400 dark:text-zinc-400/30">]</span>
								</div>
								<span>Download Logo PNG</span>
							</button>
							<hr className="borde-border" />
							<button
								onClick={(e) => downloadAllAssets(e)}
								className="flex items-center gap-3 w-full p-2 text-black dark:text-white hover:bg-gray-100 dark:hover:bg-zinc-900 rounded-md transition-colors cursor-pointer"
							>
								<div className="flex items-center">
									<span className="text-gray-400 dark:text-zinc-400/30">[</span>

									<svg
										xmlns="http://www.w3.org/2000/svg"
										width="1em"
										height="1em"
										viewBox="0 0 24 24"
										className="h-[13.8px] w-[13.8px] mx-[3px]"
									>
										<path
											fill="none"
											stroke="currentColor"
											strokeLinecap="round"
											strokeLinejoin="round"
											strokeWidth="2"
											d="M4 8v8.8c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874c.427.218.987.218 2.105.218h9.606c1.118 0 1.677 0 2.104-.218c.377-.192.683-.498.875-.874c.218-.428.218-.987.218-2.105V8M4 8h16M4 8l1.365-2.39c.335-.585.503-.878.738-1.092c.209-.189.456-.332.723-.42C7.13 4 7.466 4 8.143 4h7.714c.676 0 1.015 0 1.318.099c.267.087.513.23.721.42c.236.213.404.506.74 1.093L20 8m-8 3v6m0 0l3-2m-3 2l-3-2"
										></path>
									</svg>
									<span className="text-gray-400 dark:text-zinc-400/30">]</span>
								</div>
								<span>Brand Assets</span>
							</button>
						</div>
					</div>
				</div>
			)}
		</div>
	);
}

```

--------------------------------------------------------------------------------
/docs/app/docs/[[...slug]]/page.client.tsx:
--------------------------------------------------------------------------------

```typescript
"use client";
import { useState, useTransition } from "react";
import {
	Check,
	Copy,
	ChevronDown,
	ExternalLink,
	MessageCircle,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
import {
	Popover,
	PopoverContent,
	PopoverTrigger,
} from "fumadocs-ui/components/ui/popover";
import { cva } from "class-variance-authority";

import { type MouseEventHandler, useEffect, useRef } from "react";
import { useEffectEvent } from "fumadocs-core/utils/use-effect-event";

export function useCopyButton(
	onCopy: () => void | Promise<void>,
): [checked: boolean, onClick: MouseEventHandler] {
	const [checked, setChecked] = useState(false);
	const timeoutRef = useRef<number | null>(null);

	const onClick: MouseEventHandler = useEffectEvent(() => {
		if (timeoutRef.current) window.clearTimeout(timeoutRef.current);
		const res = Promise.resolve(onCopy());

		void res.then(() => {
			setChecked(true);
			timeoutRef.current = window.setTimeout(() => {
				setChecked(false);
			}, 1500);
		});
	});

	// Avoid updates after being unmounted
	useEffect(() => {
		return () => {
			if (timeoutRef.current) window.clearTimeout(timeoutRef.current);
		};
	}, []);

	return [checked, onClick];
}

const cache = new Map<string, string>();

export function LLMCopyButton() {
	const [isLoading, startTransition] = useTransition();
	const [checked, onClick] = useCopyButton(async () => {
		startTransition(async () => {
			const url = window.location.pathname + ".mdx";
			const cached = cache.get(url);

			if (cached) {
				await navigator.clipboard.writeText(cached);
			} else {
				await navigator.clipboard.write([
					new ClipboardItem({
						"text/plain": fetch(url).then(async (res) => {
							const content = await res.text();
							cache.set(url, content);

							return content;
						}),
					}),
				]);
			}
		});
	});

	return (
		<button
			disabled={isLoading}
			className={cn(
				buttonVariants({
					variant: "secondary",
					size: "sm",
					className: "gap-2 [&_svg]:size-3.5 [&_svg]:text-fd-muted-foreground",
				}),
			)}
			onClick={onClick}
		>
			{checked ? <Check /> : <Copy />}
			Copy Markdown
		</button>
	);
}

const optionVariants = cva(
	"text-sm p-2 rounded-lg inline-flex items-center gap-2 hover:text-fd-accent-foreground hover:bg-fd-accent [&_svg]:size-4",
);

export function ViewOptions(props: { markdownUrl: string; githubUrl: string }) {
	const markdownUrl = new URL(props.markdownUrl, "https://better-auth.com");
	const q = `Read ${markdownUrl}, I want to ask questions about it.`;

	const claude = `https://claude.ai/new?${new URLSearchParams({
		q,
	})}`;
	const gpt = `https://chatgpt.com/?${new URLSearchParams({
		hints: "search",
		q,
	})}`;
	const t3 = `https://t3.chat/new?${new URLSearchParams({
		q,
	})}`;

	return (
		<Popover>
			<PopoverTrigger
				className={cn(
					buttonVariants({
						variant: "secondary",
						size: "sm",
						className: "gap-2",
					}),
				)}
			>
				Open in
				<ChevronDown className="size-3.5 text-fd-muted-foreground" />
			</PopoverTrigger>
			<PopoverContent className="flex flex-col overflow-auto">
				{[
					{
						title: "Open in GitHub",
						href: props.githubUrl,
						icon: (
							<svg fill="currentColor" role="img" viewBox="0 0 24 24">
								<title>GitHub</title>
								<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
							</svg>
						),
					},
					{
						title: "Open in ChatGPT",
						href: gpt,
						icon: (
							<svg
								role="img"
								viewBox="0 0 24 24"
								fill="currentColor"
								xmlns="http://www.w3.org/2000/svg"
							>
								<title>OpenAI</title>
								<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
							</svg>
						),
					},
					{
						title: "Open in Claude",
						href: claude,
						icon: (
							<svg
								fill="currentColor"
								role="img"
								viewBox="0 0 24 24"
								xmlns="http://www.w3.org/2000/svg"
							>
								<title>Anthropic</title>
								<path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z" />
							</svg>
						),
					},
					{
						title: "Open in T3 Chat",
						href: t3,
						icon: <MessageCircle />,
					},
				].map((item) => (
					<a
						key={item.href}
						href={item.href}
						rel="noreferrer noopener"
						target="_blank"
						className={cn(optionVariants())}
					>
						{item.icon}
						{item.title}
						<ExternalLink className="text-fd-muted-foreground size-3.5 ms-auto" />
					</a>
				))}
			</PopoverContent>
		</Popover>
	);
}

```

--------------------------------------------------------------------------------
/packages/cli/src/commands/mcp.ts:
--------------------------------------------------------------------------------

```typescript
import { Command } from "commander";
import { execSync } from "child_process";
import * as os from "os";
import * as fs from "fs";
import * as path from "path";
import chalk from "chalk";
import { base64 } from "@better-auth/utils/base64";

interface MCPOptions {
	cursor?: boolean;
	claudeCode?: boolean;
	openCode?: boolean;
	manual?: boolean;
}

export async function mcpAction(options: MCPOptions) {
	const mcpUrl = "https://mcp.chonkie.ai/better-auth/better-auth-builder/mcp";
	const mcpName = "Better Auth";

	if (options.cursor) {
		await handleCursorAction(mcpUrl, mcpName);
	} else if (options.claudeCode) {
		handleClaudeCodeAction(mcpUrl);
	} else if (options.openCode) {
		handleOpenCodeAction(mcpUrl);
	} else if (options.manual) {
		handleManualAction(mcpUrl, mcpName);
	} else {
		showAllOptions(mcpUrl, mcpName);
	}
}

async function handleCursorAction(mcpUrl: string, mcpName: string) {
	const mcpConfig = {
		url: mcpUrl,
	};

	const encodedConfig = base64.encode(
		new TextEncoder().encode(JSON.stringify(mcpConfig)),
	);
	const deeplinkUrl = `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(mcpName)}&config=${encodedConfig}`;

	console.log(chalk.bold.blue("🚀 Adding Better Auth MCP to Cursor..."));

	try {
		const platform = os.platform();
		let command: string;

		switch (platform) {
			case "darwin":
				command = `open "${deeplinkUrl}"`;
				break;
			case "win32":
				command = `start "" "${deeplinkUrl}"`;
				break;
			case "linux":
				command = `xdg-open "${deeplinkUrl}"`;
				break;
			default:
				throw new Error(`Unsupported platform: ${platform}`);
		}

		execSync(command, { stdio: "inherit" });
		console.log(chalk.green("\n✓ Cursor MCP installed successfully!"));
	} catch (error) {
		console.log(
			chalk.yellow(
				"\n⚠ Could not automatically open Cursor. Please copy the deeplink URL above and open it manually.",
			),
		);
		console.log(
			chalk.gray(
				"\nYou can also manually add this configuration to your Cursor MCP settings:",
			),
		);
		console.log(chalk.gray(JSON.stringify(mcpConfig, null, 2)));
	}

	console.log(chalk.bold.white("\n✨ Next Steps:"));
	console.log(
		chalk.gray("• The MCP server will be added to your Cursor configuration"),
	);
	console.log(
		chalk.gray("• You can now use Better Auth features directly in Cursor"),
	);
}

function handleClaudeCodeAction(mcpUrl: string) {
	console.log(chalk.bold.blue("🤖 Adding Better Auth MCP to Claude Code..."));

	const command = `claude mcp add --transport http better-auth ${mcpUrl}`;

	try {
		execSync(command, { stdio: "inherit" });
		console.log(chalk.green("\n✓ Claude Code MCP installed successfully!"));
	} catch (error) {
		console.log(
			chalk.yellow(
				"\n⚠ Could not automatically add to Claude Code. Please run this command manually:",
			),
		);
		console.log(chalk.cyan(command));
	}

	console.log(chalk.bold.white("\n✨ Next Steps:"));
	console.log(
		chalk.gray(
			"• The MCP server will be added to your Claude Code configuration",
		),
	);
	console.log(
		chalk.gray(
			"• You can now use Better Auth features directly in Claude Code",
		),
	);
}

function handleOpenCodeAction(mcpUrl: string) {
	console.log(chalk.bold.blue("🔧 Adding Better Auth MCP to Open Code..."));

	const openCodeConfig = {
		$schema: "https://opencode.ai/config.json",
		mcp: {
			"Better Auth": {
				type: "remote",
				url: mcpUrl,
				enabled: true,
			},
		},
	};

	const configPath = path.join(process.cwd(), "opencode.json");

	try {
		let existingConfig: {
			mcp?: Record<string, unknown>;
			[key: string]: unknown;
		} = {};
		if (fs.existsSync(configPath)) {
			const existingContent = fs.readFileSync(configPath, "utf8");
			existingConfig = JSON.parse(existingContent);
		}

		const mergedConfig = {
			...existingConfig,
			...openCodeConfig,
			mcp: {
				...existingConfig.mcp,
				...openCodeConfig.mcp,
			},
		};

		fs.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2));
		console.log(
			chalk.green(`\n✓ Open Code configuration written to ${configPath}`),
		);
		console.log(chalk.green("✓ Better Auth MCP added successfully!"));
	} catch (error) {
		console.log(
			chalk.yellow(
				"\n⚠ Could not automatically write opencode.json. Please add this configuration manually:",
			),
		);
		console.log(chalk.cyan(JSON.stringify(openCodeConfig, null, 2)));
	}

	console.log(chalk.bold.white("\n✨ Next Steps:"));
	console.log(chalk.gray("• Restart Open Code to load the new MCP server"));
	console.log(
		chalk.gray("• You can now use Better Auth features directly in Open Code"),
	);
}

function handleManualAction(mcpUrl: string, mcpName: string) {
	console.log(chalk.bold.blue("📝 Adding Better Auth MCP Configuration..."));

	const manualConfig = {
		[mcpName]: {
			url: mcpUrl,
		},
	};

	const configPath = path.join(process.cwd(), "mcp.json");

	try {
		let existingConfig = {};
		if (fs.existsSync(configPath)) {
			const existingContent = fs.readFileSync(configPath, "utf8");
			existingConfig = JSON.parse(existingContent);
		}

		const mergedConfig = {
			...existingConfig,
			...manualConfig,
		};

		fs.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2));
		console.log(chalk.green(`\n✓ MCP configuration written to ${configPath}`));
		console.log(chalk.green("✓ Better Auth MCP added successfully!"));
	} catch (error) {
		console.log(
			chalk.yellow(
				"\n⚠ Could not automatically write mcp.json. Please add this configuration manually:",
			),
		);
		console.log(chalk.cyan(JSON.stringify(manualConfig, null, 2)));
	}

	console.log(chalk.bold.white("\n✨ Next Steps:"));
	console.log(chalk.gray("• Restart your MCP client to load the new server"));
	console.log(
		chalk.gray(
			"• You can now use Better Auth features directly in your MCP client",
		),
	);
}

function showAllOptions(mcpUrl: string, mcpName: string) {
	console.log(chalk.bold.blue("🔌 Better Auth MCP Server"));
	console.log(chalk.gray("Choose your MCP client to get started:"));
	console.log();

	console.log(chalk.bold.white("Available Commands:"));
	console.log(chalk.cyan("  --cursor      ") + chalk.gray("Add to Cursor"));
	console.log(
		chalk.cyan("  --claude-code ") + chalk.gray("Add to Claude Code"),
	);
	console.log(chalk.cyan("  --open-code   ") + chalk.gray("Add to Open Code"));
	console.log(
		chalk.cyan("  --manual      ") + chalk.gray("Manual configuration"),
	);
	console.log();
}

export const mcp = new Command("mcp")
	.description("Add Better Auth MCP server to MCP Clients")
	.option("--cursor", "Automatically open Cursor with the MCP configuration")
	.option("--claude-code", "Show Claude Code MCP configuration command")
	.option("--open-code", "Show Open Code MCP configuration")
	.option("--manual", "Show manual MCP configuration for mcp.json")
	.action(mcpAction);

```

--------------------------------------------------------------------------------
/packages/core/src/types/context.ts:
--------------------------------------------------------------------------------

```typescript
import type {
	Account,
	BetterAuthDBSchema,
	SecondaryStorage,
	Session,
	User,
	Verification,
} from "../db";
import type { OAuthProvider } from "../oauth2";
import { createLogger } from "../env";
import type { DBAdapter, Where } from "../db/adapter";
import type { BetterAuthCookies } from "./cookie";
import type { DBPreservedModels } from "../db";
import type { LiteralUnion } from "./helper";
import type { CookieOptions, EndpointContext } from "better-call";
import type {
	BetterAuthOptions,
	BetterAuthRateLimitOptions,
} from "./init-options";

export type GenericEndpointContext<
	Options extends BetterAuthOptions = BetterAuthOptions,
> = EndpointContext<string, any> & {
	context: AuthContext<Options>;
};

export interface InternalAdapter<
	Options extends BetterAuthOptions = BetterAuthOptions,
> {
	createOAuthUser(
		user: Omit<User, "id" | "createdAt" | "updatedAt">,
		account: Omit<Account, "userId" | "id" | "createdAt" | "updatedAt"> &
			Partial<Account>,
	): Promise<{ user: User; account: Account }>;

	createUser<T extends Record<string, any>>(
		user: Omit<User, "id" | "createdAt" | "updatedAt" | "emailVerified"> &
			Partial<User> &
			Record<string, any>,
	): Promise<T & User>;

	createAccount<T extends Record<string, any>>(
		account: Omit<Account, "id" | "createdAt" | "updatedAt"> &
			Partial<Account> &
			T,
	): Promise<T & Account>;

	listSessions(userId: string): Promise<Session[]>;

	listUsers(
		limit?: number,
		offset?: number,
		sortBy?: { field: string; direction: "asc" | "desc" },
		where?: Where[],
	): Promise<User[]>;

	countTotalUsers(where?: Where[]): Promise<number>;

	deleteUser(userId: string): Promise<void>;

	createSession(
		userId: string,
		dontRememberMe?: boolean,
		override?: Partial<Session> & Record<string, any>,
		overrideAll?: boolean,
	): Promise<Session>;

	findSession(token: string): Promise<{
		session: Session & Record<string, any>;
		user: User & Record<string, any>;
	} | null>;

	findSessions(
		sessionTokens: string[],
	): Promise<{ session: Session; user: User }[]>;

	updateSession(
		sessionToken: string,
		session: Partial<Session> & Record<string, any>,
	): Promise<Session | null>;

	deleteSession(token: string): Promise<void>;

	deleteAccounts(userId: string): Promise<void>;

	deleteAccount(accountId: string): Promise<void>;

	deleteSessions(userIdOrSessionTokens: string | string[]): Promise<void>;

	findOAuthUser(
		email: string,
		accountId: string,
		providerId: string,
	): Promise<{ user: User; accounts: Account[] } | null>;

	findUserByEmail(
		email: string,
		options?: { includeAccounts: boolean },
	): Promise<{ user: User; accounts: Account[] } | null>;

	findUserById(userId: string): Promise<User | null>;

	linkAccount(
		account: Omit<Account, "id" | "createdAt" | "updatedAt"> & Partial<Account>,
	): Promise<Account>;

	// fixme: any type
	updateUser(
		userId: string,
		data: Partial<User> & Record<string, any>,
	): Promise<any>;

	updateUserByEmail(
		email: string,
		data: Partial<User & Record<string, any>>,
	): Promise<User>;

	updatePassword(userId: string, password: string): Promise<void>;

	findAccounts(userId: string): Promise<Account[]>;

	findAccount(accountId: string): Promise<Account | null>;

	findAccountByProviderId(
		accountId: string,
		providerId: string,
	): Promise<Account | null>;

	findAccountByUserId(userId: string): Promise<Account[]>;

	updateAccount(id: string, data: Partial<Account>): Promise<Account>;

	createVerificationValue(
		data: Omit<Verification, "createdAt" | "id" | "updatedAt"> &
			Partial<Verification>,
	): Promise<Verification>;

	findVerificationValue(identifier: string): Promise<Verification | null>;

	deleteVerificationValue(id: string): Promise<void>;

	deleteVerificationByIdentifier(identifier: string): Promise<void>;

	updateVerificationValue(
		id: string,
		data: Partial<Verification>,
	): Promise<Verification>;
}

type CreateCookieGetterFn = (
	cookieName: string,
	overrideAttributes?: Partial<CookieOptions>,
) => {
	name: string;
	attributes: CookieOptions;
};

type CheckPasswordFn<Options extends BetterAuthOptions = BetterAuthOptions> = (
	userId: string,
	ctx: GenericEndpointContext<Options>,
) => Promise<boolean>;

export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
	{
		options: Options;
		appName: string;
		baseURL: string;
		trustedOrigins: string[];
		oauthConfig?: {
			/**
			 * This is dangerous and should only be used in dev or staging environments.
			 */
			skipStateCookieCheck?: boolean;
		};
		/**
		 * New session that will be set after the request
		 * meaning: there is a `set-cookie` header that will set
		 * the session cookie. This is the fetched session. And it's set
		 * by `setNewSession` method.
		 */
		newSession: {
			session: Session & Record<string, any>;
			user: User & Record<string, any>;
		} | null;
		session: {
			session: Session & Record<string, any>;
			user: User & Record<string, any>;
		} | null;
		setNewSession: (
			session: {
				session: Session & Record<string, any>;
				user: User & Record<string, any>;
			} | null,
		) => void;
		socialProviders: OAuthProvider[];
		authCookies: BetterAuthCookies;
		logger: ReturnType<typeof createLogger>;
		rateLimit: {
			enabled: boolean;
			window: number;
			max: number;
			storage: "memory" | "database" | "secondary-storage";
		} & BetterAuthRateLimitOptions;
		adapter: DBAdapter<Options>;
		internalAdapter: InternalAdapter<Options>;
		createAuthCookie: CreateCookieGetterFn;
		secret: string;
		sessionConfig: {
			updateAge: number;
			expiresIn: number;
			freshAge: number;
		};
		generateId: (options: {
			model: LiteralUnion<DBPreservedModels, string>;
			size?: number;
		}) => string | false;
		secondaryStorage: SecondaryStorage | undefined;
		password: {
			hash: (password: string) => Promise<string>;
			verify: (data: { password: string; hash: string }) => Promise<boolean>;
			config: {
				minPasswordLength: number;
				maxPasswordLength: number;
			};
			checkPassword: CheckPasswordFn<Options>;
		};
		tables: BetterAuthDBSchema;
		runMigrations: () => Promise<void>;
		publishTelemetry: (event: {
			type: string;
			anonymousId?: string;
			payload: Record<string, any>;
		}) => Promise<void>;
		/**
		 * This skips the origin check for all requests.
		 *
		 * set to true by default for `test` environments and `false`
		 * for other environments.
		 *
		 * It's inferred from the `options.advanced?.disableCSRFCheck`
		 * option or `options.advanced?.disableOriginCheck` option.
		 *
		 * @default false
		 */
		skipOriginCheck: boolean;
		/**
		 * This skips the CSRF check for all requests.
		 *
		 * This is inferred from the `options.advanced?.
		 * disableCSRFCheck` option.
		 *
		 * @default false
		 */
		skipCSRFCheck: boolean;
	};

```

--------------------------------------------------------------------------------
/packages/better-auth/src/api/routes/callback.ts:
--------------------------------------------------------------------------------

```typescript
import * as z from "zod";
import { setSessionCookie } from "../../cookies";
import { setTokenUtil } from "../../oauth2/utils";
import { handleOAuthUserInfo } from "../../oauth2/link-account";
import { parseState } from "../../oauth2/state";
import { HIDE_METADATA } from "../../utils/hide-metadata";
import { createAuthEndpoint } from "@better-auth/core/api";
import { safeJSONParse } from "../../utils/json";
import type { OAuth2Tokens } from "@better-auth/core/oauth2";

const schema = z.object({
	code: z.string().optional(),
	error: z.string().optional(),
	device_id: z.string().optional(),
	error_description: z.string().optional(),
	state: z.string().optional(),
	user: z.string().optional(),
});

export const callbackOAuth = createAuthEndpoint(
	"/callback/:id",
	{
		method: ["GET", "POST"],
		body: schema.optional(),
		query: schema.optional(),
		metadata: HIDE_METADATA,
	},
	async (c) => {
		let queryOrBody: z.infer<typeof schema>;
		const defaultErrorURL =
			c.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;
		try {
			if (c.method === "GET") {
				queryOrBody = schema.parse(c.query);
			} else if (c.method === "POST") {
				queryOrBody = schema.parse(c.body);
			} else {
				throw new Error("Unsupported method");
			}
		} catch (e) {
			c.context.logger.error("INVALID_CALLBACK_REQUEST", e);
			throw c.redirect(`${defaultErrorURL}?error=invalid_callback_request`);
		}

		const { code, error, state, error_description, device_id } = queryOrBody;

		if (!state) {
			c.context.logger.error("State not found", error);
			const sep = defaultErrorURL.includes("?") ? "&" : "?";
			const url = `${defaultErrorURL}${sep}state=state_not_found`;
			throw c.redirect(url);
		}

		const {
			codeVerifier,
			callbackURL,
			link,
			errorURL,
			newUserURL,
			requestSignUp,
		} = await parseState(c);

		function redirectOnError(error: string, description?: string) {
			const baseURL = errorURL ?? defaultErrorURL;

			const params = new URLSearchParams({ error });
			if (description) params.set("error_description", description);

			const sep = baseURL.includes("?") ? "&" : "?";
			const url = `${baseURL}${sep}${params.toString()}`;

			throw c.redirect(url);
		}

		if (error) {
			redirectOnError(error, error_description);
		}

		if (!code) {
			c.context.logger.error("Code not found");
			throw redirectOnError("no_code");
		}
		const provider = c.context.socialProviders.find(
			(p) => p.id === c.params.id,
		);

		if (!provider) {
			c.context.logger.error(
				"Oauth provider with id",
				c.params.id,
				"not found",
			);
			throw redirectOnError("oauth_provider_not_found");
		}

		let tokens: OAuth2Tokens;
		try {
			tokens = await provider.validateAuthorizationCode({
				code: code,
				codeVerifier,
				deviceId: device_id,
				redirectURI: `${c.context.baseURL}/callback/${provider.id}`,
			});
		} catch (e) {
			c.context.logger.error("", e);
			throw redirectOnError("invalid_code");
		}
		const userInfo = await provider
			.getUserInfo({
				...tokens,
				user: c.body?.user ? safeJSONParse<any>(c.body.user) : undefined,
			})
			.then((res) => res?.user);

		if (!userInfo) {
			c.context.logger.error("Unable to get user info");
			return redirectOnError("unable_to_get_user_info");
		}

		if (!callbackURL) {
			c.context.logger.error("No callback URL found");
			throw redirectOnError("no_callback_url");
		}

		if (link) {
			const trustedProviders =
				c.context.options.account?.accountLinking?.trustedProviders;
			const isTrustedProvider = trustedProviders?.includes(
				provider.id as "apple",
			);
			if (
				(!isTrustedProvider && !userInfo.emailVerified) ||
				c.context.options.account?.accountLinking?.enabled === false
			) {
				c.context.logger.error("Unable to link account - untrusted provider");
				return redirectOnError("unable_to_link_account");
			}

			if (
				userInfo.email !== link.email &&
				c.context.options.account?.accountLinking?.allowDifferentEmails !== true
			) {
				return redirectOnError("email_doesn't_match");
			}

			const existingAccount = await c.context.internalAdapter.findAccount(
				String(userInfo.id),
			);

			if (existingAccount) {
				if (existingAccount.userId.toString() !== link.userId.toString()) {
					return redirectOnError("account_already_linked_to_different_user");
				}
				const updateData = Object.fromEntries(
					Object.entries({
						accessToken: await setTokenUtil(tokens.accessToken, c.context),
						refreshToken: await setTokenUtil(tokens.refreshToken, c.context),
						idToken: tokens.idToken,
						accessTokenExpiresAt: tokens.accessTokenExpiresAt,
						refreshTokenExpiresAt: tokens.refreshTokenExpiresAt,
						scope: tokens.scopes?.join(","),
					}).filter(([_, value]) => value !== undefined),
				);
				await c.context.internalAdapter.updateAccount(
					existingAccount.id,
					updateData,
				);
			} else {
				const newAccount = await c.context.internalAdapter.createAccount({
					userId: link.userId,
					providerId: provider.id,
					accountId: String(userInfo.id),
					...tokens,
					accessToken: await setTokenUtil(tokens.accessToken, c.context),
					refreshToken: await setTokenUtil(tokens.refreshToken, c.context),
					scope: tokens.scopes?.join(","),
				});
				if (!newAccount) {
					return redirectOnError("unable_to_link_account");
				}
			}
			let toRedirectTo: string;
			try {
				const url = callbackURL;
				toRedirectTo = url.toString();
			} catch {
				toRedirectTo = callbackURL;
			}
			throw c.redirect(toRedirectTo);
		}

		if (!userInfo.email) {
			c.context.logger.error(
				"Provider did not return email. This could be due to misconfiguration in the provider settings.",
			);
			return redirectOnError("email_not_found");
		}

		const result = await handleOAuthUserInfo(c, {
			userInfo: {
				...userInfo,
				id: String(userInfo.id),
				email: userInfo.email,
				name: userInfo.name || userInfo.email,
			},
			account: {
				providerId: provider.id,
				accountId: String(userInfo.id),
				...tokens,
				scope: tokens.scopes?.join(","),
			},
			callbackURL,
			disableSignUp:
				(provider.disableImplicitSignUp && !requestSignUp) ||
				provider.options?.disableSignUp,
			overrideUserInfo: provider.options?.overrideUserInfoOnSignIn,
		});
		if (result.error) {
			c.context.logger.error(result.error.split(" ").join("_"));
			return redirectOnError(result.error.split(" ").join("_"));
		}
		const { session, user } = result.data!;
		await setSessionCookie(c, {
			session,
			user,
		});
		let toRedirectTo: string;
		try {
			const url = result.isRegister ? newUserURL || callbackURL : callbackURL;
			toRedirectTo = url.toString();
		} catch {
			toRedirectTo = result.isRegister
				? newUserURL || callbackURL
				: callbackURL;
		}
		throw c.redirect(toRedirectTo);
	},
);

```
Page 15/51FirstPrevNextLast