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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/organization/adapter.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { Session, User } from "../../types";
  2 | import { getDate } from "../../utils/date";
  3 | import type { OrganizationOptions } from "./types";
  4 | import type {
  5 | 	InferInvitation,
  6 | 	InferMember,
  7 | 	InferOrganization,
  8 | 	InferTeam,
  9 | 	InvitationInput,
 10 | 	Member,
 11 | 	MemberInput,
 12 | 	OrganizationInput,
 13 | 	Team,
 14 | 	TeamInput,
 15 | 	TeamMember,
 16 | } from "./schema";
 17 | import { BetterAuthError } from "@better-auth/core/error";
 18 | import parseJSON from "../../client/parser";
 19 | import { type InferAdditionalFieldsFromPluginOptions } from "../../db";
 20 | import { getCurrentAdapter } from "@better-auth/core/context";
 21 | import type { AuthContext, GenericEndpointContext } from "@better-auth/core";
 22 | 
 23 | export const getOrgAdapter = <O extends OrganizationOptions>(
 24 | 	context: AuthContext,
 25 | 	options?: O,
 26 | ) => {
 27 | 	const baseAdapter = context.adapter;
 28 | 	return {
 29 | 		findOrganizationBySlug: async (slug: string) => {
 30 | 			const adapter = await getCurrentAdapter(baseAdapter);
 31 | 			const organization = await adapter.findOne<InferOrganization<O>>({
 32 | 				model: "organization",
 33 | 				where: [
 34 | 					{
 35 | 						field: "slug",
 36 | 						value: slug,
 37 | 					},
 38 | 				],
 39 | 			});
 40 | 			return organization;
 41 | 		},
 42 | 		createOrganization: async (data: {
 43 | 			organization: OrganizationInput &
 44 | 				// This represents the additional fields from the plugin options
 45 | 				Record<string, any>;
 46 | 		}) => {
 47 | 			const adapter = await getCurrentAdapter(baseAdapter);
 48 | 			const organization = await adapter.create<
 49 | 				OrganizationInput,
 50 | 				InferOrganization<O, false>
 51 | 			>({
 52 | 				model: "organization",
 53 | 				data: {
 54 | 					...data.organization,
 55 | 					metadata: data.organization.metadata
 56 | 						? JSON.stringify(data.organization.metadata)
 57 | 						: undefined,
 58 | 				},
 59 | 				forceAllowId: true,
 60 | 			});
 61 | 
 62 | 			return {
 63 | 				...organization,
 64 | 				metadata:
 65 | 					organization.metadata && typeof organization.metadata === "string"
 66 | 						? JSON.parse(organization.metadata)
 67 | 						: undefined,
 68 | 			} as typeof organization;
 69 | 		},
 70 | 		findMemberByEmail: async (data: {
 71 | 			email: string;
 72 | 			organizationId: string;
 73 | 		}) => {
 74 | 			const adapter = await getCurrentAdapter(baseAdapter);
 75 | 			const user = await adapter.findOne<User>({
 76 | 				model: "user",
 77 | 				where: [
 78 | 					{
 79 | 						field: "email",
 80 | 						value: data.email.toLowerCase(),
 81 | 					},
 82 | 				],
 83 | 			});
 84 | 			if (!user) {
 85 | 				return null;
 86 | 			}
 87 | 			const member = await adapter.findOne<Member>({
 88 | 				model: "member",
 89 | 				where: [
 90 | 					{
 91 | 						field: "organizationId",
 92 | 						value: data.organizationId,
 93 | 					},
 94 | 					{
 95 | 						field: "userId",
 96 | 						value: user.id,
 97 | 					},
 98 | 				],
 99 | 			});
100 | 			if (!member) {
101 | 				return null;
102 | 			}
103 | 			return {
104 | 				...member,
105 | 				user: {
106 | 					id: user.id,
107 | 					name: user.name,
108 | 					email: user.email,
109 | 					image: user.image,
110 | 				},
111 | 			};
112 | 		},
113 | 		listMembers: async (data: {
114 | 			organizationId?: string;
115 | 			limit?: number;
116 | 			offset?: number;
117 | 			sortBy?: string;
118 | 			sortOrder?: "asc" | "desc";
119 | 			filter?: {
120 | 				field: string;
121 | 				operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "contains";
122 | 				value: any;
123 | 			};
124 | 		}) => {
125 | 			const adapter = await getCurrentAdapter(baseAdapter);
126 | 			const members = await Promise.all([
127 | 				adapter.findMany<Member>({
128 | 					model: "member",
129 | 					where: [
130 | 						{ field: "organizationId", value: data.organizationId },
131 | 						...(data.filter?.field
132 | 							? [
133 | 									{
134 | 										field: data.filter?.field,
135 | 										value: data.filter?.value,
136 | 									},
137 | 								]
138 | 							: []),
139 | 					],
140 | 					limit: data.limit || options?.membershipLimit || 100,
141 | 					offset: data.offset || 0,
142 | 					sortBy: data.sortBy
143 | 						? { field: data.sortBy, direction: data.sortOrder || "asc" }
144 | 						: undefined,
145 | 				}),
146 | 				adapter.count({
147 | 					model: "member",
148 | 					where: [
149 | 						{ field: "organizationId", value: data.organizationId },
150 | 						...(data.filter?.field
151 | 							? [
152 | 									{
153 | 										field: data.filter?.field,
154 | 										value: data.filter?.value,
155 | 									},
156 | 								]
157 | 							: []),
158 | 					],
159 | 				}),
160 | 			]);
161 | 			const users = await adapter.findMany<User>({
162 | 				model: "user",
163 | 				where: [
164 | 					{
165 | 						field: "id",
166 | 						value: members[0].map((member) => member.userId),
167 | 						operator: "in",
168 | 					},
169 | 				],
170 | 			});
171 | 			return {
172 | 				members: members[0].map((member) => {
173 | 					const user = users.find((user) => user.id === member.userId);
174 | 					if (!user) {
175 | 						throw new BetterAuthError(
176 | 							"Unexpected error: User not found for member",
177 | 						);
178 | 					}
179 | 					return {
180 | 						...member,
181 | 						user: {
182 | 							id: user.id,
183 | 							name: user.name,
184 | 							email: user.email,
185 | 							image: user.image,
186 | 						},
187 | 					};
188 | 				}),
189 | 				total: members[1],
190 | 			};
191 | 		},
192 | 		findMemberByOrgId: async (data: {
193 | 			userId: string;
194 | 			organizationId: string;
195 | 		}) => {
196 | 			const adapter = await getCurrentAdapter(baseAdapter);
197 | 			const [member, user] = await Promise.all([
198 | 				await adapter.findOne<Member>({
199 | 					model: "member",
200 | 					where: [
201 | 						{
202 | 							field: "userId",
203 | 							value: data.userId,
204 | 						},
205 | 						{
206 | 							field: "organizationId",
207 | 							value: data.organizationId,
208 | 						},
209 | 					],
210 | 				}),
211 | 				await adapter.findOne<User>({
212 | 					model: "user",
213 | 					where: [
214 | 						{
215 | 							field: "id",
216 | 							value: data.userId,
217 | 						},
218 | 					],
219 | 				}),
220 | 			]);
221 | 			if (!user || !member) {
222 | 				return null;
223 | 			}
224 | 			return {
225 | 				...member,
226 | 				user: {
227 | 					id: user.id,
228 | 					name: user.name,
229 | 					email: user.email,
230 | 					image: user.image,
231 | 				},
232 | 			};
233 | 		},
234 | 		findMemberById: async (memberId: string) => {
235 | 			const adapter = await getCurrentAdapter(baseAdapter);
236 | 			const member = await adapter.findOne<Member>({
237 | 				model: "member",
238 | 				where: [
239 | 					{
240 | 						field: "id",
241 | 						value: memberId,
242 | 					},
243 | 				],
244 | 			});
245 | 			if (!member) {
246 | 				return null;
247 | 			}
248 | 			const user = await adapter.findOne<User>({
249 | 				model: "user",
250 | 				where: [
251 | 					{
252 | 						field: "id",
253 | 						value: member.userId,
254 | 					},
255 | 				],
256 | 			});
257 | 			if (!user) {
258 | 				return null;
259 | 			}
260 | 			return {
261 | 				...member,
262 | 				user: {
263 | 					id: user.id,
264 | 					name: user.name,
265 | 					email: user.email,
266 | 					image: user.image,
267 | 				},
268 | 			};
269 | 		},
270 | 		createMember: async (
271 | 			data: Omit<MemberInput, "id"> &
272 | 				// Additional fields from the plugin options
273 | 				Record<string, any>,
274 | 		) => {
275 | 			const adapter = await getCurrentAdapter(baseAdapter);
276 | 			const member = await adapter.create<
277 | 				typeof data,
278 | 				Member & InferAdditionalFieldsFromPluginOptions<"member", O, false>
279 | 			>({
280 | 				model: "member",
281 | 				data: {
282 | 					...data,
283 | 					createdAt: new Date(),
284 | 				},
285 | 			});
286 | 			return member;
287 | 		},
288 | 		updateMember: async (memberId: string, role: string) => {
289 | 			const adapter = await getCurrentAdapter(baseAdapter);
290 | 			const member = await adapter.update<InferMember<O>>({
291 | 				model: "member",
292 | 				where: [
293 | 					{
294 | 						field: "id",
295 | 						value: memberId,
296 | 					},
297 | 				],
298 | 				update: {
299 | 					role,
300 | 				},
301 | 			});
302 | 			return member;
303 | 		},
304 | 		deleteMember: async (memberId: string) => {
305 | 			const adapter = await getCurrentAdapter(baseAdapter);
306 | 			const member = await adapter.delete<InferMember<O>>({
307 | 				model: "member",
308 | 				where: [
309 | 					{
310 | 						field: "id",
311 | 						value: memberId,
312 | 					},
313 | 				],
314 | 			});
315 | 			return member;
316 | 		},
317 | 		updateOrganization: async (
318 | 			organizationId: string,
319 | 			data: Partial<OrganizationInput>,
320 | 		) => {
321 | 			const adapter = await getCurrentAdapter(baseAdapter);
322 | 			const organization = await adapter.update<InferOrganization<O>>({
323 | 				model: "organization",
324 | 				where: [
325 | 					{
326 | 						field: "id",
327 | 						value: organizationId,
328 | 					},
329 | 				],
330 | 				update: {
331 | 					...data,
332 | 					metadata:
333 | 						typeof data.metadata === "object"
334 | 							? JSON.stringify(data.metadata)
335 | 							: data.metadata,
336 | 				},
337 | 			});
338 | 			if (!organization) {
339 | 				return null;
340 | 			}
341 | 			return {
342 | 				...organization,
343 | 				metadata: organization.metadata
344 | 					? parseJSON<Record<string, any>>(organization.metadata)
345 | 					: undefined,
346 | 			};
347 | 		},
348 | 		deleteOrganization: async (organizationId: string) => {
349 | 			const adapter = await getCurrentAdapter(baseAdapter);
350 | 			await adapter.delete({
351 | 				model: "member",
352 | 				where: [
353 | 					{
354 | 						field: "organizationId",
355 | 						value: organizationId,
356 | 					},
357 | 				],
358 | 			});
359 | 			await adapter.delete({
360 | 				model: "invitation",
361 | 				where: [
362 | 					{
363 | 						field: "organizationId",
364 | 						value: organizationId,
365 | 					},
366 | 				],
367 | 			});
368 | 			await adapter.delete<InferOrganization<O>>({
369 | 				model: "organization",
370 | 				where: [
371 | 					{
372 | 						field: "id",
373 | 						value: organizationId,
374 | 					},
375 | 				],
376 | 			});
377 | 			return organizationId;
378 | 		},
379 | 		setActiveOrganization: async (
380 | 			sessionToken: string,
381 | 			organizationId: string | null,
382 | 			ctx: GenericEndpointContext,
383 | 		) => {
384 | 			const session = await context.internalAdapter.updateSession(
385 | 				sessionToken,
386 | 				{
387 | 					activeOrganizationId: organizationId,
388 | 				},
389 | 				ctx,
390 | 			);
391 | 			return session as Session;
392 | 		},
393 | 		findOrganizationById: async (organizationId: string) => {
394 | 			const adapter = await getCurrentAdapter(baseAdapter);
395 | 			const organization = await adapter.findOne<InferOrganization<O>>({
396 | 				model: "organization",
397 | 				where: [
398 | 					{
399 | 						field: "id",
400 | 						value: organizationId,
401 | 					},
402 | 				],
403 | 			});
404 | 			return organization;
405 | 		},
406 | 		checkMembership: async ({
407 | 			userId,
408 | 			organizationId,
409 | 		}: {
410 | 			userId: string;
411 | 			organizationId: string;
412 | 		}) => {
413 | 			const adapter = await getCurrentAdapter(baseAdapter);
414 | 			const member = await adapter.findOne<InferMember<O>>({
415 | 				model: "member",
416 | 				where: [
417 | 					{
418 | 						field: "userId",
419 | 						value: userId,
420 | 					},
421 | 					{
422 | 						field: "organizationId",
423 | 						value: organizationId,
424 | 					},
425 | 				],
426 | 			});
427 | 			return member;
428 | 		},
429 | 		/**
430 | 		 * @requires db
431 | 		 */
432 | 		findFullOrganization: async ({
433 | 			organizationId,
434 | 			isSlug,
435 | 			includeTeams,
436 | 			membersLimit,
437 | 		}: {
438 | 			organizationId: string;
439 | 			isSlug?: boolean;
440 | 			includeTeams?: boolean;
441 | 			membersLimit?: number;
442 | 		}) => {
443 | 			const adapter = await getCurrentAdapter(baseAdapter);
444 | 			const org = await adapter.findOne<InferOrganization<O>>({
445 | 				model: "organization",
446 | 				where: [{ field: isSlug ? "slug" : "id", value: organizationId }],
447 | 			});
448 | 			if (!org) {
449 | 				return null;
450 | 			}
451 | 			const [invitations, members, teams] = await Promise.all([
452 | 				adapter.findMany<InferInvitation<O>>({
453 | 					model: "invitation",
454 | 					where: [{ field: "organizationId", value: org.id }],
455 | 				}),
456 | 				adapter.findMany<InferMember<O>>({
457 | 					model: "member",
458 | 					where: [{ field: "organizationId", value: org.id }],
459 | 					limit: membersLimit ?? options?.membershipLimit ?? 100,
460 | 				}),
461 | 				includeTeams
462 | 					? adapter.findMany<InferTeam<O>>({
463 | 							model: "team",
464 | 							where: [{ field: "organizationId", value: org.id }],
465 | 						})
466 | 					: null,
467 | 			]);
468 | 
469 | 			if (!org) return null;
470 | 
471 | 			const userIds = members.map((member) => member.userId);
472 | 			const users =
473 | 				userIds.length > 0
474 | 					? await adapter.findMany<User>({
475 | 							model: "user",
476 | 							where: [{ field: "id", value: userIds, operator: "in" }],
477 | 							limit: options?.membershipLimit || 100,
478 | 						})
479 | 					: [];
480 | 
481 | 			const userMap = new Map(users.map((user) => [user.id, user]));
482 | 			const membersWithUsers = members.map((member) => {
483 | 				const user = userMap.get(member.userId);
484 | 				if (!user) {
485 | 					throw new BetterAuthError(
486 | 						"Unexpected error: User not found for member",
487 | 					);
488 | 				}
489 | 				return {
490 | 					...member,
491 | 					user: {
492 | 						id: user.id,
493 | 						name: user.name,
494 | 						email: user.email,
495 | 						image: user.image,
496 | 					},
497 | 				};
498 | 			});
499 | 
500 | 			return {
501 | 				...org,
502 | 				invitations,
503 | 				members: membersWithUsers,
504 | 				teams,
505 | 			};
506 | 		},
507 | 		listOrganizations: async (userId: string) => {
508 | 			const adapter = await getCurrentAdapter(baseAdapter);
509 | 			const members = await adapter.findMany<InferMember<O>>({
510 | 				model: "member",
511 | 				where: [
512 | 					{
513 | 						field: "userId",
514 | 						value: userId,
515 | 					},
516 | 				],
517 | 			});
518 | 
519 | 			if (!members || members.length === 0) {
520 | 				return [];
521 | 			}
522 | 
523 | 			const organizationIds = members.map((member) => member.organizationId);
524 | 
525 | 			const organizations = await adapter.findMany<InferOrganization<O>>({
526 | 				model: "organization",
527 | 				where: [
528 | 					{
529 | 						field: "id",
530 | 						value: organizationIds,
531 | 						operator: "in",
532 | 					},
533 | 				],
534 | 			});
535 | 			return organizations;
536 | 		},
537 | 		createTeam: async (data: Omit<TeamInput, "id">) => {
538 | 			const adapter = await getCurrentAdapter(baseAdapter);
539 | 			const team = await adapter.create<Omit<TeamInput, "id">, InferTeam<O>>({
540 | 				model: "team",
541 | 				data,
542 | 			});
543 | 			return team;
544 | 		},
545 | 		findTeamById: async <IncludeMembers extends boolean>({
546 | 			teamId,
547 | 			organizationId,
548 | 			includeTeamMembers,
549 | 		}: {
550 | 			teamId: string;
551 | 			organizationId?: string;
552 | 			includeTeamMembers?: IncludeMembers;
553 | 		}): Promise<
554 | 			| (InferTeam<O> &
555 | 					(IncludeMembers extends true ? { members: TeamMember[] } : {}))
556 | 			| null
557 | 		> => {
558 | 			const adapter = await getCurrentAdapter(baseAdapter);
559 | 			const team = await adapter.findOne<InferTeam<O>>({
560 | 				model: "team",
561 | 				where: [
562 | 					{
563 | 						field: "id",
564 | 						value: teamId,
565 | 					},
566 | 					...(organizationId
567 | 						? [
568 | 								{
569 | 									field: "organizationId",
570 | 									value: organizationId,
571 | 								},
572 | 							]
573 | 						: []),
574 | 				],
575 | 			});
576 | 			if (!team) {
577 | 				return null;
578 | 			}
579 | 
580 | 			let members: TeamMember[] = [];
581 | 			if (includeTeamMembers) {
582 | 				members = await adapter.findMany<TeamMember>({
583 | 					model: "teamMember",
584 | 					where: [
585 | 						{
586 | 							field: "teamId",
587 | 							value: teamId,
588 | 						},
589 | 					],
590 | 					limit: options?.membershipLimit || 100,
591 | 				});
592 | 				return {
593 | 					...team,
594 | 					members,
595 | 				};
596 | 			}
597 | 
598 | 			return team as InferTeam<O> &
599 | 				(IncludeMembers extends true ? { members: TeamMember[] } : {});
600 | 		},
601 | 		updateTeam: async (
602 | 			teamId: string,
603 | 			data: { name?: string; description?: string; status?: string },
604 | 		) => {
605 | 			const adapter = await getCurrentAdapter(baseAdapter);
606 | 			if ("id" in data) data.id = undefined;
607 | 			const team = await adapter.update<
608 | 				Team & InferAdditionalFieldsFromPluginOptions<"team", O>
609 | 			>({
610 | 				model: "team",
611 | 				where: [
612 | 					{
613 | 						field: "id",
614 | 						value: teamId,
615 | 					},
616 | 				],
617 | 				update: {
618 | 					...data,
619 | 				},
620 | 			});
621 | 			return team;
622 | 		},
623 | 
624 | 		deleteTeam: async (teamId: string) => {
625 | 			const adapter = await getCurrentAdapter(baseAdapter);
626 | 			await adapter.deleteMany({
627 | 				model: "teamMember",
628 | 				where: [
629 | 					{
630 | 						field: "teamId",
631 | 						value: teamId,
632 | 					},
633 | 				],
634 | 			});
635 | 			const team = await adapter.delete<Team>({
636 | 				model: "team",
637 | 				where: [
638 | 					{
639 | 						field: "id",
640 | 						value: teamId,
641 | 					},
642 | 				],
643 | 			});
644 | 			return team;
645 | 		},
646 | 
647 | 		listTeams: async (organizationId: string) => {
648 | 			const adapter = await getCurrentAdapter(baseAdapter);
649 | 			const teams = await adapter.findMany<Team>({
650 | 				model: "team",
651 | 				where: [
652 | 					{
653 | 						field: "organizationId",
654 | 						value: organizationId,
655 | 					},
656 | 				],
657 | 			});
658 | 			return teams;
659 | 		},
660 | 
661 | 		createTeamInvitation: async ({
662 | 			email,
663 | 			role,
664 | 			teamId,
665 | 			organizationId,
666 | 			inviterId,
667 | 			expiresIn = 1000 * 60 * 60 * 48, // Default expiration: 48 hours
668 | 		}: {
669 | 			email: string;
670 | 			role: string;
671 | 			teamId: string;
672 | 			organizationId: string;
673 | 			inviterId: string;
674 | 			expiresIn?: number;
675 | 		}) => {
676 | 			const adapter = await getCurrentAdapter(baseAdapter);
677 | 			const expiresAt = getDate(expiresIn); // Get expiration date
678 | 
679 | 			const invitation = await adapter.create<
680 | 				InvitationInput,
681 | 				InferInvitation<O>
682 | 			>({
683 | 				model: "invitation",
684 | 				data: {
685 | 					email,
686 | 					role,
687 | 					organizationId,
688 | 					teamId,
689 | 					inviterId,
690 | 					status: "pending",
691 | 					expiresAt,
692 | 				},
693 | 			});
694 | 
695 | 			return invitation;
696 | 		},
697 | 
698 | 		setActiveTeam: async (
699 | 			sessionToken: string,
700 | 			teamId: string | null,
701 | 			ctx: GenericEndpointContext,
702 | 		) => {
703 | 			const session = await context.internalAdapter.updateSession(
704 | 				sessionToken,
705 | 				{
706 | 					activeTeamId: teamId,
707 | 				},
708 | 				ctx,
709 | 			);
710 | 			return session as Session;
711 | 		},
712 | 
713 | 		listTeamMembers: async (data: { teamId: string }) => {
714 | 			const adapter = await getCurrentAdapter(baseAdapter);
715 | 			const members = await adapter.findMany<TeamMember>({
716 | 				model: "teamMember",
717 | 				where: [
718 | 					{
719 | 						field: "teamId",
720 | 						value: data.teamId,
721 | 					},
722 | 				],
723 | 			});
724 | 
725 | 			return members;
726 | 		},
727 | 		countTeamMembers: async (data: { teamId: string }) => {
728 | 			const adapter = await getCurrentAdapter(baseAdapter);
729 | 			const count = await adapter.count({
730 | 				model: "teamMember",
731 | 				where: [{ field: "teamId", value: data.teamId }],
732 | 			});
733 | 			return count;
734 | 		},
735 | 		countMembers: async (data: { organizationId: string }) => {
736 | 			const adapter = await getCurrentAdapter(baseAdapter);
737 | 			const count = await adapter.count({
738 | 				model: "member",
739 | 				where: [{ field: "organizationId", value: data.organizationId }],
740 | 			});
741 | 			return count;
742 | 		},
743 | 		listTeamsByUser: async (data: { userId: string }) => {
744 | 			const adapter = await getCurrentAdapter(baseAdapter);
745 | 			const members = await adapter.findMany<TeamMember>({
746 | 				model: "teamMember",
747 | 				where: [
748 | 					{
749 | 						field: "userId",
750 | 						value: data.userId,
751 | 					},
752 | 				],
753 | 			});
754 | 
755 | 			const teams = await adapter.findMany<Team>({
756 | 				model: "team",
757 | 				where: [
758 | 					{
759 | 						field: "id",
760 | 						operator: "in",
761 | 						value: members.map((m) => m.teamId),
762 | 					},
763 | 				],
764 | 			});
765 | 
766 | 			return teams;
767 | 		},
768 | 
769 | 		findTeamMember: async (data: { teamId: string; userId: string }) => {
770 | 			const adapter = await getCurrentAdapter(baseAdapter);
771 | 			const member = await adapter.findOne<TeamMember>({
772 | 				model: "teamMember",
773 | 				where: [
774 | 					{
775 | 						field: "teamId",
776 | 						value: data.teamId,
777 | 					},
778 | 					{
779 | 						field: "userId",
780 | 						value: data.userId,
781 | 					},
782 | 				],
783 | 			});
784 | 
785 | 			return member;
786 | 		},
787 | 
788 | 		findOrCreateTeamMember: async (data: {
789 | 			teamId: string;
790 | 			userId: string;
791 | 		}) => {
792 | 			const adapter = await getCurrentAdapter(baseAdapter);
793 | 			const member = await adapter.findOne<TeamMember>({
794 | 				model: "teamMember",
795 | 				where: [
796 | 					{
797 | 						field: "teamId",
798 | 						value: data.teamId,
799 | 					},
800 | 					{
801 | 						field: "userId",
802 | 						value: data.userId,
803 | 					},
804 | 				],
805 | 			});
806 | 
807 | 			if (member) return member;
808 | 
809 | 			return await adapter.create<Omit<TeamMember, "id">, TeamMember>({
810 | 				model: "teamMember",
811 | 				data: {
812 | 					teamId: data.teamId,
813 | 					userId: data.userId,
814 | 					createdAt: new Date(),
815 | 				},
816 | 			});
817 | 		},
818 | 
819 | 		removeTeamMember: async (data: { teamId: string; userId: string }) => {
820 | 			const adapter = await getCurrentAdapter(baseAdapter);
821 | 			await adapter.delete({
822 | 				model: "teamMember",
823 | 				where: [
824 | 					{
825 | 						field: "teamId",
826 | 						value: data.teamId,
827 | 					},
828 | 					{
829 | 						field: "userId",
830 | 						value: data.userId,
831 | 					},
832 | 				],
833 | 			});
834 | 		},
835 | 
836 | 		findInvitationsByTeamId: async (teamId: string) => {
837 | 			const adapter = await getCurrentAdapter(baseAdapter);
838 | 			const invitations = await adapter.findMany<InferInvitation<O>>({
839 | 				model: "invitation",
840 | 				where: [
841 | 					{
842 | 						field: "teamId",
843 | 						value: teamId,
844 | 					},
845 | 				],
846 | 			});
847 | 			return invitations;
848 | 		},
849 | 		listUserInvitations: async (email: string) => {
850 | 			const adapter = await getCurrentAdapter(baseAdapter);
851 | 			const invitations = await adapter.findMany<InferInvitation<O>>({
852 | 				model: "invitation",
853 | 				where: [{ field: "email", value: email.toLowerCase() }],
854 | 			});
855 | 			return invitations;
856 | 		},
857 | 		createInvitation: async ({
858 | 			invitation,
859 | 			user,
860 | 		}: {
861 | 			invitation: {
862 | 				email: string;
863 | 				role: string;
864 | 				organizationId: string;
865 | 				teamIds: string[];
866 | 			} & Record<string, any>; // This represents the additionalFields for the invitation
867 | 			user: User;
868 | 		}) => {
869 | 			const adapter = await getCurrentAdapter(baseAdapter);
870 | 			const defaultExpiration = 60 * 60 * 48;
871 | 			const expiresAt = getDate(
872 | 				options?.invitationExpiresIn || defaultExpiration,
873 | 				"sec",
874 | 			);
875 | 			const invite = await adapter.create<
876 | 				Omit<InvitationInput, "id">,
877 | 				InferInvitation<O>
878 | 			>({
879 | 				model: "invitation",
880 | 				data: {
881 | 					status: "pending",
882 | 					expiresAt,
883 | 					createdAt: new Date(),
884 | 					inviterId: user.id,
885 | 					...invitation,
886 | 					teamId:
887 | 						invitation.teamIds.length > 0 ? invitation.teamIds.join(",") : null,
888 | 				},
889 | 			});
890 | 
891 | 			return invite;
892 | 		},
893 | 		findInvitationById: async (id: string) => {
894 | 			const adapter = await getCurrentAdapter(baseAdapter);
895 | 			const invitation = await adapter.findOne<InferInvitation<O>>({
896 | 				model: "invitation",
897 | 				where: [
898 | 					{
899 | 						field: "id",
900 | 						value: id,
901 | 					},
902 | 				],
903 | 			});
904 | 			return invitation;
905 | 		},
906 | 		findPendingInvitation: async (data: {
907 | 			email: string;
908 | 			organizationId: string;
909 | 		}) => {
910 | 			const adapter = await getCurrentAdapter(baseAdapter);
911 | 			const invitation = await adapter.findMany<InferInvitation<O>>({
912 | 				model: "invitation",
913 | 				where: [
914 | 					{
915 | 						field: "email",
916 | 						value: data.email.toLowerCase(),
917 | 					},
918 | 					{
919 | 						field: "organizationId",
920 | 						value: data.organizationId,
921 | 					},
922 | 					{
923 | 						field: "status",
924 | 						value: "pending",
925 | 					},
926 | 				],
927 | 			});
928 | 			return invitation.filter(
929 | 				(invite) => new Date(invite.expiresAt) > new Date(),
930 | 			);
931 | 		},
932 | 		findPendingInvitations: async (data: { organizationId: string }) => {
933 | 			const adapter = await getCurrentAdapter(baseAdapter);
934 | 			const invitations = await adapter.findMany<InferInvitation<O>>({
935 | 				model: "invitation",
936 | 				where: [
937 | 					{
938 | 						field: "organizationId",
939 | 						value: data.organizationId,
940 | 					},
941 | 					{
942 | 						field: "status",
943 | 						value: "pending",
944 | 					},
945 | 				],
946 | 			});
947 | 			return invitations.filter(
948 | 				(invite) => new Date(invite.expiresAt) > new Date(),
949 | 			);
950 | 		},
951 | 		listInvitations: async (data: { organizationId: string }) => {
952 | 			const adapter = await getCurrentAdapter(baseAdapter);
953 | 			const invitations = await adapter.findMany<InferInvitation<O>>({
954 | 				model: "invitation",
955 | 				where: [
956 | 					{
957 | 						field: "organizationId",
958 | 						value: data.organizationId,
959 | 					},
960 | 				],
961 | 			});
962 | 			return invitations;
963 | 		},
964 | 		updateInvitation: async (data: {
965 | 			invitationId: string;
966 | 			status: "accepted" | "canceled" | "rejected";
967 | 		}) => {
968 | 			const adapter = await getCurrentAdapter(baseAdapter);
969 | 			const invitation = await adapter.update<InferInvitation<O>>({
970 | 				model: "invitation",
971 | 				where: [
972 | 					{
973 | 						field: "id",
974 | 						value: data.invitationId,
975 | 					},
976 | 				],
977 | 				update: {
978 | 					status: data.status,
979 | 				},
980 | 			});
981 | 			return invitation;
982 | 		},
983 | 	};
984 | };
985 | 
```

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

```typescript
  1 | import {
  2 | 	afterAll,
  3 | 	afterEach,
  4 | 	beforeAll,
  5 | 	describe,
  6 | 	expect,
  7 | 	it,
  8 | 	test,
  9 | } from "vitest";
 10 | import { getTestInstance } from "../../test-utils/test-instance";
 11 | import { oidcProvider } from ".";
 12 | import { genericOAuth } from "../generic-oauth";
 13 | import type { Client } from "./types";
 14 | import { createAuthClient } from "../../client";
 15 | import { oidcClient } from "./client";
 16 | import { genericOAuthClient } from "../generic-oauth/client";
 17 | import { listen, type Listener } from "listhen";
 18 | import { toNodeHandler } from "../../integrations/node";
 19 | import { jwt } from "../jwt";
 20 | import { createLocalJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
 21 | 
 22 | // Type for the server client with OIDC plugin
 23 | type ServerClient = ReturnType<
 24 | 	typeof createAuthClient<{
 25 | 		plugins: [ReturnType<typeof oidcClient>];
 26 | 	}>
 27 | >;
 28 | 
 29 | /**
 30 |  * Helper to handle OIDC consent flow when required per OIDC spec
 31 |  */
 32 | async function handleConsentFlow(
 33 | 	redirectURI: string,
 34 | 	serverClient: ServerClient,
 35 | 	sessionHeaders: Headers,
 36 | 	consentHeaders: Headers,
 37 | ): Promise<string> {
 38 | 	if (!redirectURI.includes("consent_code=")) {
 39 | 		return redirectURI;
 40 | 	}
 41 | 
 42 | 	// Extract consent code from redirect URL
 43 | 	const url = new URL(redirectURI, "http://localhost:3000");
 44 | 	const consentCode = url.searchParams.get("consent_code");
 45 | 
 46 | 	if (!consentCode) {
 47 | 		throw new Error("Consent code not found in redirect URL");
 48 | 	}
 49 | 
 50 | 	// Merge session headers with consent cookies
 51 | 	const authHeaders = new Headers(sessionHeaders);
 52 | 	consentHeaders.forEach((value, key) => {
 53 | 		if (key.toLowerCase() === "cookie") {
 54 | 			const existing = authHeaders.get("Cookie") || "";
 55 | 			authHeaders.set("Cookie", existing ? `${existing}; ${value}` : value);
 56 | 		} else {
 57 | 			authHeaders.set(key, value);
 58 | 		}
 59 | 	});
 60 | 
 61 | 	// Accept consent
 62 | 	const response = await serverClient.oauth2.consent(
 63 | 		{ accept: true, consent_code: consentCode },
 64 | 		{ headers: authHeaders, throw: true },
 65 | 	);
 66 | 
 67 | 	return response.redirectURI;
 68 | }
 69 | 
 70 | describe("oidc", async () => {
 71 | 	const {
 72 | 		auth: authorizationServer,
 73 | 		signInWithTestUser,
 74 | 		customFetchImpl,
 75 | 		testUser,
 76 | 	} = await getTestInstance({
 77 | 		baseURL: "http://localhost:3000",
 78 | 		plugins: [
 79 | 			oidcProvider({
 80 | 				loginPage: "/login",
 81 | 				consentPage: "/oauth2/authorize",
 82 | 				requirePKCE: true,
 83 | 				getAdditionalUserInfoClaim(user) {
 84 | 					return {
 85 | 						custom: "custom value",
 86 | 						userId: user.id,
 87 | 					};
 88 | 				},
 89 | 			}),
 90 | 			jwt(),
 91 | 		],
 92 | 	});
 93 | 	const { headers } = await signInWithTestUser();
 94 | 	const serverClient = createAuthClient({
 95 | 		plugins: [oidcClient()],
 96 | 		baseURL: "http://localhost:3000",
 97 | 		fetchOptions: {
 98 | 			customFetchImpl,
 99 | 			headers,
100 | 		},
101 | 	});
102 | 
103 | 	let server: Listener;
104 | 
105 | 	beforeAll(async () => {
106 | 		server = await listen(toNodeHandler(authorizationServer.handler), {
107 | 			port: 3000,
108 | 		});
109 | 	});
110 | 
111 | 	afterAll(async () => {
112 | 		await server.close();
113 | 	});
114 | 
115 | 	let application: Client = {
116 | 		clientId: "test-client-id",
117 | 		clientSecret: "test-client-secret-oidc",
118 | 		redirectURLs: ["http://localhost:3000/api/auth/oauth2/callback/test"],
119 | 		metadata: {},
120 | 		icon: "",
121 | 		type: "web",
122 | 		disabled: false,
123 | 		name: "test",
124 | 	};
125 | 
126 | 	it("should create oidc client", async ({ expect }) => {
127 | 		const createdClient = await serverClient.oauth2.register({
128 | 			client_name: application.name,
129 | 			redirect_uris: application.redirectURLs,
130 | 			logo_uri: application.icon,
131 | 		});
132 | 		expect(createdClient.data).toMatchObject({
133 | 			client_id: expect.any(String),
134 | 			client_secret: expect.any(String),
135 | 			client_name: "test",
136 | 			logo_uri: "",
137 | 			redirect_uris: ["http://localhost:3000/api/auth/oauth2/callback/test"],
138 | 			grant_types: ["authorization_code"],
139 | 			response_types: ["code"],
140 | 			token_endpoint_auth_method: "client_secret_basic",
141 | 			client_id_issued_at: expect.any(Number),
142 | 			client_secret_expires_at: 0,
143 | 		});
144 | 		if (createdClient.data) {
145 | 			application = {
146 | 				clientId: createdClient.data.client_id,
147 | 				clientSecret: createdClient.data.client_secret,
148 | 				redirectURLs: createdClient.data.redirect_uris,
149 | 				metadata: {},
150 | 				icon: createdClient.data.logo_uri || "",
151 | 				type: "web",
152 | 				disabled: false,
153 | 				name: createdClient.data.client_name || "",
154 | 			};
155 | 		}
156 | 	});
157 | 
158 | 	it("should sign in the user with the provider", async ({ expect }) => {
159 | 		// The RP (Relying Party) - the client application
160 | 		const { customFetchImpl: customFetchImplRP, cookieSetter } =
161 | 			await getTestInstance({
162 | 				account: {
163 | 					accountLinking: {
164 | 						trustedProviders: ["test"],
165 | 					},
166 | 				},
167 | 				plugins: [
168 | 					genericOAuth({
169 | 						config: [
170 | 							{
171 | 								providerId: "test",
172 | 								clientId: application.clientId,
173 | 								clientSecret: application.clientSecret || "",
174 | 								authorizationUrl:
175 | 									"http://localhost:3000/api/auth/oauth2/authorize",
176 | 								tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
177 | 								scopes: ["openid", "profile", "email"],
178 | 								pkce: true,
179 | 							},
180 | 						],
181 | 					}),
182 | 				],
183 | 			});
184 | 
185 | 		const client = createAuthClient({
186 | 			plugins: [genericOAuthClient()],
187 | 			baseURL: "http://localhost:5000",
188 | 			fetchOptions: {
189 | 				customFetchImpl: customFetchImplRP,
190 | 			},
191 | 		});
192 | 		const oAuthHeaders = new Headers();
193 | 		const data = await client.signIn.oauth2(
194 | 			{
195 | 				providerId: "test",
196 | 				callbackURL: "/dashboard",
197 | 			},
198 | 			{
199 | 				throw: true,
200 | 				onSuccess: cookieSetter(oAuthHeaders),
201 | 			},
202 | 		);
203 | 		expect(data.url).toContain(
204 | 			"http://localhost:3000/api/auth/oauth2/authorize",
205 | 		);
206 | 		expect(data.url).toContain(`client_id=${application.clientId}`);
207 | 
208 | 		// Make the authorization request
209 | 		let redirectURI = "";
210 | 		const consentHeaders = new Headers();
211 | 		await serverClient.$fetch(data.url, {
212 | 			method: "GET",
213 | 			onError(context) {
214 | 				redirectURI = context.response.headers.get("Location") || "";
215 | 				// Capture any consent cookies
216 | 				cookieSetter(consentHeaders)(context);
217 | 			},
218 | 		});
219 | 
220 | 		// Handle consent flow if required (per OIDC spec for non-trusted clients)
221 | 		redirectURI = await handleConsentFlow(
222 | 			redirectURI,
223 | 			serverClient,
224 | 			headers,
225 | 			consentHeaders,
226 | 		);
227 | 
228 | 		// Verify we got an authorization code
229 | 		expect(redirectURI).toContain(
230 | 			"http://localhost:3000/api/auth/oauth2/callback/test?code=",
231 | 		);
232 | 
233 | 		// Complete the OAuth flow
234 | 		let callbackURL = "";
235 | 		await client.$fetch(redirectURI, {
236 | 			headers: oAuthHeaders,
237 | 			onError(context) {
238 | 				callbackURL = context.response.headers.get("Location") || "";
239 | 			},
240 | 		});
241 | 		expect(callbackURL).toContain("/dashboard");
242 | 	});
243 | 
244 | 	it("should sign in after a consent flow", async ({ expect }) => {
245 | 		// The RP (Relying Party) - the client application
246 | 		const { customFetchImpl: customFetchImplRP, cookieSetter } =
247 | 			await getTestInstance({
248 | 				account: {
249 | 					accountLinking: {
250 | 						trustedProviders: ["test"],
251 | 					},
252 | 				},
253 | 				plugins: [
254 | 					genericOAuth({
255 | 						config: [
256 | 							{
257 | 								providerId: "test",
258 | 								clientId: application.clientId,
259 | 								clientSecret: application.clientSecret || "",
260 | 								authorizationUrl:
261 | 									"http://localhost:3000/api/auth/oauth2/authorize",
262 | 								tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
263 | 								scopes: ["openid", "profile", "email"],
264 | 								prompt: "consent",
265 | 								pkce: true,
266 | 							},
267 | 						],
268 | 					}),
269 | 				],
270 | 			});
271 | 
272 | 		const client = createAuthClient({
273 | 			plugins: [genericOAuthClient()],
274 | 			baseURL: "http://localhost:5000",
275 | 			fetchOptions: {
276 | 				customFetchImpl: customFetchImplRP,
277 | 			},
278 | 		});
279 | 		const oAuthHeaders = new Headers();
280 | 		const data = await client.signIn.oauth2(
281 | 			{
282 | 				providerId: "test",
283 | 				callbackURL: "/dashboard",
284 | 			},
285 | 			{
286 | 				throw: true,
287 | 				onSuccess: cookieSetter(oAuthHeaders),
288 | 			},
289 | 		);
290 | 		expect(data.url).toContain(
291 | 			"http://localhost:3000/api/auth/oauth2/authorize",
292 | 		);
293 | 		expect(data.url).toContain(`client_id=${application.clientId}`);
294 | 
295 | 		let redirectURI = "";
296 | 		const newHeaders = new Headers();
297 | 		await serverClient.$fetch(data.url, {
298 | 			method: "GET",
299 | 			onError(context) {
300 | 				redirectURI = context.response.headers.get("Location") || "";
301 | 				cookieSetter(newHeaders)(context);
302 | 				newHeaders.append("Cookie", headers.get("Cookie") || "");
303 | 			},
304 | 		});
305 | 		expect(redirectURI).toContain("/oauth2/authorize?");
306 | 		expect(redirectURI).toContain("consent_code=");
307 | 		expect(redirectURI).toContain("client_id=");
308 | 
309 | 		// No need to extract consent_code - it's in the signed cookie
310 | 		const res = await serverClient.oauth2.consent(
311 | 			{
312 | 				accept: true,
313 | 			},
314 | 			{
315 | 				headers: newHeaders,
316 | 				throw: true,
317 | 			},
318 | 		);
319 | 		expect(res.redirectURI).toContain(
320 | 			"http://localhost:3000/api/auth/oauth2/callback/test?code=",
321 | 		);
322 | 
323 | 		let callbackURL = "";
324 | 		await client.$fetch(res.redirectURI, {
325 | 			headers: oAuthHeaders,
326 | 			onError(context) {
327 | 				callbackURL = context.response.headers.get("Location") || "";
328 | 			},
329 | 		});
330 | 		expect(callbackURL).toContain("/dashboard");
331 | 	});
332 | 
333 | 	it("should sign in after a login flow", async ({ expect }) => {
334 | 		// The RP (Relying Party) - the client application
335 | 		const { customFetchImpl: customFetchImplRP, cookieSetter } =
336 | 			await getTestInstance({
337 | 				account: {
338 | 					accountLinking: {
339 | 						trustedProviders: ["test"],
340 | 					},
341 | 				},
342 | 				plugins: [
343 | 					genericOAuth({
344 | 						config: [
345 | 							{
346 | 								providerId: "test",
347 | 								clientId: application.clientId,
348 | 								clientSecret: application.clientSecret || "",
349 | 								authorizationUrl:
350 | 									"http://localhost:3000/api/auth/oauth2/authorize",
351 | 								tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
352 | 								scopes: ["openid", "profile", "email"],
353 | 								prompt: "login",
354 | 								pkce: true,
355 | 							},
356 | 						],
357 | 					}),
358 | 				],
359 | 			});
360 | 
361 | 		const client = createAuthClient({
362 | 			plugins: [genericOAuthClient()],
363 | 			baseURL: "http://localhost:5000",
364 | 			fetchOptions: {
365 | 				customFetchImpl: customFetchImplRP,
366 | 			},
367 | 		});
368 | 		const oAuthHeaders = new Headers();
369 | 		const data = await client.signIn.oauth2(
370 | 			{
371 | 				providerId: "test",
372 | 				callbackURL: "/dashboard",
373 | 			},
374 | 			{
375 | 				throw: true,
376 | 				onSuccess: cookieSetter(oAuthHeaders),
377 | 			},
378 | 		);
379 | 		expect(data.url).toContain(
380 | 			"http://localhost:3000/api/auth/oauth2/authorize",
381 | 		);
382 | 		expect(data.url).toContain(`client_id=${application.clientId}`);
383 | 
384 | 		let redirectURI = "";
385 | 		const newHeaders = new Headers();
386 | 		await serverClient.$fetch(data.url, {
387 | 			method: "GET",
388 | 			onError(context) {
389 | 				redirectURI = context.response.headers.get("Location") || "";
390 | 				cookieSetter(newHeaders)(context);
391 | 			},
392 | 			headers: newHeaders,
393 | 		});
394 | 		expect(redirectURI).toContain("/login");
395 | 
396 | 		await serverClient.signIn.email(
397 | 			{
398 | 				email: testUser.email,
399 | 				password: testUser.password,
400 | 			},
401 | 			{
402 | 				headers: newHeaders,
403 | 				onError(context) {
404 | 					redirectURI = context.response.headers.get("Location") || "";
405 | 					cookieSetter(newHeaders)(context);
406 | 				},
407 | 			},
408 | 		);
409 | 
410 | 		expect(redirectURI).toContain(
411 | 			"http://localhost:3000/api/auth/oauth2/callback/test?code=",
412 | 		);
413 | 		let callbackURL = "";
414 | 		await client.$fetch(redirectURI, {
415 | 			headers: oAuthHeaders,
416 | 			onError(context) {
417 | 				callbackURL = context.response.headers.get("Location") || "";
418 | 			},
419 | 		});
420 | 		expect(callbackURL).toContain("/dashboard");
421 | 	});
422 | });
423 | 
424 | describe("oidc storage", async () => {
425 | 	let server: Listener;
426 | 
427 | 	afterEach(async () => {
428 | 		if (server) {
429 | 			await server.close();
430 | 		}
431 | 	});
432 | 
433 | 	test.each([
434 | 		{
435 | 			storeClientSecret: undefined,
436 | 		},
437 | 		{
438 | 			storeClientSecret: "hashed",
439 | 		},
440 | 		{
441 | 			storeClientSecret: "encrypted",
442 | 		},
443 | 	] as const)("OIDC base test", async ({ storeClientSecret }) => {
444 | 		const {
445 | 			auth: authorizationServer,
446 | 			signInWithTestUser,
447 | 			customFetchImpl,
448 | 		} = await getTestInstance({
449 | 			baseURL: "http://localhost:3000",
450 | 			plugins: [
451 | 				oidcProvider({
452 | 					loginPage: "/login",
453 | 					consentPage: "/oauth2/authorize",
454 | 					requirePKCE: true,
455 | 					getAdditionalUserInfoClaim(user) {
456 | 						return {
457 | 							custom: "custom value",
458 | 							userId: user.id,
459 | 						};
460 | 					},
461 | 					storeClientSecret,
462 | 				}),
463 | 				jwt(),
464 | 			],
465 | 		});
466 | 		const { headers } = await signInWithTestUser();
467 | 		const serverClient = createAuthClient({
468 | 			plugins: [oidcClient()],
469 | 			baseURL: "http://localhost:3000",
470 | 			fetchOptions: {
471 | 				customFetchImpl,
472 | 				headers,
473 | 			},
474 | 		});
475 | 
476 | 		server = await listen(toNodeHandler(authorizationServer.handler), {
477 | 			port: 3000,
478 | 		});
479 | 
480 | 		let application: Client = {
481 | 			clientId: "test-client-id",
482 | 			clientSecret: "test-client-secret-oidc",
483 | 			redirectURLs: ["http://localhost:3000/api/auth/oauth2/callback/test"],
484 | 			metadata: {},
485 | 			icon: "",
486 | 			type: "web",
487 | 			disabled: false,
488 | 			name: "test",
489 | 		};
490 | 		const createdClient = await serverClient.oauth2.register({
491 | 			client_name: application.name,
492 | 			redirect_uris: application.redirectURLs,
493 | 			logo_uri: application.icon,
494 | 		});
495 | 		expect(createdClient.data).toMatchObject({
496 | 			client_id: expect.any(String),
497 | 			client_secret: expect.any(String),
498 | 			client_name: "test",
499 | 			logo_uri: "",
500 | 			redirect_uris: ["http://localhost:3000/api/auth/oauth2/callback/test"],
501 | 			grant_types: ["authorization_code"],
502 | 			response_types: ["code"],
503 | 			token_endpoint_auth_method: "client_secret_basic",
504 | 			client_id_issued_at: expect.any(Number),
505 | 			client_secret_expires_at: 0,
506 | 		});
507 | 		if (createdClient.data) {
508 | 			application = {
509 | 				clientId: createdClient.data.client_id,
510 | 				clientSecret: createdClient.data.client_secret,
511 | 				redirectURLs: createdClient.data.redirect_uris,
512 | 				metadata: {},
513 | 				icon: createdClient.data.logo_uri || "",
514 | 				type: "web",
515 | 				disabled: false,
516 | 				name: createdClient.data.client_name || "",
517 | 			};
518 | 		}
519 | 		// The RP (Relying Party) - the client application
520 | 		const { customFetchImpl: customFetchImplRP, cookieSetter } =
521 | 			await getTestInstance({
522 | 				account: {
523 | 					accountLinking: {
524 | 						trustedProviders: ["test"],
525 | 					},
526 | 				},
527 | 				plugins: [
528 | 					genericOAuth({
529 | 						config: [
530 | 							{
531 | 								providerId: "test",
532 | 								clientId: application.clientId,
533 | 								clientSecret: application.clientSecret || "",
534 | 								authorizationUrl:
535 | 									"http://localhost:3000/api/auth/oauth2/authorize",
536 | 								tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
537 | 								scopes: ["openid", "profile", "email"],
538 | 								pkce: true,
539 | 							},
540 | 						],
541 | 					}),
542 | 				],
543 | 			});
544 | 
545 | 		const client = createAuthClient({
546 | 			plugins: [genericOAuthClient()],
547 | 			baseURL: "http://localhost:5000",
548 | 			fetchOptions: {
549 | 				customFetchImpl: customFetchImplRP,
550 | 			},
551 | 		});
552 | 		const oAuthHeaders = new Headers();
553 | 		const data = await client.signIn.oauth2(
554 | 			{
555 | 				providerId: "test",
556 | 				callbackURL: "/dashboard",
557 | 			},
558 | 			{
559 | 				throw: true,
560 | 				onSuccess: cookieSetter(oAuthHeaders),
561 | 			},
562 | 		);
563 | 		expect(data.url).toContain(
564 | 			"http://localhost:3000/api/auth/oauth2/authorize",
565 | 		);
566 | 		expect(data.url).toContain(`client_id=${application.clientId}`);
567 | 
568 | 		let redirectURI = "";
569 | 		const newHeaders = new Headers();
570 | 		await serverClient.$fetch(data.url, {
571 | 			method: "GET",
572 | 			onError(context) {
573 | 				redirectURI = context.response.headers.get("Location") || "";
574 | 				cookieSetter(newHeaders)(context);
575 | 				// Note: headers might be available from parent scope (serverClient auth)
576 | 				// newHeaders already has the consent cookies
577 | 			},
578 | 		});
579 | 
580 | 		// Handle consent flow if required (per OIDC spec for non-trusted clients)
581 | 		redirectURI = await handleConsentFlow(
582 | 			redirectURI,
583 | 			serverClient,
584 | 			headers,
585 | 			newHeaders,
586 | 		);
587 | 
588 | 		// Verify we got an authorization code
589 | 		expect(redirectURI).toContain(
590 | 			"http://localhost:3000/api/auth/oauth2/callback/test?code=",
591 | 		);
592 | 
593 | 		let callbackURL = "";
594 | 		await client.$fetch(redirectURI, {
595 | 			headers: oAuthHeaders,
596 | 			onError(context) {
597 | 				callbackURL = context.response.headers.get("Location") || "";
598 | 			},
599 | 		});
600 | 		expect(callbackURL).toContain("/dashboard");
601 | 	});
602 | });
603 | 
604 | describe("oidc-jwt", async () => {
605 | 	let server: Listener | null = null;
606 | 
607 | 	afterEach(async () => {
608 | 		if (server) {
609 | 			await server.close();
610 | 			server = null;
611 | 		}
612 | 	});
613 | 
614 | 	test.each([
615 | 		{ useJwt: true, description: "with jwt plugin", expected: "EdDSA" },
616 | 		{ useJwt: false, description: "without jwt plugin", expected: "HS256" },
617 | 	])(
618 | 		"testing oidc-provider $description to return token signed with $expected",
619 | 		async ({ useJwt, expected }) => {
620 | 			const {
621 | 				auth: authorizationServer,
622 | 				signInWithTestUser,
623 | 				customFetchImpl,
624 | 				testUser,
625 | 			} = await getTestInstance({
626 | 				baseURL: "http://localhost:3000",
627 | 				plugins: [
628 | 					oidcProvider({
629 | 						loginPage: "/login",
630 | 						consentPage: "/oauth2/authorize",
631 | 						requirePKCE: true,
632 | 						getAdditionalUserInfoClaim(user) {
633 | 							return {
634 | 								custom: "custom value",
635 | 								userId: user.id,
636 | 							};
637 | 						},
638 | 						useJWTPlugin: useJwt,
639 | 					}),
640 | 					...(useJwt ? [jwt()] : []),
641 | 				],
642 | 			});
643 | 			const { headers } = await signInWithTestUser();
644 | 			const serverClient = createAuthClient({
645 | 				plugins: [oidcClient()],
646 | 				baseURL: "http://localhost:3000",
647 | 				fetchOptions: {
648 | 					customFetchImpl,
649 | 					headers,
650 | 				},
651 | 			});
652 | 			server = await listen(toNodeHandler(authorizationServer.handler), {
653 | 				port: 3000,
654 | 			});
655 | 			let application: Client = {
656 | 				clientId: "test-client-id",
657 | 				clientSecret: "test-client-secret-oidc",
658 | 				redirectURLs: ["http://localhost:3000/api/auth/oauth2/callback/test"],
659 | 				metadata: {},
660 | 				icon: "",
661 | 				type: "web",
662 | 				disabled: false,
663 | 				name: "test",
664 | 			};
665 | 			const createdClient = await serverClient.oauth2.register({
666 | 				client_name: application.name,
667 | 				redirect_uris: application.redirectURLs,
668 | 				logo_uri: application.icon,
669 | 			});
670 | 			expect(createdClient.data).toMatchObject({
671 | 				client_id: expect.any(String),
672 | 				client_secret: expect.any(String),
673 | 				client_name: "test",
674 | 				logo_uri: "",
675 | 				redirect_uris: ["http://localhost:3000/api/auth/oauth2/callback/test"],
676 | 				grant_types: ["authorization_code"],
677 | 				response_types: ["code"],
678 | 				token_endpoint_auth_method: "client_secret_basic",
679 | 				client_id_issued_at: expect.any(Number),
680 | 				client_secret_expires_at: 0,
681 | 			});
682 | 			if (createdClient.data) {
683 | 				application = {
684 | 					clientId: createdClient.data.client_id,
685 | 					clientSecret: createdClient.data.client_secret,
686 | 					redirectURLs: createdClient.data.redirect_uris,
687 | 					metadata: {},
688 | 					icon: createdClient.data.logo_uri || "",
689 | 					type: "web",
690 | 					disabled: false,
691 | 					name: createdClient.data.client_name || "",
692 | 				};
693 | 			}
694 | 
695 | 			// The RP (Relying Party) - the client application
696 | 			const { customFetchImpl: customFetchImplRP, cookieSetter } =
697 | 				await getTestInstance({
698 | 					account: {
699 | 						accountLinking: {
700 | 							trustedProviders: ["test"],
701 | 						},
702 | 					},
703 | 					plugins: [
704 | 						genericOAuth({
705 | 							config: [
706 | 								{
707 | 									providerId: "test",
708 | 									clientId: application.clientId,
709 | 									clientSecret: application.clientSecret || "",
710 | 									authorizationUrl:
711 | 										"http://localhost:3000/api/auth/oauth2/authorize",
712 | 									tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
713 | 									scopes: ["openid", "profile", "email"],
714 | 									pkce: true,
715 | 								},
716 | 							],
717 | 						}),
718 | 					],
719 | 				});
720 | 
721 | 			const client = createAuthClient({
722 | 				plugins: [genericOAuthClient()],
723 | 				baseURL: "http://localhost:5000",
724 | 				fetchOptions: {
725 | 					customFetchImpl: customFetchImplRP,
726 | 				},
727 | 			});
728 | 			const oAuthHeaders = new Headers();
729 | 			const data = await client.signIn.oauth2(
730 | 				{
731 | 					providerId: "test",
732 | 					callbackURL: "/dashboard",
733 | 				},
734 | 				{
735 | 					throw: true,
736 | 					onSuccess: cookieSetter(oAuthHeaders),
737 | 				},
738 | 			);
739 | 			expect(data.url).toContain(
740 | 				"http://localhost:3000/api/auth/oauth2/authorize",
741 | 			);
742 | 			expect(data.url).toContain(`client_id=${application.clientId}`);
743 | 
744 | 			let redirectURI = "";
745 | 			const newHeaders = new Headers();
746 | 			await serverClient.$fetch(data.url, {
747 | 				method: "GET",
748 | 				onError(context) {
749 | 					redirectURI = context.response.headers.get("Location") || "";
750 | 					cookieSetter(newHeaders)(context);
751 | 					if (headers.get("Cookie")) {
752 | 						newHeaders.append("Cookie", headers.get("Cookie") || "");
753 | 					}
754 | 				},
755 | 			});
756 | 
757 | 			// Check if consent is needed (per OIDC spec)
758 | 			if (redirectURI.includes("consent_code=")) {
759 | 				// Handle consent flow - this is expected per OIDC spec for non-trusted clients
760 | 				expect(redirectURI).toContain("/oauth2/authorize?");
761 | 				expect(redirectURI).toContain("consent_code=");
762 | 				expect(redirectURI).toContain("client_id=");
763 | 
764 | 				// Extract consent_code from URL
765 | 				const url = new URL(redirectURI, "http://localhost:3000");
766 | 				const consentCode = url.searchParams.get("consent_code");
767 | 
768 | 				const res = await serverClient.oauth2.consent(
769 | 					{
770 | 						accept: true,
771 | 						consent_code: consentCode,
772 | 					},
773 | 					{
774 | 						headers: newHeaders,
775 | 						throw: true,
776 | 					},
777 | 				);
778 | 				expect(res.redirectURI).toContain(
779 | 					"http://localhost:3000/api/auth/oauth2/callback/test?code=",
780 | 				);
781 | 				redirectURI = res.redirectURI;
782 | 			} else {
783 | 				// Direct code response (trusted client)
784 | 				expect(redirectURI).toContain(
785 | 					"http://localhost:3000/api/auth/oauth2/callback/test?code=",
786 | 				);
787 | 			}
788 | 			let authToken = undefined;
789 | 			let callbackURL = "";
790 | 			await client.$fetch(redirectURI, {
791 | 				headers: oAuthHeaders,
792 | 				onError(context) {
793 | 					callbackURL = context.response.headers.get("Location") || "";
794 | 					authToken = context.response.headers.get("set-auth-token")!;
795 | 				},
796 | 			});
797 | 			expect(callbackURL).toContain("/dashboard");
798 | 			const accessToken = await client.getAccessToken(
799 | 				{ providerId: "test", userId: testUser.id },
800 | 				{
801 | 					auth: {
802 | 						type: "Bearer",
803 | 						token: authToken,
804 | 					},
805 | 				},
806 | 			);
807 | 			const decoded = decodeProtectedHeader(accessToken.data?.idToken!);
808 | 			if (useJwt) {
809 | 				const jwks = await authorizationServer.api.getJwks();
810 | 				const jwkSet = createLocalJWKSet(jwks);
811 | 				const checkSignature = await jwtVerify(
812 | 					accessToken.data?.idToken!,
813 | 					jwkSet,
814 | 				);
815 | 				expect(checkSignature).toBeDefined();
816 | 				expect(Number.isInteger(checkSignature.payload.iat)).toBeTruthy();
817 | 				expect(Number.isInteger(checkSignature.payload.exp)).toBeTruthy();
818 | 			} else {
819 | 				const clientSecret = application.clientSecret;
820 | 				const checkSignature = await jwtVerify(
821 | 					accessToken.data?.idToken!,
822 | 					new TextEncoder().encode(clientSecret),
823 | 				);
824 | 				expect(checkSignature).toBeDefined();
825 | 			}
826 | 
827 | 			// expect(checkSignature.payload).toBeDefined();
828 | 			expect(decoded.alg).toBe(expected);
829 | 		},
830 | 	);
831 | });
832 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/package.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "name": "better-auth",
  3 |   "version": "1.4.0-beta.10",
  4 |   "description": "The most comprehensive authentication library for TypeScript.",
  5 |   "type": "module",
  6 |   "license": "MIT",
  7 |   "repository": {
  8 |     "type": "git",
  9 |     "url": "https://github.com/better-auth/better-auth",
 10 |     "directory": "packages/better-auth"
 11 |   },
 12 |   "keywords": [
 13 |     "auth",
 14 |     "oauth",
 15 |     "oidc",
 16 |     "2fa",
 17 |     "social",
 18 |     "security",
 19 |     "typescript",
 20 |     "nextjs"
 21 |   ],
 22 |   "publishConfig": {
 23 |     "access": "public"
 24 |   },
 25 |   "scripts": {
 26 |     "build": "tsdown",
 27 |     "test": "vitest",
 28 |     "prepare": "prisma generate --schema ./src/adapters/prisma-adapter/test/base.prisma",
 29 |     "typecheck": "tsc --project tsconfig.json"
 30 |   },
 31 |   "main": "./dist/index.js",
 32 |   "module": "./dist/index.js",
 33 |   "exports": {
 34 |     ".": {
 35 |       "import": {
 36 |         "types": "./dist/index.d.ts",
 37 |         "default": "./dist/index.js"
 38 |       },
 39 |       "require": {
 40 |         "types": "./dist/index.d.cts",
 41 |         "default": "./dist/index.cjs"
 42 |       }
 43 |     },
 44 |     "./social-providers": {
 45 |       "import": {
 46 |         "types": "./dist/social-providers/index.d.ts",
 47 |         "default": "./dist/social-providers/index.js"
 48 |       },
 49 |       "require": {
 50 |         "types": "./dist/social-providers/index.d.cts",
 51 |         "default": "./dist/social-providers/index.cjs"
 52 |       }
 53 |     },
 54 |     "./client": {
 55 |       "import": {
 56 |         "types": "./dist/client/index.d.ts",
 57 |         "default": "./dist/client/index.js"
 58 |       },
 59 |       "require": {
 60 |         "types": "./dist/client/index.d.cts",
 61 |         "default": "./dist/client/index.cjs"
 62 |       }
 63 |     },
 64 |     "./client/plugins": {
 65 |       "import": {
 66 |         "types": "./dist/client/plugins/index.d.ts",
 67 |         "default": "./dist/client/plugins/index.js"
 68 |       },
 69 |       "require": {
 70 |         "types": "./dist/client/plugins/index.d.cts",
 71 |         "default": "./dist/client/plugins/index.cjs"
 72 |       }
 73 |     },
 74 |     "./types": {
 75 |       "import": {
 76 |         "types": "./dist/types/index.d.ts",
 77 |         "default": "./dist/types/index.js"
 78 |       },
 79 |       "require": {
 80 |         "types": "./dist/types/index.d.cts",
 81 |         "default": "./dist/types/index.cjs"
 82 |       }
 83 |     },
 84 |     "./crypto": {
 85 |       "import": {
 86 |         "types": "./dist/crypto/index.d.ts",
 87 |         "default": "./dist/crypto/index.js"
 88 |       },
 89 |       "require": {
 90 |         "types": "./dist/crypto/index.d.cts",
 91 |         "default": "./dist/crypto/index.cjs"
 92 |       }
 93 |     },
 94 |     "./cookies": {
 95 |       "import": {
 96 |         "types": "./dist/cookies/index.d.ts",
 97 |         "default": "./dist/cookies/index.js"
 98 |       },
 99 |       "require": {
100 |         "types": "./dist/cookies/index.d.cts",
101 |         "default": "./dist/cookies/index.cjs"
102 |       }
103 |     },
104 |     "./oauth2": {
105 |       "import": {
106 |         "types": "./dist/oauth2/index.d.ts",
107 |         "default": "./dist/oauth2/index.js"
108 |       },
109 |       "require": {
110 |         "types": "./dist/oauth2/index.d.cts",
111 |         "default": "./dist/oauth2/index.cjs"
112 |       }
113 |     },
114 |     "./react": {
115 |       "import": {
116 |         "types": "./dist/client/react/index.d.ts",
117 |         "default": "./dist/client/react/index.js"
118 |       },
119 |       "require": {
120 |         "types": "./dist/client/react/index.d.cts",
121 |         "default": "./dist/client/react/index.cjs"
122 |       }
123 |     },
124 |     "./solid": {
125 |       "import": {
126 |         "types": "./dist/client/solid/index.d.ts",
127 |         "default": "./dist/client/solid/index.js"
128 |       },
129 |       "require": {
130 |         "types": "./dist/client/solid/index.d.cts",
131 |         "default": "./dist/client/solid/index.cjs"
132 |       }
133 |     },
134 |     "./lynx": {
135 |       "import": {
136 |         "types": "./dist/client/lynx/index.d.ts",
137 |         "default": "./dist/client/lynx/index.js"
138 |       },
139 |       "require": {
140 |         "types": "./dist/client/lynx/index.d.cts",
141 |         "default": "./dist/client/lynx/index.cjs"
142 |       }
143 |     },
144 |     "./test": {
145 |       "import": {
146 |         "types": "./dist/test-utils/index.d.ts",
147 |         "default": "./dist/test-utils/index.js"
148 |       },
149 |       "require": {
150 |         "types": "./dist/test-utils/index.d.cts",
151 |         "default": "./dist/test-utils/index.cjs"
152 |       }
153 |     },
154 |     "./api": {
155 |       "import": {
156 |         "types": "./dist/api/index.d.ts",
157 |         "default": "./dist/api/index.js"
158 |       },
159 |       "require": {
160 |         "types": "./dist/api/index.d.cts",
161 |         "default": "./dist/api/index.cjs"
162 |       }
163 |     },
164 |     "./db": {
165 |       "import": {
166 |         "types": "./dist/db/index.d.ts",
167 |         "default": "./dist/db/index.js"
168 |       },
169 |       "require": {
170 |         "types": "./dist/db/index.d.cts",
171 |         "default": "./dist/db/index.cjs"
172 |       }
173 |     },
174 |     "./vue": {
175 |       "import": {
176 |         "types": "./dist/client/vue/index.d.ts",
177 |         "default": "./dist/client/vue/index.js"
178 |       },
179 |       "require": {
180 |         "types": "./dist/client/vue/index.d.cts",
181 |         "default": "./dist/client/vue/index.cjs"
182 |       }
183 |     },
184 |     "./plugins": {
185 |       "import": {
186 |         "types": "./dist/plugins/index.d.ts",
187 |         "default": "./dist/plugins/index.js"
188 |       },
189 |       "require": {
190 |         "types": "./dist/plugins/index.d.cts",
191 |         "default": "./dist/plugins/index.cjs"
192 |       }
193 |     },
194 |     "./svelte-kit": {
195 |       "import": {
196 |         "types": "./dist/integrations/svelte-kit.d.ts",
197 |         "default": "./dist/integrations/svelte-kit.js"
198 |       },
199 |       "require": {
200 |         "types": "./dist/integrations/svelte-kit.d.cts",
201 |         "default": "./dist/integrations/svelte-kit.cjs"
202 |       }
203 |     },
204 |     "./solid-start": {
205 |       "import": {
206 |         "types": "./dist/integrations/solid-start.d.ts",
207 |         "default": "./dist/integrations/solid-start.js"
208 |       },
209 |       "require": {
210 |         "types": "./dist/integrations/solid-start.d.cts",
211 |         "default": "./dist/integrations/solid-start.cjs"
212 |       }
213 |     },
214 |     "./svelte": {
215 |       "import": {
216 |         "types": "./dist/client/svelte/index.d.ts",
217 |         "default": "./dist/client/svelte/index.js"
218 |       },
219 |       "require": {
220 |         "types": "./dist/client/svelte/index.d.cts",
221 |         "default": "./dist/client/svelte/index.cjs"
222 |       }
223 |     },
224 |     "./next-js": {
225 |       "import": {
226 |         "types": "./dist/integrations/next-js.d.ts",
227 |         "default": "./dist/integrations/next-js.js"
228 |       },
229 |       "require": {
230 |         "types": "./dist/integrations/next-js.d.cts",
231 |         "default": "./dist/integrations/next-js.cjs"
232 |       }
233 |     },
234 |     "./react-start": {
235 |       "import": {
236 |         "types": "./dist/integrations/react-start.d.ts",
237 |         "default": "./dist/integrations/react-start.js"
238 |       },
239 |       "require": {
240 |         "types": "./dist/integrations/react-start.d.cts",
241 |         "default": "./dist/integrations/react-start.cjs"
242 |       }
243 |     },
244 |     "./node": {
245 |       "import": {
246 |         "types": "./dist/integrations/node.d.ts",
247 |         "default": "./dist/integrations/node.js"
248 |       },
249 |       "require": {
250 |         "types": "./dist/integrations/node.d.cts",
251 |         "default": "./dist/integrations/node.cjs"
252 |       }
253 |     },
254 |     "./adapters/prisma": {
255 |       "import": {
256 |         "types": "./dist/adapters/prisma-adapter/index.d.ts",
257 |         "default": "./dist/adapters/prisma-adapter/index.js"
258 |       },
259 |       "require": {
260 |         "types": "./dist/adapters/prisma-adapter/index.d.cts",
261 |         "default": "./dist/adapters/prisma-adapter/index.cjs"
262 |       }
263 |     },
264 |     "./adapters/drizzle": {
265 |       "import": {
266 |         "types": "./dist/adapters/drizzle-adapter/index.d.ts",
267 |         "default": "./dist/adapters/drizzle-adapter/index.js"
268 |       },
269 |       "require": {
270 |         "types": "./dist/adapters/drizzle-adapter/index.d.cts",
271 |         "default": "./dist/adapters/drizzle-adapter/index.cjs"
272 |       }
273 |     },
274 |     "./adapters/mongodb": {
275 |       "import": {
276 |         "types": "./dist/adapters/mongodb-adapter/index.d.ts",
277 |         "default": "./dist/adapters/mongodb-adapter/index.js"
278 |       },
279 |       "require": {
280 |         "types": "./dist/adapters/mongodb-adapter/index.d.cts",
281 |         "default": "./dist/adapters/mongodb-adapter/index.cjs"
282 |       }
283 |     },
284 |     "./adapters/memory": {
285 |       "import": {
286 |         "types": "./dist/adapters/memory-adapter/index.d.ts",
287 |         "default": "./dist/adapters/memory-adapter/index.js"
288 |       },
289 |       "require": {
290 |         "types": "./dist/adapters/memory-adapter/index.d.cts",
291 |         "default": "./dist/adapters/memory-adapter/index.cjs"
292 |       }
293 |     },
294 |     "./adapters/test": {
295 |       "import": {
296 |         "types": "./dist/adapters/test.d.ts",
297 |         "default": "./dist/adapters/test.js"
298 |       },
299 |       "require": {
300 |         "types": "./dist/adapters/test.d.cts",
301 |         "default": "./dist/adapters/test.cjs"
302 |       }
303 |     },
304 |     "./adapters": {
305 |       "import": {
306 |         "types": "./dist/adapters/index.d.ts",
307 |         "default": "./dist/adapters/index.js"
308 |       },
309 |       "require": {
310 |         "types": "./dist/adapters/index.d.cts",
311 |         "default": "./dist/adapters/index.cjs"
312 |       }
313 |     },
314 |     "./plugins/access": {
315 |       "import": {
316 |         "types": "./dist/plugins/access/index.d.ts",
317 |         "default": "./dist/plugins/access/index.js"
318 |       },
319 |       "require": {
320 |         "types": "./dist/plugins/access/index.d.cts",
321 |         "default": "./dist/plugins/access/index.cjs"
322 |       }
323 |     },
324 |     "./plugins/admin": {
325 |       "import": {
326 |         "types": "./dist/plugins/admin/index.d.ts",
327 |         "default": "./dist/plugins/admin/index.js"
328 |       },
329 |       "require": {
330 |         "types": "./dist/plugins/admin/index.d.cts",
331 |         "default": "./dist/plugins/admin/index.cjs"
332 |       }
333 |     },
334 |     "./plugins/admin/access": {
335 |       "import": {
336 |         "types": "./dist/plugins/admin/access/index.d.ts",
337 |         "default": "./dist/plugins/admin/access/index.js"
338 |       },
339 |       "require": {
340 |         "types": "./dist/plugins/admin/access/index.d.cts",
341 |         "default": "./dist/plugins/admin/access/index.cjs"
342 |       }
343 |     },
344 |     "./plugins/anonymous": {
345 |       "import": {
346 |         "types": "./dist/plugins/anonymous/index.d.ts",
347 |         "default": "./dist/plugins/anonymous/index.js"
348 |       },
349 |       "require": {
350 |         "types": "./dist/plugins/anonymous/index.d.cts",
351 |         "default": "./dist/plugins/anonymous/index.cjs"
352 |       }
353 |     },
354 |     "./plugins/bearer": {
355 |       "import": {
356 |         "types": "./dist/plugins/bearer/index.d.ts",
357 |         "default": "./dist/plugins/bearer/index.js"
358 |       },
359 |       "require": {
360 |         "types": "./dist/plugins/bearer/index.d.cts",
361 |         "default": "./dist/plugins/bearer/index.cjs"
362 |       }
363 |     },
364 |     "./plugins/custom-session": {
365 |       "import": {
366 |         "types": "./dist/plugins/custom-session/index.d.ts",
367 |         "default": "./dist/plugins/custom-session/index.js"
368 |       },
369 |       "require": {
370 |         "types": "./dist/plugins/custom-session/index.d.cts",
371 |         "default": "./dist/plugins/custom-session/index.cjs"
372 |       }
373 |     },
374 |     "./plugins/email-otp": {
375 |       "import": {
376 |         "types": "./dist/plugins/email-otp/index.d.ts",
377 |         "default": "./dist/plugins/email-otp/index.js"
378 |       },
379 |       "require": {
380 |         "types": "./dist/plugins/email-otp/index.d.cts",
381 |         "default": "./dist/plugins/email-otp/index.cjs"
382 |       }
383 |     },
384 |     "./plugins/generic-oauth": {
385 |       "import": {
386 |         "types": "./dist/plugins/generic-oauth/index.d.ts",
387 |         "default": "./dist/plugins/generic-oauth/index.js"
388 |       },
389 |       "require": {
390 |         "types": "./dist/plugins/generic-oauth/index.d.cts",
391 |         "default": "./dist/plugins/generic-oauth/index.cjs"
392 |       }
393 |     },
394 |     "./plugins/jwt": {
395 |       "import": {
396 |         "types": "./dist/plugins/jwt/index.d.ts",
397 |         "default": "./dist/plugins/jwt/index.js"
398 |       },
399 |       "require": {
400 |         "types": "./dist/plugins/jwt/index.d.cts",
401 |         "default": "./dist/plugins/jwt/index.cjs"
402 |       }
403 |     },
404 |     "./plugins/haveibeenpwned": {
405 |       "import": {
406 |         "types": "./dist/plugins/haveibeenpwned/index.d.ts",
407 |         "default": "./dist/plugins/haveibeenpwned/index.js"
408 |       },
409 |       "require": {
410 |         "types": "./dist/plugins/haveibeenpwned/index.d.cts",
411 |         "default": "./dist/plugins/haveibeenpwned/index.cjs"
412 |       }
413 |     },
414 |     "./plugins/sso": {
415 |       "import": {
416 |         "types": "./dist/plugins/sso/index.d.ts",
417 |         "default": "./dist/plugins/sso/index.js"
418 |       },
419 |       "require": {
420 |         "types": "./dist/plugins/sso/index.d.cts",
421 |         "default": "./dist/plugins/sso/index.cjs"
422 |       }
423 |     },
424 |     "./plugins/oidc-provider": {
425 |       "import": {
426 |         "types": "./dist/plugins/oidc-provider/index.d.ts",
427 |         "default": "./dist/plugins/oidc-provider/index.js"
428 |       },
429 |       "require": {
430 |         "types": "./dist/plugins/oidc-provider/index.d.cts",
431 |         "default": "./dist/plugins/oidc-provider/index.cjs"
432 |       }
433 |     },
434 |     "./plugins/magic-link": {
435 |       "import": {
436 |         "types": "./dist/plugins/magic-link/index.d.ts",
437 |         "default": "./dist/plugins/magic-link/index.js"
438 |       },
439 |       "require": {
440 |         "types": "./dist/plugins/magic-link/index.d.cts",
441 |         "default": "./dist/plugins/magic-link/index.cjs"
442 |       }
443 |     },
444 |     "./plugins/multi-session": {
445 |       "import": {
446 |         "types": "./dist/plugins/multi-session/index.d.ts",
447 |         "default": "./dist/plugins/multi-session/index.js"
448 |       },
449 |       "require": {
450 |         "types": "./dist/plugins/multi-session/index.d.cts",
451 |         "default": "./dist/plugins/multi-session/index.cjs"
452 |       }
453 |     },
454 |     "./plugins/oauth-proxy": {
455 |       "import": {
456 |         "types": "./dist/plugins/oauth-proxy/index.d.ts",
457 |         "default": "./dist/plugins/oauth-proxy/index.js"
458 |       },
459 |       "require": {
460 |         "types": "./dist/plugins/oauth-proxy/index.d.cts",
461 |         "default": "./dist/plugins/oauth-proxy/index.cjs"
462 |       }
463 |     },
464 |     "./plugins/organization": {
465 |       "import": {
466 |         "types": "./dist/plugins/organization/index.d.ts",
467 |         "default": "./dist/plugins/organization/index.js"
468 |       },
469 |       "require": {
470 |         "types": "./dist/plugins/organization/index.d.cts",
471 |         "default": "./dist/plugins/organization/index.cjs"
472 |       }
473 |     },
474 |     "./plugins/organization/access": {
475 |       "import": {
476 |         "types": "./dist/plugins/organization/access/index.d.ts",
477 |         "default": "./dist/plugins/organization/access/index.js"
478 |       },
479 |       "require": {
480 |         "types": "./dist/plugins/organization/access/index.d.cts",
481 |         "default": "./dist/plugins/organization/access/index.cjs"
482 |       }
483 |     },
484 |     "./plugins/one-time-token": {
485 |       "import": {
486 |         "types": "./dist/plugins/one-time-token/index.d.ts",
487 |         "default": "./dist/plugins/one-time-token/index.js"
488 |       },
489 |       "require": {
490 |         "types": "./dist/plugins/one-time-token/index.d.cts",
491 |         "default": "./dist/plugins/one-time-token/index.cjs"
492 |       }
493 |     },
494 |     "./plugins/passkey": {
495 |       "import": {
496 |         "types": "./dist/plugins/passkey/index.d.ts",
497 |         "default": "./dist/plugins/passkey/index.js"
498 |       },
499 |       "require": {
500 |         "types": "./dist/plugins/passkey/index.d.cts",
501 |         "default": "./dist/plugins/passkey/index.cjs"
502 |       }
503 |     },
504 |     "./plugins/phone-number": {
505 |       "import": {
506 |         "types": "./dist/plugins/phone-number/index.d.ts",
507 |         "default": "./dist/plugins/phone-number/index.js"
508 |       },
509 |       "require": {
510 |         "types": "./dist/plugins/phone-number/index.d.cts",
511 |         "default": "./dist/plugins/phone-number/index.cjs"
512 |       }
513 |     },
514 |     "./plugins/two-factor": {
515 |       "import": {
516 |         "types": "./dist/plugins/two-factor/index.d.ts",
517 |         "default": "./dist/plugins/two-factor/index.js"
518 |       },
519 |       "require": {
520 |         "types": "./dist/plugins/two-factor/index.d.cts",
521 |         "default": "./dist/plugins/two-factor/index.cjs"
522 |       }
523 |     },
524 |     "./plugins/username": {
525 |       "import": {
526 |         "types": "./dist/plugins/username/index.d.ts",
527 |         "default": "./dist/plugins/username/index.js"
528 |       },
529 |       "require": {
530 |         "types": "./dist/plugins/username/index.d.cts",
531 |         "default": "./dist/plugins/username/index.cjs"
532 |       }
533 |     },
534 |     "./plugins/siwe": {
535 |       "import": {
536 |         "types": "./dist/plugins/siwe/index.d.ts",
537 |         "default": "./dist/plugins/siwe/index.js"
538 |       },
539 |       "require": {
540 |         "types": "./dist/plugins/siwe/index.d.cts",
541 |         "default": "./dist/plugins/siwe/index.cjs"
542 |       }
543 |     },
544 |     "./plugins/device-authorization": {
545 |       "import": {
546 |         "types": "./dist/plugins/device-authorization/index.d.ts",
547 |         "default": "./dist/plugins/device-authorization/index.js"
548 |       },
549 |       "require": {
550 |         "types": "./dist/plugins/device-authorization/index.d.cts",
551 |         "default": "./dist/plugins/device-authorization/index.cjs"
552 |       }
553 |     }
554 |   },
555 |   "typesVersions": {
556 |     "*": {
557 |       "*": [
558 |         "./dist/index.d.ts"
559 |       ],
560 |       "node": [
561 |         "./dist/integrations/node.d.ts"
562 |       ],
563 |       "react": [
564 |         "./dist/client/react/index.d.ts"
565 |       ],
566 |       "vue": [
567 |         "./dist/client/vue/index.d.ts"
568 |       ],
569 |       "svelte": [
570 |         "./dist/client/svelte/index.d.ts"
571 |       ],
572 |       "social-providers": [
573 |         "./dist/social-providers/index.d.ts"
574 |       ],
575 |       "client": [
576 |         "./dist/client/index.d.ts"
577 |       ],
578 |       "client/plugins": [
579 |         "./dist/client/plugins/index.d.ts"
580 |       ],
581 |       "types": [
582 |         "./dist/types/index.d.ts"
583 |       ],
584 |       "crypto": [
585 |         "./dist/crypto/index.d.ts"
586 |       ],
587 |       "cookies": [
588 |         "./dist/cookies/index.d.ts"
589 |       ],
590 |       "oauth2": [
591 |         "./dist/oauth2/index.d.ts"
592 |       ],
593 |       "solid": [
594 |         "./dist/client/solid/index.d.ts"
595 |       ],
596 |       "lynx": [
597 |         "./dist/client/lynx/index.d.ts"
598 |       ],
599 |       "api": [
600 |         "./dist/api/index.d.ts"
601 |       ],
602 |       "db": [
603 |         "./dist/db/index.d.ts"
604 |       ],
605 |       "svelte-kit": [
606 |         "./dist/integrations/svelte-kit.d.ts"
607 |       ],
608 |       "solid-start": [
609 |         "./dist/integrations/solid-start.d.ts"
610 |       ],
611 |       "next-js": [
612 |         "./dist/integrations/next-js.d.ts"
613 |       ],
614 |       "react-start": [
615 |         "./dist/integrations/react-start.d.ts"
616 |       ],
617 |       "adapters": [
618 |         "./dist/adapters/index.d.ts"
619 |       ],
620 |       "adapters/prisma": [
621 |         "./dist/adapters/prisma-adapter/index.d.ts"
622 |       ],
623 |       "adapters/drizzle": [
624 |         "./dist/adapters/drizzle-adapter/index.d.ts"
625 |       ],
626 |       "adapters/mongodb": [
627 |         "./dist/adapters/mongodb-adapter/index.d.ts"
628 |       ],
629 |       "adapters/memory": [
630 |         "./dist/adapters/memory-adapter/index.d.ts"
631 |       ],
632 |       "plugins": [
633 |         "./dist/plugins/index.d.ts"
634 |       ],
635 |       "plugins/access": [
636 |         "./dist/plugins/access/index.d.ts"
637 |       ],
638 |       "plugins/admin": [
639 |         "./dist/plugins/admin/index.d.ts"
640 |       ],
641 |       "plugins/admin/access": [
642 |         "./dist/plugins/admin/access/index.d.ts"
643 |       ],
644 |       "plugins/anonymous": [
645 |         "./dist/plugins/anonymous/index.d.ts"
646 |       ],
647 |       "plugins/bearer": [
648 |         "./dist/plugins/bearer/index.d.ts"
649 |       ],
650 |       "plugins/custom-session": [
651 |         "./dist/plugins/custom-session/index.d.ts"
652 |       ],
653 |       "plugins/email-otp": [
654 |         "./dist/plugins/email-otp/index.d.ts"
655 |       ],
656 |       "plugins/generic-oauth": [
657 |         "./dist/plugins/generic-oauth/index.d.ts"
658 |       ],
659 |       "plugins/haveibeenpwned": [
660 |         "./dist/plugins/haveibeenpwned/index.d.ts"
661 |       ],
662 |       "plugins/oauth-proxy": [
663 |         "./dist/plugins/oauth-proxy/index.d.ts"
664 |       ],
665 |       "plugins/one-time-token": [
666 |         "./dist/plugins/one-time-token/index.d.ts"
667 |       ],
668 |       "plugins/sso": [
669 |         "./dist/plugins/sso/index.d.ts"
670 |       ],
671 |       "plugins/oidc-provider": [
672 |         "./dist/plugins/oidc-provider/index.d.ts"
673 |       ],
674 |       "plugins/jwt": [
675 |         "./dist/plugins/jwt/index.d.ts"
676 |       ],
677 |       "plugins/magic-link": [
678 |         "./dist/plugins/magic-link/index.d.ts"
679 |       ],
680 |       "plugins/organization": [
681 |         "./dist/plugins/organization/index.d.ts"
682 |       ],
683 |       "plugins/organization/access": [
684 |         "./dist/plugins/organization/access/index.d.ts"
685 |       ],
686 |       "plugins/passkey": [
687 |         "./dist/plugins/passkey/index.d.ts"
688 |       ],
689 |       "plugins/phone-number": [
690 |         "./dist/plugins/phone-number/index.d.ts"
691 |       ],
692 |       "plugins/two-factor": [
693 |         "./dist/plugins/two-factor/index.d.ts"
694 |       ],
695 |       "plugins/username": [
696 |         "./dist/plugins/username/index.d.ts"
697 |       ],
698 |       "plugins/siwe": [
699 |         "./dist/plugins/siwe/index.d.ts"
700 |       ],
701 |       "plugins/device-authorization": [
702 |         "./dist/plugins/device-authorization/index.d.ts"
703 |       ]
704 |     }
705 |   },
706 |   "dependencies": {
707 |     "@better-auth/core": "workspace:*",
708 |     "@better-auth/telemetry": "workspace:*",
709 |     "@better-auth/utils": "0.3.0",
710 |     "@better-fetch/fetch": "catalog:",
711 |     "@noble/ciphers": "^2.0.0",
712 |     "@noble/hashes": "^2.0.0",
713 |     "@simplewebauthn/browser": "^13.1.2",
714 |     "@simplewebauthn/server": "^13.1.2",
715 |     "better-call": "catalog:",
716 |     "defu": "^6.1.4",
717 |     "jose": "^6.1.0",
718 |     "kysely": "^0.28.5",
719 |     "nanostores": "^1.0.1",
720 |     "zod": "^4.1.5"
721 |   },
722 |   "peerDependenciesOptional": {
723 |     "@lynx-js/react": "*",
724 |     "@sveltejs/kit": "^2.0.0",
725 |     "next": "^14.0.0 || ^15.0.0",
726 |     "react": "^18.0.0 || ^19.0.0",
727 |     "react-dom": "^18.0.0 || ^19.0.0",
728 |     "solid-js": "^1.0.0",
729 |     "svelte": "^4.0.0 || ^5.0.0",
730 |     "vue": "^3.0.0"
731 |   },
732 |   "peerDependenciesMeta": {
733 |     "@lynx-js/react": {
734 |       "optional": true
735 |     },
736 |     "@sveltejs/kit": {
737 |       "optional": true
738 |     },
739 |     "next": {
740 |       "optional": true
741 |     },
742 |     "react": {
743 |       "optional": true
744 |     },
745 |     "react-dom": {
746 |       "optional": true
747 |     },
748 |     "solid-js": {
749 |       "optional": true
750 |     },
751 |     "svelte": {
752 |       "optional": true
753 |     },
754 |     "vue": {
755 |       "optional": true
756 |     }
757 |   },
758 |   "devDependencies": {
759 |     "@lynx-js/react": "^0.114.0",
760 |     "@prisma/client": "^5.22.0",
761 |     "@sveltejs/kit": "^2.37.1",
762 |     "@tanstack/react-start": "^1.131.3",
763 |     "@tanstack/start-server-core": "^1.131.36",
764 |     "@types/better-sqlite3": "^7.6.13",
765 |     "@types/bun": "^1.2.23",
766 |     "@types/keccak": "^3.0.5",
767 |     "@types/pg": "^8.15.5",
768 |     "@types/prompts": "^2.4.9",
769 |     "@types/react": "^19.2.2",
770 |     "better-sqlite3": "^12.2.0",
771 |     "concurrently": "^9.2.1",
772 |     "deepmerge": "^4.3.1",
773 |     "drizzle-kit": "^0.31.4",
774 |     "drizzle-orm": "^0.38.2",
775 |     "happy-dom": "^20.0.0",
776 |     "hono": "^4.9.7",
777 |     "listhen": "^1.9.0",
778 |     "mongodb": "^6.18.0",
779 |     "ms": "4.0.0-nightly.202508271359",
780 |     "msw": "^2.11.5",
781 |     "mysql2": "^3.14.4",
782 |     "next": "^15.5.4",
783 |     "oauth2-mock-server": "^7.2.1",
784 |     "pg": "^8.16.3",
785 |     "prisma": "^5.22.0",
786 |     "react": "^19.2.0",
787 |     "react-dom": "^19.2.0",
788 |     "react-native": "~0.80.2",
789 |     "solid-js": "^1.9.8",
790 |     "tarn": "^3.0.2",
791 |     "tedious": "^18.6.1",
792 |     "tsdown": "catalog:",
793 |     "type-fest": "^4.41.0",
794 |     "typescript": "catalog:",
795 |     "vue": "^3.5.18"
796 |   },
797 |   "files": [
798 |     "dist"
799 |   ]
800 | }
801 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/plugins/organization/routes/crud-org.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as z from "zod";
  2 | import { createAuthEndpoint } from "@better-auth/core/middleware";
  3 | import { getOrgAdapter } from "../adapter";
  4 | import { orgMiddleware, orgSessionMiddleware } from "../call";
  5 | import { APIError } from "better-call";
  6 | import { setSessionCookie } from "../../../cookies";
  7 | import { ORGANIZATION_ERROR_CODES } from "../error-codes";
  8 | import { getSessionFromCtx, requestOnlySessionMiddleware } from "../../../api";
  9 | import type { OrganizationOptions } from "../types";
 10 | import type {
 11 | 	InferInvitation,
 12 | 	InferMember,
 13 | 	InferOrganization,
 14 | 	Member,
 15 | 	Team,
 16 | 	TeamMember,
 17 | } from "../schema";
 18 | import { hasPermission } from "../has-permission";
 19 | import {
 20 | 	toZodSchema,
 21 | 	type InferAdditionalFieldsFromPluginOptions,
 22 | } from "../../../db";
 23 | 
 24 | export const createOrganization = <O extends OrganizationOptions>(
 25 | 	options?: O,
 26 | ) => {
 27 | 	const additionalFieldsSchema = toZodSchema({
 28 | 		fields: options?.schema?.organization?.additionalFields || {},
 29 | 		isClientSide: true,
 30 | 	});
 31 | 	const baseSchema = z.object({
 32 | 		name: z.string().min(1).meta({
 33 | 			description: "The name of the organization",
 34 | 		}),
 35 | 		slug: z.string().min(1).meta({
 36 | 			description: "The slug of the organization",
 37 | 		}),
 38 | 		userId: z.coerce
 39 | 			.string()
 40 | 			.meta({
 41 | 				description:
 42 | 					'The user id of the organization creator. If not provided, the current user will be used. Should only be used by admins or when called by the server. server-only. Eg: "user-id"',
 43 | 			})
 44 | 			.optional(),
 45 | 		logo: z
 46 | 			.string()
 47 | 			.meta({
 48 | 				description: "The logo of the organization",
 49 | 			})
 50 | 			.optional(),
 51 | 		metadata: z
 52 | 			.record(z.string(), z.any())
 53 | 			.meta({
 54 | 				description: "The metadata of the organization",
 55 | 			})
 56 | 			.optional(),
 57 | 		keepCurrentActiveOrganization: z
 58 | 			.boolean()
 59 | 			.meta({
 60 | 				description:
 61 | 					"Whether to keep the current active organization active after creating a new one. Eg: true",
 62 | 			})
 63 | 			.optional(),
 64 | 	});
 65 | 
 66 | 	type Body = InferAdditionalFieldsFromPluginOptions<"organization", O> &
 67 | 		z.infer<typeof baseSchema>;
 68 | 
 69 | 	return createAuthEndpoint(
 70 | 		"/organization/create",
 71 | 		{
 72 | 			method: "POST",
 73 | 			body: z.object({
 74 | 				...baseSchema.shape,
 75 | 				...additionalFieldsSchema.shape,
 76 | 			}),
 77 | 			use: [orgMiddleware],
 78 | 			metadata: {
 79 | 				$Infer: {
 80 | 					body: {} as Body,
 81 | 				},
 82 | 				openapi: {
 83 | 					description: "Create an organization",
 84 | 					responses: {
 85 | 						"200": {
 86 | 							description: "Success",
 87 | 							content: {
 88 | 								"application/json": {
 89 | 									schema: {
 90 | 										type: "object",
 91 | 										description: "The organization that was created",
 92 | 										$ref: "#/components/schemas/Organization",
 93 | 									},
 94 | 								},
 95 | 							},
 96 | 						},
 97 | 					},
 98 | 				},
 99 | 			},
100 | 		},
101 | 		async (ctx) => {
102 | 			const session = await getSessionFromCtx(ctx);
103 | 
104 | 			if (!session && (ctx.request || ctx.headers)) {
105 | 				throw new APIError("UNAUTHORIZED");
106 | 			}
107 | 			let user = session?.user || null;
108 | 			if (!user) {
109 | 				if (!ctx.body.userId) {
110 | 					throw new APIError("UNAUTHORIZED");
111 | 				}
112 | 				user = await ctx.context.internalAdapter.findUserById(ctx.body.userId);
113 | 			}
114 | 			if (!user) {
115 | 				return ctx.json(null, {
116 | 					status: 401,
117 | 				});
118 | 			}
119 | 			const options = ctx.context.orgOptions;
120 | 			const canCreateOrg =
121 | 				typeof options?.allowUserToCreateOrganization === "function"
122 | 					? await options.allowUserToCreateOrganization(user)
123 | 					: options?.allowUserToCreateOrganization === undefined
124 | 						? true
125 | 						: options.allowUserToCreateOrganization;
126 | 
127 | 			if (!canCreateOrg) {
128 | 				throw new APIError("FORBIDDEN", {
129 | 					message:
130 | 						ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION,
131 | 				});
132 | 			}
133 | 			const adapter = getOrgAdapter<O>(ctx.context, options as O);
134 | 
135 | 			const userOrganizations = await adapter.listOrganizations(user.id);
136 | 			const hasReachedOrgLimit =
137 | 				typeof options.organizationLimit === "number"
138 | 					? userOrganizations.length >= options.organizationLimit
139 | 					: typeof options.organizationLimit === "function"
140 | 						? await options.organizationLimit(user)
141 | 						: false;
142 | 
143 | 			if (hasReachedOrgLimit) {
144 | 				throw new APIError("FORBIDDEN", {
145 | 					message:
146 | 						ORGANIZATION_ERROR_CODES.YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS,
147 | 				});
148 | 			}
149 | 
150 | 			const existingOrganization = await adapter.findOrganizationBySlug(
151 | 				ctx.body.slug,
152 | 			);
153 | 			if (existingOrganization) {
154 | 				throw new APIError("BAD_REQUEST", {
155 | 					message: ORGANIZATION_ERROR_CODES.ORGANIZATION_ALREADY_EXISTS,
156 | 				});
157 | 			}
158 | 
159 | 			let {
160 | 				keepCurrentActiveOrganization: _,
161 | 				userId: __,
162 | 				...orgData
163 | 			} = ctx.body;
164 | 
165 | 			if (options.organizationCreation?.beforeCreate) {
166 | 				const response = await options.organizationCreation.beforeCreate(
167 | 					{
168 | 						organization: {
169 | 							...orgData,
170 | 							createdAt: new Date(),
171 | 						},
172 | 						user,
173 | 					},
174 | 					ctx.request,
175 | 				);
176 | 				if (response && typeof response === "object" && "data" in response) {
177 | 					orgData = {
178 | 						...ctx.body,
179 | 						...response.data,
180 | 					};
181 | 				}
182 | 			}
183 | 
184 | 			if (options?.organizationHooks?.beforeCreateOrganization) {
185 | 				const response =
186 | 					await options?.organizationHooks.beforeCreateOrganization({
187 | 						organization: orgData,
188 | 						user,
189 | 					});
190 | 				if (response && typeof response === "object" && "data" in response) {
191 | 					orgData = {
192 | 						...ctx.body,
193 | 						...response.data,
194 | 					};
195 | 				}
196 | 			}
197 | 
198 | 			const organization = await adapter.createOrganization({
199 | 				organization: {
200 | 					...orgData,
201 | 					createdAt: new Date(),
202 | 				},
203 | 			});
204 | 
205 | 			let member:
206 | 				| (Member & InferAdditionalFieldsFromPluginOptions<"member", O, false>)
207 | 				| undefined;
208 | 			let teamMember: TeamMember | null = null;
209 | 			let data = {
210 | 				userId: user.id,
211 | 				organizationId: organization.id,
212 | 				role: ctx.context.orgOptions.creatorRole || "owner",
213 | 			};
214 | 			if (options?.organizationHooks?.beforeAddMember) {
215 | 				const response = await options?.organizationHooks.beforeAddMember({
216 | 					member: {
217 | 						userId: user.id,
218 | 						organizationId: organization.id,
219 | 						role: ctx.context.orgOptions.creatorRole || "owner",
220 | 					},
221 | 					user,
222 | 					organization,
223 | 				});
224 | 				if (response && typeof response === "object" && "data" in response) {
225 | 					data = {
226 | 						...data,
227 | 						...response.data,
228 | 					};
229 | 				}
230 | 			}
231 | 			member = await adapter.createMember(data);
232 | 			if (options?.organizationHooks?.afterAddMember) {
233 | 				await options?.organizationHooks.afterAddMember({
234 | 					member,
235 | 					user,
236 | 					organization,
237 | 				});
238 | 			}
239 | 			if (
240 | 				options?.teams?.enabled &&
241 | 				options.teams.defaultTeam?.enabled !== false
242 | 			) {
243 | 				let teamData = {
244 | 					organizationId: organization.id,
245 | 					name: `${organization.name}`,
246 | 					createdAt: new Date(),
247 | 				};
248 | 				if (options?.organizationHooks?.beforeCreateTeam) {
249 | 					const response = await options?.organizationHooks.beforeCreateTeam({
250 | 						team: {
251 | 							organizationId: organization.id,
252 | 							name: `${organization.name}`,
253 | 						},
254 | 						user,
255 | 						organization,
256 | 					});
257 | 					if (response && typeof response === "object" && "data" in response) {
258 | 						teamData = {
259 | 							...teamData,
260 | 							...response.data,
261 | 						};
262 | 					}
263 | 				}
264 | 				const defaultTeam =
265 | 					(await options.teams.defaultTeam?.customCreateDefaultTeam?.(
266 | 						organization,
267 | 						ctx.request,
268 | 					)) || (await adapter.createTeam(teamData));
269 | 
270 | 				teamMember = await adapter.findOrCreateTeamMember({
271 | 					teamId: defaultTeam.id,
272 | 					userId: user.id,
273 | 				});
274 | 
275 | 				if (options?.organizationHooks?.afterCreateTeam) {
276 | 					await options?.organizationHooks.afterCreateTeam({
277 | 						team: defaultTeam,
278 | 						user,
279 | 						organization,
280 | 					});
281 | 				}
282 | 			}
283 | 
284 | 			if (options.organizationCreation?.afterCreate) {
285 | 				await options.organizationCreation.afterCreate(
286 | 					{
287 | 						organization,
288 | 						user,
289 | 						member,
290 | 					},
291 | 					ctx.request,
292 | 				);
293 | 			}
294 | 
295 | 			if (options?.organizationHooks?.afterCreateOrganization) {
296 | 				await options?.organizationHooks.afterCreateOrganization({
297 | 					organization,
298 | 					user,
299 | 					member,
300 | 				});
301 | 			}
302 | 
303 | 			if (ctx.context.session && !ctx.body.keepCurrentActiveOrganization) {
304 | 				await adapter.setActiveOrganization(
305 | 					ctx.context.session.session.token,
306 | 					organization.id,
307 | 					ctx,
308 | 				);
309 | 			}
310 | 
311 | 			if (
312 | 				teamMember &&
313 | 				ctx.context.session &&
314 | 				!ctx.body.keepCurrentActiveOrganization
315 | 			) {
316 | 				await adapter.setActiveTeam(
317 | 					ctx.context.session.session.token,
318 | 					teamMember.teamId,
319 | 					ctx,
320 | 				);
321 | 			}
322 | 
323 | 			return ctx.json({
324 | 				...organization,
325 | 				metadata:
326 | 					organization.metadata && typeof organization.metadata === "string"
327 | 						? JSON.parse(organization.metadata)
328 | 						: organization.metadata,
329 | 				members: [member],
330 | 			});
331 | 		},
332 | 	);
333 | };
334 | 
335 | export const checkOrganizationSlug = <O extends OrganizationOptions>(
336 | 	options: O,
337 | ) =>
338 | 	createAuthEndpoint(
339 | 		"/organization/check-slug",
340 | 		{
341 | 			method: "POST",
342 | 			body: z.object({
343 | 				slug: z.string().meta({
344 | 					description: 'The organization slug to check. Eg: "my-org"',
345 | 				}),
346 | 			}),
347 | 			use: [requestOnlySessionMiddleware, orgMiddleware],
348 | 		},
349 | 		async (ctx) => {
350 | 			const orgAdapter = getOrgAdapter<O>(ctx.context, options);
351 | 			const org = await orgAdapter.findOrganizationBySlug(ctx.body.slug);
352 | 			if (!org) {
353 | 				return ctx.json({
354 | 					status: true,
355 | 				});
356 | 			}
357 | 			throw new APIError("BAD_REQUEST", {
358 | 				message: "slug is taken",
359 | 			});
360 | 		},
361 | 	);
362 | 
363 | export const updateOrganization = <O extends OrganizationOptions>(
364 | 	options?: O,
365 | ) => {
366 | 	const additionalFieldsSchema = toZodSchema({
367 | 		fields: options?.schema?.organization?.additionalFields || {},
368 | 		isClientSide: true,
369 | 	});
370 | 	type Body = {
371 | 		data: {
372 | 			name?: string;
373 | 			slug?: string;
374 | 			logo?: string;
375 | 			metadata?: Record<string, any>;
376 | 		} & Partial<InferAdditionalFieldsFromPluginOptions<"organization", O>>;
377 | 		organizationId?: string | undefined;
378 | 	};
379 | 	return createAuthEndpoint(
380 | 		"/organization/update",
381 | 		{
382 | 			method: "POST",
383 | 			body: z.object({
384 | 				data: z
385 | 					.object({
386 | 						...additionalFieldsSchema.shape,
387 | 						name: z
388 | 							.string()
389 | 							.min(1)
390 | 							.meta({
391 | 								description: "The name of the organization",
392 | 							})
393 | 							.optional(),
394 | 						slug: z
395 | 							.string()
396 | 							.min(1)
397 | 							.meta({
398 | 								description: "The slug of the organization",
399 | 							})
400 | 							.optional(),
401 | 						logo: z
402 | 							.string()
403 | 							.meta({
404 | 								description: "The logo of the organization",
405 | 							})
406 | 							.optional(),
407 | 						metadata: z
408 | 							.record(z.string(), z.any())
409 | 							.meta({
410 | 								description: "The metadata of the organization",
411 | 							})
412 | 							.optional(),
413 | 					})
414 | 					.partial(),
415 | 				organizationId: z
416 | 					.string()
417 | 					.meta({
418 | 						description: 'The organization ID. Eg: "org-id"',
419 | 					})
420 | 					.optional(),
421 | 			}),
422 | 			requireHeaders: true,
423 | 			use: [orgMiddleware],
424 | 			metadata: {
425 | 				$Infer: {
426 | 					body: {} as Body,
427 | 				},
428 | 				openapi: {
429 | 					description: "Update an organization",
430 | 					responses: {
431 | 						"200": {
432 | 							description: "Success",
433 | 							content: {
434 | 								"application/json": {
435 | 									schema: {
436 | 										type: "object",
437 | 										description: "The updated organization",
438 | 										$ref: "#/components/schemas/Organization",
439 | 									},
440 | 								},
441 | 							},
442 | 						},
443 | 					},
444 | 				},
445 | 			},
446 | 		},
447 | 		async (ctx) => {
448 | 			const session = await ctx.context.getSession(ctx);
449 | 			if (!session) {
450 | 				throw new APIError("UNAUTHORIZED", {
451 | 					message: "User not found",
452 | 				});
453 | 			}
454 | 			const organizationId =
455 | 				ctx.body.organizationId || session.session.activeOrganizationId;
456 | 			if (!organizationId) {
457 | 				throw new APIError("BAD_REQUEST", {
458 | 					message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
459 | 				});
460 | 			}
461 | 			const adapter = getOrgAdapter<O>(ctx.context, options);
462 | 			const member = await adapter.findMemberByOrgId({
463 | 				userId: session.user.id,
464 | 				organizationId: organizationId,
465 | 			});
466 | 			if (!member) {
467 | 				throw new APIError("BAD_REQUEST", {
468 | 					message:
469 | 						ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION,
470 | 				});
471 | 			}
472 | 			const canUpdateOrg = await hasPermission(
473 | 				{
474 | 					permissions: {
475 | 						organization: ["update"],
476 | 					},
477 | 					role: member.role,
478 | 					options: ctx.context.orgOptions,
479 | 					organizationId,
480 | 				},
481 | 				ctx,
482 | 			);
483 | 			if (!canUpdateOrg) {
484 | 				throw new APIError("FORBIDDEN", {
485 | 					message:
486 | 						ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION,
487 | 				});
488 | 			}
489 | 			if (options?.organizationHooks?.beforeUpdateOrganization) {
490 | 				const response =
491 | 					await options.organizationHooks.beforeUpdateOrganization({
492 | 						organization: ctx.body.data,
493 | 						user: session.user,
494 | 						member,
495 | 					});
496 | 				if (response && typeof response === "object" && "data" in response) {
497 | 					ctx.body.data = {
498 | 						...ctx.body.data,
499 | 						...response.data,
500 | 					};
501 | 				}
502 | 			}
503 | 			const updatedOrg = await adapter.updateOrganization(
504 | 				organizationId,
505 | 				ctx.body.data,
506 | 			);
507 | 			if (options?.organizationHooks?.afterUpdateOrganization) {
508 | 				await options.organizationHooks.afterUpdateOrganization({
509 | 					organization: updatedOrg,
510 | 					user: session.user,
511 | 					member,
512 | 				});
513 | 			}
514 | 			return ctx.json(updatedOrg);
515 | 		},
516 | 	);
517 | };
518 | 
519 | export const deleteOrganization = <O extends OrganizationOptions>(
520 | 	options: O,
521 | ) => {
522 | 	return createAuthEndpoint(
523 | 		"/organization/delete",
524 | 		{
525 | 			method: "POST",
526 | 			body: z.object({
527 | 				organizationId: z.string().meta({
528 | 					description: "The organization id to delete",
529 | 				}),
530 | 			}),
531 | 			requireHeaders: true,
532 | 			use: [orgMiddleware],
533 | 			metadata: {
534 | 				openapi: {
535 | 					description: "Delete an organization",
536 | 					responses: {
537 | 						"200": {
538 | 							description: "Success",
539 | 							content: {
540 | 								"application/json": {
541 | 									schema: {
542 | 										type: "string",
543 | 										description: "The organization id that was deleted",
544 | 									},
545 | 								},
546 | 							},
547 | 						},
548 | 					},
549 | 				},
550 | 			},
551 | 		},
552 | 		async (ctx) => {
553 | 			const disableOrganizationDeletion =
554 | 				ctx.context.orgOptions.organizationDeletion?.disabled ||
555 | 				ctx.context.orgOptions.disableOrganizationDeletion;
556 | 			if (disableOrganizationDeletion) {
557 | 				if (ctx.context.orgOptions.organizationDeletion?.disabled) {
558 | 					ctx.context.logger.info(
559 | 						"`organizationDeletion.disabled` is deprecated. Use `disableOrganizationDeletion` instead",
560 | 					);
561 | 				}
562 | 				throw new APIError("NOT_FOUND", {
563 | 					message: "Organization deletion is disabled",
564 | 				});
565 | 			}
566 | 			const session = await ctx.context.getSession(ctx);
567 | 			if (!session) {
568 | 				throw new APIError("UNAUTHORIZED", { status: 401 });
569 | 			}
570 | 
571 | 			const organizationId = ctx.body.organizationId;
572 | 			if (!organizationId) {
573 | 				return ctx.json(null, {
574 | 					status: 400,
575 | 					body: {
576 | 						message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
577 | 					},
578 | 				});
579 | 			}
580 | 			const adapter = getOrgAdapter<O>(ctx.context, options);
581 | 			const member = await adapter.findMemberByOrgId({
582 | 				userId: session.user.id,
583 | 				organizationId: organizationId,
584 | 			});
585 | 			if (!member) {
586 | 				throw new APIError("BAD_REQUEST", {
587 | 					message:
588 | 						ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION,
589 | 				});
590 | 			}
591 | 			const canDeleteOrg = await hasPermission(
592 | 				{
593 | 					role: member.role,
594 | 					permissions: {
595 | 						organization: ["delete"],
596 | 					},
597 | 					organizationId,
598 | 					options: ctx.context.orgOptions,
599 | 				},
600 | 				ctx,
601 | 			);
602 | 			if (!canDeleteOrg) {
603 | 				throw new APIError("FORBIDDEN", {
604 | 					message:
605 | 						ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION,
606 | 				});
607 | 			}
608 | 			if (organizationId === session.session.activeOrganizationId) {
609 | 				/**
610 | 				 * If the organization is deleted, we set the active organization to null
611 | 				 */
612 | 				await adapter.setActiveOrganization(session.session.token, null, ctx);
613 | 			}
614 | 
615 | 			const org = await adapter.findOrganizationById(organizationId);
616 | 			if (!org) {
617 | 				throw new APIError("BAD_REQUEST");
618 | 			}
619 | 			if (options?.organizationHooks?.beforeDeleteOrganization) {
620 | 				await options.organizationHooks.beforeDeleteOrganization({
621 | 					organization: org,
622 | 					user: session.user,
623 | 				});
624 | 			}
625 | 			await adapter.deleteOrganization(organizationId);
626 | 			if (options?.organizationHooks?.afterDeleteOrganization) {
627 | 				await options.organizationHooks.afterDeleteOrganization({
628 | 					organization: org,
629 | 					user: session.user,
630 | 				});
631 | 			}
632 | 			return ctx.json(org);
633 | 		},
634 | 	);
635 | };
636 | export const getFullOrganization = <O extends OrganizationOptions>(
637 | 	options: O,
638 | ) =>
639 | 	createAuthEndpoint(
640 | 		"/organization/get-full-organization",
641 | 		{
642 | 			method: "GET",
643 | 			query: z.optional(
644 | 				z.object({
645 | 					organizationId: z
646 | 						.string()
647 | 						.meta({
648 | 							description: "The organization id to get",
649 | 						})
650 | 						.optional(),
651 | 					organizationSlug: z
652 | 						.string()
653 | 						.meta({
654 | 							description: "The organization slug to get",
655 | 						})
656 | 						.optional(),
657 | 					membersLimit: z
658 | 						.number()
659 | 						.or(z.string().transform((val) => parseInt(val)))
660 | 						.meta({
661 | 							description:
662 | 								"The limit of members to get. By default, it uses the membershipLimit option which defaults to 100.",
663 | 						})
664 | 						.optional(),
665 | 				}),
666 | 			),
667 | 			requireHeaders: true,
668 | 			use: [orgMiddleware, orgSessionMiddleware],
669 | 			metadata: {
670 | 				openapi: {
671 | 					description: "Get the full organization",
672 | 					responses: {
673 | 						"200": {
674 | 							description: "Success",
675 | 							content: {
676 | 								"application/json": {
677 | 									schema: {
678 | 										type: "object",
679 | 										description: "The organization",
680 | 										$ref: "#/components/schemas/Organization",
681 | 									},
682 | 								},
683 | 							},
684 | 						},
685 | 					},
686 | 				},
687 | 			},
688 | 		},
689 | 		async (ctx) => {
690 | 			const session = ctx.context.session;
691 | 			const organizationId =
692 | 				ctx.query?.organizationSlug ||
693 | 				ctx.query?.organizationId ||
694 | 				session.session.activeOrganizationId;
695 | 			// return null if no organization is found to avoid erroring since this is a usual scenario
696 | 			if (!organizationId) {
697 | 				ctx.context.logger.info("No active organization found, returning null");
698 | 				return ctx.json(null, {
699 | 					status: 200,
700 | 				});
701 | 			}
702 | 			const adapter = getOrgAdapter<O>(ctx.context, options);
703 | 			const organization = await adapter.findFullOrganization({
704 | 				organizationId,
705 | 				isSlug: !!ctx.query?.organizationSlug,
706 | 				includeTeams: ctx.context.orgOptions.teams?.enabled,
707 | 				membersLimit: ctx.query?.membersLimit,
708 | 			});
709 | 			if (!organization) {
710 | 				throw new APIError("BAD_REQUEST", {
711 | 					message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
712 | 				});
713 | 			}
714 | 			const isMember = await adapter.checkMembership({
715 | 				userId: session.user.id,
716 | 				organizationId: organization.id,
717 | 			});
718 | 			if (!isMember) {
719 | 				await adapter.setActiveOrganization(session.session.token, null, ctx);
720 | 				throw new APIError("FORBIDDEN", {
721 | 					message:
722 | 						ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION,
723 | 				});
724 | 			}
725 | 			type OrganizationReturn = O["teams"] extends { enabled: true }
726 | 				? {
727 | 						members: InferMember<O>[];
728 | 						invitations: InferInvitation<O>[];
729 | 						teams: Team[];
730 | 					} & InferOrganization<O>
731 | 				: {
732 | 						members: InferMember<O>[];
733 | 						invitations: InferInvitation<O>[];
734 | 					} & InferOrganization<O>;
735 | 			return ctx.json(organization as unknown as OrganizationReturn);
736 | 		},
737 | 	);
738 | 
739 | export const setActiveOrganization = <O extends OrganizationOptions>(
740 | 	options: O,
741 | ) => {
742 | 	return createAuthEndpoint(
743 | 		"/organization/set-active",
744 | 		{
745 | 			method: "POST",
746 | 			body: z.object({
747 | 				organizationId: z
748 | 					.string()
749 | 					.meta({
750 | 						description:
751 | 							'The organization id to set as active. It can be null to unset the active organization. Eg: "org-id"',
752 | 					})
753 | 					.nullable()
754 | 					.optional(),
755 | 				organizationSlug: z
756 | 					.string()
757 | 					.meta({
758 | 						description:
759 | 							'The organization slug to set as active. It can be null to unset the active organization if organizationId is not provided. Eg: "org-slug"',
760 | 					})
761 | 					.optional(),
762 | 			}),
763 | 			use: [orgSessionMiddleware, orgMiddleware],
764 | 			metadata: {
765 | 				openapi: {
766 | 					description: "Set the active organization",
767 | 					responses: {
768 | 						"200": {
769 | 							description: "Success",
770 | 							content: {
771 | 								"application/json": {
772 | 									schema: {
773 | 										type: "object",
774 | 										description: "The organization",
775 | 										$ref: "#/components/schemas/Organization",
776 | 									},
777 | 								},
778 | 							},
779 | 						},
780 | 					},
781 | 				},
782 | 			},
783 | 		},
784 | 		async (ctx) => {
785 | 			const adapter = getOrgAdapter<O>(ctx.context, options);
786 | 			const session = ctx.context.session;
787 | 			let organizationId = ctx.body.organizationId;
788 | 			let organizationSlug = ctx.body.organizationSlug;
789 | 
790 | 			if (organizationId === null) {
791 | 				const sessionOrgId = session.session.activeOrganizationId;
792 | 				if (!sessionOrgId) {
793 | 					return ctx.json(null);
794 | 				}
795 | 				const updatedSession = await adapter.setActiveOrganization(
796 | 					session.session.token,
797 | 					null,
798 | 					ctx,
799 | 				);
800 | 				await setSessionCookie(ctx, {
801 | 					session: updatedSession,
802 | 					user: session.user,
803 | 				});
804 | 				return ctx.json(null);
805 | 			}
806 | 
807 | 			if (!organizationId && !organizationSlug) {
808 | 				const sessionOrgId = session.session.activeOrganizationId;
809 | 				if (!sessionOrgId) {
810 | 					return ctx.json(null);
811 | 				}
812 | 				organizationId = sessionOrgId;
813 | 			}
814 | 
815 | 			if (organizationSlug && !organizationId) {
816 | 				const organization =
817 | 					await adapter.findOrganizationBySlug(organizationSlug);
818 | 				if (!organization) {
819 | 					throw new APIError("BAD_REQUEST", {
820 | 						message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
821 | 					});
822 | 				}
823 | 				organizationId = organization.id;
824 | 			}
825 | 
826 | 			if (!organizationId) {
827 | 				throw new APIError("BAD_REQUEST", {
828 | 					message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
829 | 				});
830 | 			}
831 | 
832 | 			const isMember = await adapter.checkMembership({
833 | 				userId: session.user.id,
834 | 				organizationId,
835 | 			});
836 | 			if (!isMember) {
837 | 				await adapter.setActiveOrganization(session.session.token, null, ctx);
838 | 				throw new APIError("FORBIDDEN", {
839 | 					message:
840 | 						ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION,
841 | 				});
842 | 			}
843 | 
844 | 			let organization = await adapter.findOrganizationById(organizationId);
845 | 			if (!organization) {
846 | 				throw new APIError("BAD_REQUEST", {
847 | 					message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
848 | 				});
849 | 			}
850 | 			const updatedSession = await adapter.setActiveOrganization(
851 | 				session.session.token,
852 | 				organization.id,
853 | 				ctx,
854 | 			);
855 | 			await setSessionCookie(ctx, {
856 | 				session: updatedSession,
857 | 				user: session.user,
858 | 			});
859 | 			type OrganizationReturn = O["teams"] extends { enabled: true }
860 | 				? {
861 | 						members: InferMember<O>[];
862 | 						invitations: InferInvitation<O>[];
863 | 						teams: Team[];
864 | 					} & InferOrganization<O>
865 | 				: {
866 | 						members: InferMember<O>[];
867 | 						invitations: InferInvitation<O>[];
868 | 					} & InferOrganization<O>;
869 | 			return ctx.json(organization as unknown as OrganizationReturn);
870 | 		},
871 | 	);
872 | };
873 | 
874 | export const listOrganizations = <O extends OrganizationOptions>(options: O) =>
875 | 	createAuthEndpoint(
876 | 		"/organization/list",
877 | 		{
878 | 			method: "GET",
879 | 			use: [orgMiddleware, orgSessionMiddleware],
880 | 			metadata: {
881 | 				openapi: {
882 | 					description: "List all organizations",
883 | 					responses: {
884 | 						"200": {
885 | 							description: "Success",
886 | 							content: {
887 | 								"application/json": {
888 | 									schema: {
889 | 										type: "array",
890 | 										items: {
891 | 											$ref: "#/components/schemas/Organization",
892 | 										},
893 | 									},
894 | 								},
895 | 							},
896 | 						},
897 | 					},
898 | 				},
899 | 			},
900 | 		},
901 | 		async (ctx) => {
902 | 			const adapter = getOrgAdapter<O>(ctx.context, options);
903 | 			const organizations = await adapter.listOrganizations(
904 | 				ctx.context.session.user.id,
905 | 			);
906 | 			return ctx.json(organizations);
907 | 		},
908 | 	);
909 | 
```
Page 44/67FirstPrevNextLast