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

# Directory Structure

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

# Files

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

```typescript
   1 | import { describe, expect, it, vi } from "vitest";
   2 | import { getTestInstance } from "../../test-utils/test-instance";
   3 | import { apiKey, ERROR_CODES } from ".";
   4 | import { apiKeyClient } from "./client";
   5 | import type { ApiKey } from "./types";
   6 | import { APIError } from "better-call";
   7 | 
   8 | describe("api-key", async () => {
   9 | 	const { client, auth, signInWithTestUser } = await getTestInstance(
  10 | 		{
  11 | 			plugins: [
  12 | 				apiKey({
  13 | 					enableMetadata: true,
  14 | 					permissions: {
  15 | 						defaultPermissions: {
  16 | 							files: ["read"],
  17 | 						},
  18 | 					},
  19 | 				}),
  20 | 			],
  21 | 		},
  22 | 		{
  23 | 			clientOptions: {
  24 | 				plugins: [apiKeyClient()],
  25 | 			},
  26 | 		},
  27 | 	);
  28 | 	const { headers, user } = await signInWithTestUser();
  29 | 
  30 | 	// =========================================================================
  31 | 	// CREATE API KEY
  32 | 	// =========================================================================
  33 | 
  34 | 	it("should fail to create API keys from client without headers", async () => {
  35 | 		const apiKeyFail = await client.apiKey.create();
  36 | 
  37 | 		expect(apiKeyFail.data).toBeNull();
  38 | 		expect(apiKeyFail.error).toBeDefined();
  39 | 		expect(apiKeyFail.error?.status).toEqual(401);
  40 | 		expect(apiKeyFail.error?.statusText).toEqual("UNAUTHORIZED");
  41 | 		expect(apiKeyFail.error?.message).toEqual(ERROR_CODES.UNAUTHORIZED_SESSION);
  42 | 	});
  43 | 
  44 | 	let firstApiKey: ApiKey;
  45 | 
  46 | 	it("should successfully create API keys from client with headers", async () => {
  47 | 		const apiKey = await client.apiKey.create({}, { headers: headers });
  48 | 		if (apiKey.data) {
  49 | 			firstApiKey = apiKey.data;
  50 | 		}
  51 | 
  52 | 		expect(apiKey.data).not.toBeNull();
  53 | 		expect(apiKey.data?.key).toBeDefined();
  54 | 		expect(apiKey.data?.userId).toEqual(user.id);
  55 | 		expect(apiKey.data?.name).toBeNull();
  56 | 		expect(apiKey.data?.prefix).toBeNull();
  57 | 		expect(apiKey.data?.refillInterval).toBeNull();
  58 | 		expect(apiKey.data?.refillAmount).toBeNull();
  59 | 		expect(apiKey.data?.lastRefillAt).toBeNull();
  60 | 		expect(apiKey.data?.enabled).toEqual(true);
  61 | 		expect(apiKey.data?.rateLimitTimeWindow).toEqual(86400000);
  62 | 		expect(apiKey.data?.rateLimitMax).toEqual(10);
  63 | 		expect(apiKey.data?.requestCount).toEqual(0);
  64 | 		expect(apiKey.data?.remaining).toBeNull();
  65 | 		expect(apiKey.data?.lastRequest).toBeNull();
  66 | 		expect(apiKey.data?.expiresAt).toBeNull();
  67 | 		expect(apiKey.data?.createdAt).toBeDefined();
  68 | 		expect(apiKey.data?.updatedAt).toBeDefined();
  69 | 		expect(apiKey.data?.metadata).toBeNull();
  70 | 		expect(apiKey.error).toBeNull();
  71 | 	});
  72 | 
  73 | 	interface Err {
  74 | 		body: {
  75 | 			code: string | undefined;
  76 | 			message: string | undefined;
  77 | 		};
  78 | 		status: string;
  79 | 		statusCode: string;
  80 | 	}
  81 | 
  82 | 	it("should fail to create API Keys from server without headers and userId", async () => {
  83 | 		let res: { data: ApiKey | null; error: Err | null } = {
  84 | 			data: null,
  85 | 			error: null,
  86 | 		};
  87 | 		try {
  88 | 			const apiKey = await auth.api.createApiKey({ body: {} });
  89 | 			res.data = apiKey;
  90 | 		} catch (error: any) {
  91 | 			res.error = error;
  92 | 		}
  93 | 
  94 | 		expect(res.data).toBeNull();
  95 | 		expect(res.error).toBeDefined();
  96 | 		expect(res.error?.statusCode).toEqual(401);
  97 | 		expect(res.error?.status).toEqual("UNAUTHORIZED");
  98 | 		expect(res.error?.body.message).toEqual(ERROR_CODES.UNAUTHORIZED_SESSION);
  99 | 	});
 100 | 
 101 | 	it("should fail to create api keys from the client if user id is provided", async () => {
 102 | 		const { headers, user } = await signInWithTestUser();
 103 | 		const response = await client.apiKey.create({
 104 | 			userId: user.id,
 105 | 		});
 106 | 		expect(response.error?.status).toBe(401);
 107 | 		const newUser = await auth.api.signUpEmail({
 108 | 			body: {
 109 | 				email: "[email protected]",
 110 | 				password: "password",
 111 | 				name: "test-name",
 112 | 			},
 113 | 		});
 114 | 		const response2 = await client.apiKey.create(
 115 | 			{
 116 | 				userId: newUser.user.id,
 117 | 			},
 118 | 			{
 119 | 				headers,
 120 | 			},
 121 | 		);
 122 | 		expect(response2.error?.status).toBe(401);
 123 | 	});
 124 | 
 125 | 	it("should successfully create API keys from server with userId", async () => {
 126 | 		const apiKey = await auth.api.createApiKey({
 127 | 			body: {
 128 | 				userId: user.id,
 129 | 			},
 130 | 		});
 131 | 
 132 | 		expect(apiKey).not.toBeNull();
 133 | 		expect(apiKey.key).toBeDefined();
 134 | 		expect(apiKey.userId).toEqual(user.id);
 135 | 		expect(apiKey.name).toBeNull();
 136 | 		expect(apiKey.prefix).toBeNull();
 137 | 		expect(apiKey.refillInterval).toBeNull();
 138 | 		expect(apiKey.refillAmount).toBeNull();
 139 | 		expect(apiKey.lastRefillAt).toBeNull();
 140 | 		expect(apiKey.enabled).toEqual(true);
 141 | 		expect(apiKey.rateLimitTimeWindow).toEqual(86400000);
 142 | 		expect(apiKey.rateLimitMax).toEqual(10);
 143 | 		expect(apiKey.requestCount).toEqual(0);
 144 | 		expect(apiKey.remaining).toBeNull();
 145 | 		expect(apiKey.lastRequest).toBeNull();
 146 | 		expect(apiKey.rateLimitEnabled).toBe(true);
 147 | 	});
 148 | 
 149 | 	it("should have the real value from rateLimitEnabled", async () => {
 150 | 		const apiKey = await auth.api.createApiKey({
 151 | 			body: {
 152 | 				userId: user.id,
 153 | 				rateLimitEnabled: false,
 154 | 			},
 155 | 		});
 156 | 
 157 | 		expect(apiKey).not.toBeNull();
 158 | 		expect(apiKey.rateLimitEnabled).toBe(false);
 159 | 	});
 160 | 
 161 | 	it("should have true if the rate limit is undefined", async () => {
 162 | 		const apiKey = await auth.api.createApiKey({
 163 | 			body: {
 164 | 				userId: user.id,
 165 | 				rateLimitEnabled: undefined,
 166 | 			},
 167 | 		});
 168 | 
 169 | 		expect(apiKey).not.toBeNull();
 170 | 		expect(apiKey.rateLimitEnabled).toBe(true);
 171 | 	});
 172 | 
 173 | 	it("should require name in API keys if configured", async () => {
 174 | 		const { auth, signInWithTestUser } = await getTestInstance(
 175 | 			{
 176 | 				plugins: [
 177 | 					apiKey({
 178 | 						requireName: true,
 179 | 					}),
 180 | 				],
 181 | 			},
 182 | 			{
 183 | 				clientOptions: {
 184 | 					plugins: [apiKeyClient()],
 185 | 				},
 186 | 			},
 187 | 		);
 188 | 
 189 | 		const { user } = await signInWithTestUser();
 190 | 		let err: any;
 191 | 		try {
 192 | 			await auth.api.createApiKey({
 193 | 				body: {
 194 | 					userId: user.id,
 195 | 				},
 196 | 			});
 197 | 		} catch (error) {
 198 | 			err = error;
 199 | 		}
 200 | 		expect(err).toBeDefined();
 201 | 		expect(err.body.message).toBe(ERROR_CODES.NAME_REQUIRED);
 202 | 	});
 203 | 
 204 | 	it("should respect rateLimit configuration from plugin options", async () => {
 205 | 		const { auth, signInWithTestUser } = await getTestInstance(
 206 | 			{
 207 | 				plugins: [
 208 | 					apiKey({
 209 | 						rateLimit: {
 210 | 							enabled: false,
 211 | 							timeWindow: 1000,
 212 | 							maxRequests: 10,
 213 | 						},
 214 | 						enableMetadata: true,
 215 | 					}),
 216 | 				],
 217 | 			},
 218 | 			{
 219 | 				clientOptions: {
 220 | 					plugins: [apiKeyClient()],
 221 | 				},
 222 | 			},
 223 | 		);
 224 | 
 225 | 		const { user } = await signInWithTestUser();
 226 | 		const apiKeyResult = await auth.api.createApiKey({
 227 | 			body: {
 228 | 				userId: user.id,
 229 | 			},
 230 | 		});
 231 | 
 232 | 		expect(apiKeyResult).not.toBeNull();
 233 | 		expect(apiKeyResult.rateLimitEnabled).toBe(false);
 234 | 		expect(apiKeyResult.rateLimitTimeWindow).toBe(1000);
 235 | 		expect(apiKeyResult.rateLimitMax).toBe(10);
 236 | 	});
 237 | 
 238 | 	it("should create the API key with the given name", async () => {
 239 | 		const apiKey = await auth.api.createApiKey({
 240 | 			body: {
 241 | 				name: "test-api-key",
 242 | 			},
 243 | 			headers,
 244 | 		});
 245 | 
 246 | 		expect(apiKey).not.toBeNull();
 247 | 		expect(apiKey.name).toEqual("test-api-key");
 248 | 	});
 249 | 
 250 | 	it("should create the API key with a name that's shorter than the allowed minimum", async () => {
 251 | 		let result: { data: ApiKey | null; error: Err | null } = {
 252 | 			data: null,
 253 | 			error: null,
 254 | 		};
 255 | 		try {
 256 | 			const apiKey = await auth.api.createApiKey({
 257 | 				body: {
 258 | 					name: "test-api-key-that-is-shorter-than-the-allowed-minimum",
 259 | 				},
 260 | 				headers,
 261 | 			});
 262 | 			result.data = apiKey;
 263 | 		} catch (error: any) {
 264 | 			result.error = error;
 265 | 		}
 266 | 		expect(result.data).toBeNull();
 267 | 		expect(result.error).toBeDefined();
 268 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
 269 | 		expect(result.error?.body.message).toEqual(ERROR_CODES.INVALID_NAME_LENGTH);
 270 | 	});
 271 | 
 272 | 	it("should create the API key with a name that's longer than the allowed maximum", async () => {
 273 | 		let result: { data: ApiKey | null; error: Err | null } = {
 274 | 			data: null,
 275 | 			error: null,
 276 | 		};
 277 | 		try {
 278 | 			const apiKey = await auth.api.createApiKey({
 279 | 				body: {
 280 | 					name: "test-api-key-that-is-longer-than-the-allowed-maximum",
 281 | 				},
 282 | 				headers,
 283 | 			});
 284 | 			result.data = apiKey;
 285 | 		} catch (error: any) {
 286 | 			result.error = error;
 287 | 		}
 288 | 		expect(result.data).toBeNull();
 289 | 		expect(result.error).toBeDefined();
 290 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
 291 | 		expect(result.error?.body.message).toEqual(ERROR_CODES.INVALID_NAME_LENGTH);
 292 | 	});
 293 | 
 294 | 	it("should create the API key with the given prefix", async () => {
 295 | 		const prefix = "test-api-key_";
 296 | 		const apiKey = await auth.api.createApiKey({
 297 | 			body: {
 298 | 				prefix: prefix,
 299 | 			},
 300 | 			headers,
 301 | 		});
 302 | 
 303 | 		expect(apiKey).not.toBeNull();
 304 | 		expect(apiKey.prefix).toEqual(prefix);
 305 | 		expect(apiKey.key.startsWith(prefix)).toEqual(true);
 306 | 	});
 307 | 
 308 | 	it("should create the API key with a prefix that's shorter than the allowed minimum", async () => {
 309 | 		let result: { data: ApiKey | null; error: Err | null } = {
 310 | 			data: null,
 311 | 			error: null,
 312 | 		};
 313 | 		try {
 314 | 			const apiKey = await auth.api.createApiKey({
 315 | 				body: {
 316 | 					prefix: "test-api-key-that-is-shorter-than-the-allowed-minimum",
 317 | 				},
 318 | 				headers,
 319 | 			});
 320 | 			result.data = apiKey;
 321 | 		} catch (error: any) {
 322 | 			result.error = error;
 323 | 		}
 324 | 		expect(result.data).toBeNull();
 325 | 		expect(result.error).toBeDefined();
 326 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
 327 | 		expect(result.error?.body.message).toEqual(
 328 | 			ERROR_CODES.INVALID_PREFIX_LENGTH,
 329 | 		);
 330 | 	});
 331 | 
 332 | 	it("should create the API key with a prefix that's longer than the allowed maximum", async () => {
 333 | 		let result: { data: ApiKey | null; error: Err | null } = {
 334 | 			data: null,
 335 | 			error: null,
 336 | 		};
 337 | 		try {
 338 | 			const apiKey = await auth.api.createApiKey({
 339 | 				body: {
 340 | 					prefix: "test-api-key-that-is-longer-than-the-allowed-maximum",
 341 | 				},
 342 | 				headers,
 343 | 			});
 344 | 			result.data = apiKey;
 345 | 		} catch (error: any) {
 346 | 			result.error = error;
 347 | 		}
 348 | 		expect(result.data).toBeNull();
 349 | 		expect(result.error).toBeDefined();
 350 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
 351 | 		expect(result.error?.body.message).toEqual(
 352 | 			ERROR_CODES.INVALID_PREFIX_LENGTH,
 353 | 		);
 354 | 	});
 355 | 
 356 | 	it("should create an API key with a custom expiresIn", async () => {
 357 | 		const expiresIn = 60 * 60 * 24 * 7; // 7 days
 358 | 		const expectedResult = new Date().getTime() + expiresIn;
 359 | 		const apiKey = await auth.api.createApiKey({
 360 | 			body: {
 361 | 				expiresIn: expiresIn,
 362 | 			},
 363 | 			headers,
 364 | 		});
 365 | 		expect(apiKey).not.toBeNull();
 366 | 		expect(apiKey.expiresAt).toBeDefined();
 367 | 		expect(apiKey.expiresAt?.getTime()).toBeGreaterThanOrEqual(expectedResult);
 368 | 	});
 369 | 
 370 | 	it("should support disabling key hashing", async () => {
 371 | 		const { auth, signInWithTestUser } = await getTestInstance(
 372 | 			{
 373 | 				plugins: [
 374 | 					apiKey({
 375 | 						disableKeyHashing: true,
 376 | 					}),
 377 | 				],
 378 | 			},
 379 | 			{
 380 | 				clientOptions: {
 381 | 					plugins: [apiKeyClient()],
 382 | 				},
 383 | 			},
 384 | 		);
 385 | 		const { headers } = await signInWithTestUser();
 386 | 
 387 | 		const apiKey2 = await auth.api.createApiKey({
 388 | 			body: {},
 389 | 			headers,
 390 | 		});
 391 | 		const res = await (await auth.$context).adapter.findOne<ApiKey>({
 392 | 			model: "apikey",
 393 | 			where: [
 394 | 				{
 395 | 					field: "id",
 396 | 					value: apiKey2.id,
 397 | 				},
 398 | 			],
 399 | 		});
 400 | 		expect(res?.key).toEqual(apiKey2.key);
 401 | 	});
 402 | 
 403 | 	it("should be able to verify with key hashing disabled", async () => {
 404 | 		const { auth, signInWithTestUser } = await getTestInstance(
 405 | 			{
 406 | 				plugins: [
 407 | 					apiKey({
 408 | 						disableKeyHashing: true,
 409 | 					}),
 410 | 				],
 411 | 			},
 412 | 			{
 413 | 				clientOptions: {
 414 | 					plugins: [apiKeyClient()],
 415 | 				},
 416 | 			},
 417 | 		);
 418 | 		const { headers } = await signInWithTestUser();
 419 | 
 420 | 		const apiKey2 = await auth.api.createApiKey({
 421 | 			body: {},
 422 | 			headers,
 423 | 		});
 424 | 
 425 | 		const result = await auth.api.verifyApiKey({ body: { key: apiKey2.key } });
 426 | 		expect(result.valid).toEqual(true);
 427 | 	});
 428 | 
 429 | 	it("should fail to create a key with a custom expiresIn value when customExpiresTime is disabled", async () => {
 430 | 		const { client, auth, signInWithTestUser } = await getTestInstance(
 431 | 			{
 432 | 				plugins: [
 433 | 					apiKey({
 434 | 						enableMetadata: true,
 435 | 						keyExpiration: {
 436 | 							disableCustomExpiresTime: true,
 437 | 						},
 438 | 					}),
 439 | 				],
 440 | 			},
 441 | 			{
 442 | 				clientOptions: {
 443 | 					plugins: [apiKeyClient()],
 444 | 				},
 445 | 			},
 446 | 		);
 447 | 
 448 | 		const { headers, user } = await signInWithTestUser();
 449 | 		let result: { data: ApiKey | null; error: Err | null } = {
 450 | 			data: null,
 451 | 			error: null,
 452 | 		};
 453 | 		try {
 454 | 			const apiKey2 = await auth.api.createApiKey({
 455 | 				body: {
 456 | 					expiresIn: 10000,
 457 | 				},
 458 | 				headers,
 459 | 			});
 460 | 			result.data = apiKey2;
 461 | 		} catch (error: any) {
 462 | 			result.error = error;
 463 | 		}
 464 | 
 465 | 		expect(result.data).toBeNull();
 466 | 		expect(result.error).toBeDefined();
 467 | 		expect(result.error?.body.message).toEqual(
 468 | 			ERROR_CODES.KEY_DISABLED_EXPIRATION,
 469 | 		);
 470 | 	});
 471 | 
 472 | 	it("should create an API key with an expiresIn that's smaller than the allowed minimum", async () => {
 473 | 		let result: { data: ApiKey | null; error: Err | null } = {
 474 | 			data: null,
 475 | 			error: null,
 476 | 		};
 477 | 		try {
 478 | 			const expiresIn = 60 * 60 * 24 * 0.5; // half a day
 479 | 			const apiKey = await auth.api.createApiKey({
 480 | 				body: {
 481 | 					expiresIn: expiresIn,
 482 | 				},
 483 | 				headers,
 484 | 			});
 485 | 			result.data = apiKey;
 486 | 		} catch (error: any) {
 487 | 			result.error = error;
 488 | 		}
 489 | 		expect(result.data).toBeNull();
 490 | 		expect(result.error).toBeDefined();
 491 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
 492 | 		expect(result.error?.body.message).toEqual(
 493 | 			ERROR_CODES.EXPIRES_IN_IS_TOO_SMALL,
 494 | 		);
 495 | 	});
 496 | 
 497 | 	it("should fail to create an API key with an expiresIn that's larger than the allowed maximum", async () => {
 498 | 		let result: { data: ApiKey | null; error: Err | null } = {
 499 | 			data: null,
 500 | 			error: null,
 501 | 		};
 502 | 		try {
 503 | 			const expiresIn = 60 * 60 * 24 * 365 * 10; // 10 year
 504 | 			const apiKey = await auth.api.createApiKey({
 505 | 				body: {
 506 | 					expiresIn: expiresIn,
 507 | 				},
 508 | 				headers,
 509 | 			});
 510 | 			result.data = apiKey;
 511 | 		} catch (error: any) {
 512 | 			result.error = error;
 513 | 		}
 514 | 		expect(result.data).toBeNull();
 515 | 		expect(result.error).toBeDefined();
 516 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
 517 | 		expect(result.error?.body.message).toEqual(
 518 | 			ERROR_CODES.EXPIRES_IN_IS_TOO_LARGE,
 519 | 		);
 520 | 	});
 521 | 
 522 | 	it("should fail to create API key with custom refillAndAmount from client auth", async () => {
 523 | 		const apiKey = await client.apiKey.create(
 524 | 			{
 525 | 				refillAmount: 10,
 526 | 			},
 527 | 			{ headers },
 528 | 		);
 529 | 
 530 | 		expect(apiKey.data).toBeNull();
 531 | 		expect(apiKey.error).toBeDefined();
 532 | 		expect(apiKey.error?.statusText).toEqual("BAD_REQUEST");
 533 | 		expect(apiKey.error?.message).toEqual(ERROR_CODES.SERVER_ONLY_PROPERTY);
 534 | 
 535 | 		const apiKey2 = await client.apiKey.create(
 536 | 			{
 537 | 				refillInterval: 1001,
 538 | 			},
 539 | 			{ headers },
 540 | 		);
 541 | 
 542 | 		expect(apiKey2.data).toBeNull();
 543 | 		expect(apiKey2.error).toBeDefined();
 544 | 		expect(apiKey2.error?.statusText).toEqual("BAD_REQUEST");
 545 | 		expect(apiKey2.error?.message).toEqual(ERROR_CODES.SERVER_ONLY_PROPERTY);
 546 | 	});
 547 | 
 548 | 	it("should fail to create API key when refill interval is provided, but no refill amount", async () => {
 549 | 		let res: { data: ApiKey | null; error: Err | null } = {
 550 | 			data: null,
 551 | 			error: null,
 552 | 		};
 553 | 		try {
 554 | 			const apiKey = await auth.api.createApiKey({
 555 | 				body: {
 556 | 					refillInterval: 1000,
 557 | 					userId: user.id,
 558 | 				},
 559 | 			});
 560 | 			res.data = apiKey;
 561 | 		} catch (error: any) {
 562 | 			res.error = error;
 563 | 		}
 564 | 
 565 | 		expect(res.data).toBeNull();
 566 | 		expect(res.error).toBeDefined();
 567 | 		expect(res.error?.status).toEqual("BAD_REQUEST");
 568 | 		expect(res.error?.body.message).toEqual(
 569 | 			ERROR_CODES.REFILL_INTERVAL_AND_AMOUNT_REQUIRED,
 570 | 		);
 571 | 	});
 572 | 
 573 | 	it("should fail to create API key when refill amount is provided, but no refill interval", async () => {
 574 | 		let res: { data: ApiKey | null; error: Err | null } = {
 575 | 			data: null,
 576 | 			error: null,
 577 | 		};
 578 | 		try {
 579 | 			const apiKey = await auth.api.createApiKey({
 580 | 				body: {
 581 | 					refillAmount: 10,
 582 | 					userId: user.id,
 583 | 				},
 584 | 			});
 585 | 			res.data = apiKey;
 586 | 		} catch (error: any) {
 587 | 			res.error = error;
 588 | 		}
 589 | 
 590 | 		expect(res.data).toBeNull();
 591 | 		expect(res.error).toBeDefined();
 592 | 		expect(res.error?.status).toEqual("BAD_REQUEST");
 593 | 		expect(res.error?.body.message).toEqual(
 594 | 			ERROR_CODES.REFILL_AMOUNT_AND_INTERVAL_REQUIRED,
 595 | 		);
 596 | 	});
 597 | 
 598 | 	it("should create the API key with the given refill interval & refill amount", async () => {
 599 | 		const refillInterval = 10000;
 600 | 		const refillAmount = 10;
 601 | 		const apiKey = await auth.api.createApiKey({
 602 | 			body: {
 603 | 				refillInterval: refillInterval,
 604 | 				refillAmount: refillAmount,
 605 | 				userId: user.id,
 606 | 			},
 607 | 		});
 608 | 
 609 | 		expect(apiKey).not.toBeNull();
 610 | 		expect(apiKey.refillInterval).toEqual(refillInterval);
 611 | 		expect(apiKey.refillAmount).toEqual(refillAmount);
 612 | 	});
 613 | 
 614 | 	it("should create API Key with custom remaining", async () => {
 615 | 		const remaining = 10;
 616 | 		const apiKey = await auth.api.createApiKey({
 617 | 			body: {
 618 | 				remaining: remaining,
 619 | 				userId: user.id,
 620 | 			},
 621 | 		});
 622 | 
 623 | 		expect(apiKey).not.toBeNull();
 624 | 		expect(apiKey.remaining).toEqual(remaining);
 625 | 	});
 626 | 
 627 | 	it("should create API Key with remaining explicitly set to null", async () => {
 628 | 		const apiKey = await auth.api.createApiKey({
 629 | 			body: {
 630 | 				remaining: null,
 631 | 				userId: user.id,
 632 | 			},
 633 | 		});
 634 | 
 635 | 		expect(apiKey).not.toBeNull();
 636 | 		expect(apiKey.remaining).toBeNull();
 637 | 	});
 638 | 
 639 | 	it("should create API Key with remaining explicitly set to null and refillAmount and refillInterval are also set", async () => {
 640 | 		const refillAmount = 10; // Arbitrary non-null value
 641 | 		const refillInterval = 1000;
 642 | 		const apiKey = await auth.api.createApiKey({
 643 | 			body: {
 644 | 				remaining: null,
 645 | 				refillAmount: refillAmount,
 646 | 				refillInterval: refillInterval,
 647 | 				userId: user.id,
 648 | 			},
 649 | 		});
 650 | 
 651 | 		expect(apiKey).not.toBeNull();
 652 | 		expect(apiKey.remaining).toBeNull();
 653 | 		expect(apiKey.refillAmount).toBe(refillAmount);
 654 | 		expect(apiKey.refillInterval).toBe(refillInterval);
 655 | 	});
 656 | 
 657 | 	it("should create API Key with remaining explicitly set to 0 and refillAmount also set", async () => {
 658 | 		const remaining = 0;
 659 | 		const refillAmount = 10; // Arbitrary non-null value
 660 | 		const refillInterval = 1000;
 661 | 		const apiKey = await auth.api.createApiKey({
 662 | 			body: {
 663 | 				remaining: remaining,
 664 | 				refillAmount: refillAmount,
 665 | 				refillInterval: refillInterval,
 666 | 				userId: user.id,
 667 | 			},
 668 | 		});
 669 | 
 670 | 		expect(apiKey).not.toBeNull();
 671 | 		expect(apiKey.remaining).toBe(remaining);
 672 | 		expect(apiKey.refillAmount).toBe(refillAmount);
 673 | 		expect(apiKey.refillInterval).toBe(refillInterval);
 674 | 	});
 675 | 
 676 | 	it("should create API Key with remaining undefined and default value of null is respected with refillAmount and refillInterval provided", async () => {
 677 | 		const refillAmount = 10; // Arbitrary non-null value
 678 | 		const refillInterval = 1000;
 679 | 		const apiKey = await auth.api.createApiKey({
 680 | 			body: {
 681 | 				refillAmount: refillAmount,
 682 | 				refillInterval: refillInterval,
 683 | 				userId: user.id,
 684 | 			},
 685 | 		});
 686 | 
 687 | 		expect(apiKey).not.toBeNull();
 688 | 		expect(apiKey.remaining).toBeNull();
 689 | 		expect(apiKey.refillAmount).toBe(refillAmount);
 690 | 		expect(apiKey.refillInterval).toBe(refillInterval);
 691 | 	});
 692 | 
 693 | 	it("should create API key with invalid metadata", async () => {
 694 | 		let result: { data: ApiKey | null; error: Err | null } = {
 695 | 			data: null,
 696 | 			error: null,
 697 | 		};
 698 | 		try {
 699 | 			const apiKey = await auth.api.createApiKey({
 700 | 				body: {
 701 | 					metadata: "invalid",
 702 | 				},
 703 | 				headers,
 704 | 			});
 705 | 			result.data = apiKey;
 706 | 		} catch (error: any) {
 707 | 			result.error = error;
 708 | 		}
 709 | 		expect(result.data).toBeNull();
 710 | 		expect(result.error).toBeDefined();
 711 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
 712 | 		expect(result.error?.body.message).toEqual(
 713 | 			ERROR_CODES.INVALID_METADATA_TYPE,
 714 | 		);
 715 | 	});
 716 | 
 717 | 	it("should create API key with valid metadata", async () => {
 718 | 		const metadata = {
 719 | 			test: "test",
 720 | 		};
 721 | 		const apiKey = await auth.api.createApiKey({
 722 | 			body: {
 723 | 				metadata: metadata,
 724 | 			},
 725 | 			headers,
 726 | 		});
 727 | 
 728 | 		expect(apiKey).not.toBeNull();
 729 | 		expect(apiKey.metadata).toEqual(metadata);
 730 | 
 731 | 		const res = await auth.api.getApiKey({
 732 | 			query: {
 733 | 				id: apiKey.id,
 734 | 			},
 735 | 			headers,
 736 | 		});
 737 | 
 738 | 		expect(res).not.toBeNull();
 739 | 		if (res) {
 740 | 			expect(res.metadata).toEqual(metadata);
 741 | 		}
 742 | 	});
 743 | 
 744 | 	it("create API key's returned metadata should be an object", async () => {
 745 | 		const metadata = {
 746 | 			test: "test-123",
 747 | 		};
 748 | 		const apiKey = await auth.api.createApiKey({
 749 | 			body: {
 750 | 				metadata: metadata,
 751 | 			},
 752 | 			headers,
 753 | 		});
 754 | 
 755 | 		expect(apiKey).not.toBeNull();
 756 | 		expect(apiKey.metadata.test).toBeDefined();
 757 | 		expect(apiKey.metadata.test).toEqual(metadata.test);
 758 | 	});
 759 | 
 760 | 	it("create API key with with metadata when metadata is disabled (should fail)", async () => {
 761 | 		const { client, auth, signInWithTestUser } = await getTestInstance(
 762 | 			{
 763 | 				plugins: [
 764 | 					apiKey({
 765 | 						enableMetadata: false,
 766 | 					}),
 767 | 				],
 768 | 			},
 769 | 			{
 770 | 				clientOptions: {
 771 | 					plugins: [apiKeyClient()],
 772 | 				},
 773 | 			},
 774 | 		);
 775 | 		const { headers } = await signInWithTestUser();
 776 | 
 777 | 		const metadata = {
 778 | 			test: "test-123",
 779 | 		};
 780 | 		const result: { data: ApiKey | null; error: Err | null } = {
 781 | 			data: null,
 782 | 			error: null,
 783 | 		};
 784 | 		try {
 785 | 			const apiKey = await auth.api.createApiKey({
 786 | 				body: {
 787 | 					metadata: metadata,
 788 | 				},
 789 | 				headers,
 790 | 			});
 791 | 			result.data = apiKey;
 792 | 		} catch (error: any) {
 793 | 			result.error = error;
 794 | 		}
 795 | 
 796 | 		expect(result.data).toBeNull();
 797 | 		expect(result.error).toBeDefined();
 798 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
 799 | 		expect(result.error?.body.message).toEqual(ERROR_CODES.METADATA_DISABLED);
 800 | 	});
 801 | 
 802 | 	it("should have the first 6 chracaters of the key as the start property", async () => {
 803 | 		const { data: apiKey } = await client.apiKey.create(
 804 | 			{},
 805 | 			{ headers: headers },
 806 | 		);
 807 | 
 808 | 		expect(apiKey?.start).toBeDefined();
 809 | 		expect(apiKey?.start?.length).toEqual(6);
 810 | 		expect(apiKey?.start).toEqual(apiKey?.key?.substring(0, 6));
 811 | 	});
 812 | 
 813 | 	it("should have the start property as null if shouldStore is false", async () => {
 814 | 		const { client, auth, signInWithTestUser } = await getTestInstance(
 815 | 			{
 816 | 				plugins: [
 817 | 					apiKey({
 818 | 						startingCharactersConfig: {
 819 | 							shouldStore: false,
 820 | 						},
 821 | 					}),
 822 | 				],
 823 | 			},
 824 | 			{
 825 | 				clientOptions: {
 826 | 					plugins: [apiKeyClient()],
 827 | 				},
 828 | 			},
 829 | 		);
 830 | 		const { headers } = await signInWithTestUser();
 831 | 
 832 | 		const { data: apiKey2 } = await client.apiKey.create(
 833 | 			{},
 834 | 			{ headers: headers },
 835 | 		);
 836 | 
 837 | 		expect(apiKey2?.start).toBeNull();
 838 | 	});
 839 | 
 840 | 	it("should use the defined charactersLength if provided", async () => {
 841 | 		const customLength = 3;
 842 | 		const { client, auth, signInWithTestUser } = await getTestInstance(
 843 | 			{
 844 | 				plugins: [
 845 | 					apiKey({
 846 | 						startingCharactersConfig: {
 847 | 							shouldStore: true,
 848 | 							charactersLength: customLength,
 849 | 						},
 850 | 					}),
 851 | 				],
 852 | 			},
 853 | 			{
 854 | 				clientOptions: {
 855 | 					plugins: [apiKeyClient()],
 856 | 				},
 857 | 			},
 858 | 		);
 859 | 		const { headers } = await signInWithTestUser();
 860 | 
 861 | 		const { data: apiKey2 } = await client.apiKey.create(
 862 | 			{},
 863 | 			{ headers: headers },
 864 | 		);
 865 | 
 866 | 		expect(apiKey2?.start).toBeDefined();
 867 | 		expect(apiKey2?.start?.length).toEqual(customLength);
 868 | 		expect(apiKey2?.start).toEqual(apiKey2?.key?.substring(0, customLength));
 869 | 	});
 870 | 
 871 | 	it("should fail to create API key with custom rate-limit options from client auth", async () => {
 872 | 		const apiKey = await client.apiKey.create(
 873 | 			{
 874 | 				rateLimitMax: 15,
 875 | 			},
 876 | 			{ headers },
 877 | 		);
 878 | 
 879 | 		expect(apiKey.data).toBeNull();
 880 | 		expect(apiKey.error).toBeDefined();
 881 | 		expect(apiKey.error?.statusText).toEqual("BAD_REQUEST");
 882 | 		expect(apiKey.error?.message).toEqual(ERROR_CODES.SERVER_ONLY_PROPERTY);
 883 | 
 884 | 		const apiKey2 = await client.apiKey.create(
 885 | 			{
 886 | 				rateLimitTimeWindow: 1001,
 887 | 			},
 888 | 			{ headers },
 889 | 		);
 890 | 
 891 | 		expect(apiKey2.data).toBeNull();
 892 | 		expect(apiKey2.error).toBeDefined();
 893 | 		expect(apiKey2.error?.statusText).toEqual("BAD_REQUEST");
 894 | 		expect(apiKey2.error?.message).toEqual(ERROR_CODES.SERVER_ONLY_PROPERTY);
 895 | 	});
 896 | 
 897 | 	it("should successfully apply custom rate-limit options on the newly created API key", async () => {
 898 | 		const apiKey = await auth.api.createApiKey({
 899 | 			body: {
 900 | 				rateLimitMax: 15,
 901 | 				rateLimitTimeWindow: 1000,
 902 | 				userId: user.id,
 903 | 			},
 904 | 		});
 905 | 
 906 | 		expect(apiKey).not.toBeNull();
 907 | 		expect(apiKey?.rateLimitMax).toEqual(15);
 908 | 		expect(apiKey?.rateLimitTimeWindow).toEqual(1000);
 909 | 	});
 910 | 
 911 | 	// =========================================================================
 912 | 	// VERIFY API KEY
 913 | 	// =========================================================================
 914 | 
 915 | 	it("verify API key without key and userId", async () => {
 916 | 		const apiKey = await auth.api.verifyApiKey({
 917 | 			body: {
 918 | 				key: firstApiKey.key,
 919 | 			},
 920 | 		});
 921 | 		expect(apiKey.key).not.toBe(null);
 922 | 		expect(apiKey.valid).toBe(true);
 923 | 	});
 924 | 
 925 | 	it("verify API key with invalid key (should fail)", async () => {
 926 | 		const apiKey = await auth.api.verifyApiKey({
 927 | 			body: {
 928 | 				key: "invalid",
 929 | 			},
 930 | 		});
 931 | 		expect(apiKey.valid).toBe(false);
 932 | 		expect(apiKey.error?.code).toBe("KEY_NOT_FOUND");
 933 | 	});
 934 | 
 935 | 	let rateLimitedApiKey: ApiKey;
 936 | 
 937 | 	const {
 938 | 		client: rateLimitClient,
 939 | 		auth: rateLimitAuth,
 940 | 		signInWithTestUser: rateLimitTestUser,
 941 | 	} = await getTestInstance(
 942 | 		{
 943 | 			plugins: [
 944 | 				apiKey({
 945 | 					rateLimit: {
 946 | 						enabled: true,
 947 | 						timeWindow: 1000,
 948 | 					},
 949 | 				}),
 950 | 			],
 951 | 		},
 952 | 		{
 953 | 			clientOptions: {
 954 | 				plugins: [apiKeyClient()],
 955 | 			},
 956 | 		},
 957 | 	);
 958 | 
 959 | 	const { headers: rateLimitUserHeaders } = await rateLimitTestUser();
 960 | 
 961 | 	it("should fail to verify API key 20 times in a row due to rate-limit", async () => {
 962 | 		const { data: apiKey2 } = await rateLimitClient.apiKey.create(
 963 | 			{},
 964 | 			{ headers: rateLimitUserHeaders },
 965 | 		);
 966 | 		if (!apiKey2) return;
 967 | 		rateLimitedApiKey = apiKey2;
 968 | 		for (let i = 0; i < 20; i++) {
 969 | 			const response = await rateLimitAuth.api.verifyApiKey({
 970 | 				body: {
 971 | 					key: apiKey2.key,
 972 | 				},
 973 | 				headers: rateLimitUserHeaders,
 974 | 			});
 975 | 			if (i >= 10) {
 976 | 				expect(response.error?.code).toBe("RATE_LIMITED");
 977 | 			} else {
 978 | 				expect(response.error).toBeNull();
 979 | 			}
 980 | 		}
 981 | 	});
 982 | 
 983 | 	it("should allow us to verify API key after rate-limit window has passed", async () => {
 984 | 		vi.useFakeTimers();
 985 | 		await vi.advanceTimersByTimeAsync(1000);
 986 | 		const response = await rateLimitAuth.api.verifyApiKey({
 987 | 			body: {
 988 | 				key: rateLimitedApiKey.key,
 989 | 			},
 990 | 			headers: rateLimitUserHeaders,
 991 | 		});
 992 | 		expect(response.error).toBeNull();
 993 | 		expect(response?.valid).toBe(true);
 994 | 	});
 995 | 
 996 | 	it("should check if verifying an API key's remaining count does go down", async () => {
 997 | 		const remaining = 10;
 998 | 		const { data: apiKey } = await client.apiKey.create(
 999 | 			{
1000 | 				remaining: remaining,
1001 | 			},
1002 | 			{ headers: headers },
1003 | 		);
1004 | 		if (!apiKey) return;
1005 | 		const afterVerificationOnce = await auth.api.verifyApiKey({
1006 | 			body: {
1007 | 				key: apiKey.key,
1008 | 			},
1009 | 			headers,
1010 | 		});
1011 | 		expect(afterVerificationOnce?.valid).toEqual(true);
1012 | 		expect(afterVerificationOnce?.key?.remaining).toEqual(remaining - 1);
1013 | 		const afterVerificationTwice = await auth.api.verifyApiKey({
1014 | 			body: {
1015 | 				key: apiKey.key,
1016 | 			},
1017 | 			headers,
1018 | 		});
1019 | 		expect(afterVerificationTwice?.valid).toEqual(true);
1020 | 		expect(afterVerificationTwice?.key?.remaining).toEqual(remaining - 2);
1021 | 	});
1022 | 
1023 | 	it("should fail if the API key has no remaining", async () => {
1024 | 		const apiKey = await auth.api.createApiKey({
1025 | 			body: {
1026 | 				remaining: 1,
1027 | 				userId: user.id,
1028 | 			},
1029 | 		});
1030 | 		if (!apiKey) return;
1031 | 		// run verify once to make the remaining count go down to 0
1032 | 		await auth.api.verifyApiKey({
1033 | 			body: {
1034 | 				key: apiKey.key,
1035 | 			},
1036 | 			headers,
1037 | 		});
1038 | 		const afterVerification = await auth.api.verifyApiKey({
1039 | 			body: {
1040 | 				key: apiKey.key,
1041 | 			},
1042 | 			headers,
1043 | 		});
1044 | 		expect(afterVerification.error?.code).toBe("USAGE_EXCEEDED");
1045 | 	});
1046 | 
1047 | 	it("should fail if the API key is expired", async () => {
1048 | 		vi.useRealTimers();
1049 | 		const { headers } = await signInWithTestUser();
1050 | 		const apiKey2 = await client.apiKey.create(
1051 | 			{
1052 | 				expiresIn: 60 * 60 * 24,
1053 | 			},
1054 | 			{ headers: headers, throw: true },
1055 | 		);
1056 | 		vi.useFakeTimers();
1057 | 		await vi.advanceTimersByTimeAsync(1000 * 60 * 60 * 24 * 2);
1058 | 		const afterVerification = await auth.api.verifyApiKey({
1059 | 			body: {
1060 | 				key: apiKey2.key,
1061 | 			},
1062 | 			headers,
1063 | 		});
1064 | 		expect(afterVerification.error?.code).toEqual("KEY_EXPIRED");
1065 | 		vi.useRealTimers();
1066 | 	});
1067 | 
1068 | 	// =========================================================================
1069 | 	// UPDATE API KEY
1070 | 	// =========================================================================
1071 | 
1072 | 	interface Err {
1073 | 		body: {
1074 | 			code: string | undefined;
1075 | 			message: string | undefined;
1076 | 		};
1077 | 		status: string;
1078 | 		statusCode: string;
1079 | 	}
1080 | 
1081 | 	it("should fail to update API key name without headers or userId", async () => {
1082 | 		let res: { data: ApiKey | null; error: Err | null } = {
1083 | 			data: null,
1084 | 			error: null,
1085 | 		};
1086 | 		try {
1087 | 			const apiKey = await auth.api.updateApiKey({
1088 | 				body: {
1089 | 					keyId: firstApiKey.id,
1090 | 					name: "test-api-key",
1091 | 				},
1092 | 			});
1093 | 			res.data = apiKey as ApiKey;
1094 | 		} catch (error: any) {
1095 | 			res.error = error;
1096 | 		}
1097 | 		expect(res.data).toBeNull();
1098 | 		expect(res.error).toBeDefined();
1099 | 		expect(res.error?.statusCode).toEqual(401);
1100 | 		expect(res.error?.status).toEqual("UNAUTHORIZED");
1101 | 		expect(res.error?.body.message).toEqual(ERROR_CODES.UNAUTHORIZED_SESSION);
1102 | 	});
1103 | 
1104 | 	it("should update API key name with headers", async () => {
1105 | 		const newName = "Hello World";
1106 | 		const apiKey = await auth.api.updateApiKey({
1107 | 			body: {
1108 | 				keyId: firstApiKey.id,
1109 | 				name: newName,
1110 | 			},
1111 | 			headers,
1112 | 		});
1113 | 		expect(apiKey).toBeDefined();
1114 | 		expect(apiKey.name).not.toEqual(firstApiKey.name);
1115 | 		expect(apiKey.name).toEqual(newName);
1116 | 	});
1117 | 
1118 | 	it("should fail to update API key name with a length larger than the allowed maximum", async () => {
1119 | 		let error: APIError | null = null;
1120 | 		await auth.api
1121 | 			.updateApiKey({
1122 | 				body: {
1123 | 					keyId: firstApiKey.id,
1124 | 					name: "test-api-key-that-is-longer-than-the-allowed-maximum",
1125 | 				},
1126 | 				headers,
1127 | 			})
1128 | 			.catch((e) => {
1129 | 				if (e instanceof APIError) {
1130 | 					error = e;
1131 | 					expect(error?.status).toEqual("BAD_REQUEST");
1132 | 					expect(error?.body?.message).toEqual(ERROR_CODES.INVALID_NAME_LENGTH);
1133 | 				}
1134 | 			});
1135 | 		expect(error).not.toBeNull();
1136 | 	});
1137 | 
1138 | 	it("should fail to update API key name with a length smaller than the allowed minimum", async () => {
1139 | 		let error: APIError | null = null;
1140 | 		await auth.api
1141 | 			.updateApiKey({
1142 | 				body: {
1143 | 					keyId: firstApiKey.id,
1144 | 					name: "",
1145 | 				},
1146 | 				headers,
1147 | 			})
1148 | 			.catch((e) => {
1149 | 				if (e instanceof APIError) {
1150 | 					error = e;
1151 | 					expect(error?.status).toEqual("BAD_REQUEST");
1152 | 					expect(error?.body?.message).toEqual(ERROR_CODES.INVALID_NAME_LENGTH);
1153 | 				}
1154 | 			});
1155 | 		expect(error).not.toBeNull();
1156 | 	});
1157 | 
1158 | 	it("should fail to update API key with no values to update", async () => {
1159 | 		let error: APIError | null = null;
1160 | 		await auth.api
1161 | 			.updateApiKey({
1162 | 				body: {
1163 | 					keyId: firstApiKey.id,
1164 | 				},
1165 | 				headers,
1166 | 			})
1167 | 			.catch((e) => {
1168 | 				if (e instanceof APIError) {
1169 | 					error = e;
1170 | 					expect(error?.status).toEqual("BAD_REQUEST");
1171 | 					expect(error?.body?.message).toEqual(ERROR_CODES.NO_VALUES_TO_UPDATE);
1172 | 				}
1173 | 			});
1174 | 		expect(error).not.toBeNull();
1175 | 	});
1176 | 
1177 | 	it("should update API key expiresIn value", async () => {
1178 | 		const expiresIn = 60 * 60 * 24 * 7; // 7 days
1179 | 		const expectedResult = new Date().getTime() + expiresIn;
1180 | 		const apiKey = await auth.api.updateApiKey({
1181 | 			body: {
1182 | 				keyId: firstApiKey.id,
1183 | 				expiresIn: expiresIn,
1184 | 			},
1185 | 			headers,
1186 | 		});
1187 | 		expect(apiKey).not.toBeNull();
1188 | 		expect(apiKey.expiresAt).toBeDefined();
1189 | 		expect(apiKey.expiresAt?.getTime()).toBeGreaterThanOrEqual(expectedResult);
1190 | 	});
1191 | 
1192 | 	it("should fail to update expiresIn value if `disableCustomExpiresTime` is enabled", async () => {
1193 | 		const { client, auth, signInWithTestUser } = await getTestInstance(
1194 | 			{
1195 | 				plugins: [
1196 | 					apiKey({
1197 | 						keyExpiration: {
1198 | 							disableCustomExpiresTime: true,
1199 | 						},
1200 | 					}),
1201 | 				],
1202 | 			},
1203 | 			{
1204 | 				clientOptions: {
1205 | 					plugins: [apiKeyClient()],
1206 | 				},
1207 | 			},
1208 | 		);
1209 | 		const { headers } = await signInWithTestUser();
1210 | 
1211 | 		const { data: firstApiKey } = await client.apiKey.create({}, { headers });
1212 | 
1213 | 		if (!firstApiKey) return;
1214 | 
1215 | 		let result: { data: Partial<ApiKey> | null; error: Err | null } = {
1216 | 			data: null,
1217 | 			error: null,
1218 | 		};
1219 | 		try {
1220 | 			const apiKey = await auth.api.updateApiKey({
1221 | 				body: {
1222 | 					keyId: firstApiKey.id,
1223 | 					expiresIn: 1000 * 60 * 60 * 24 * 7, // 7 days
1224 | 				},
1225 | 				headers,
1226 | 			});
1227 | 			result.data = apiKey;
1228 | 		} catch (error: any) {
1229 | 			result.error = error;
1230 | 		}
1231 | 		expect(result.data).toBeNull();
1232 | 		expect(result.error).toBeDefined();
1233 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
1234 | 		expect(result.error?.body.message).toEqual(
1235 | 			ERROR_CODES.KEY_DISABLED_EXPIRATION,
1236 | 		);
1237 | 	});
1238 | 
1239 | 	it("should fail to update expiresIn value if it's smaller than the allowed minimum", async () => {
1240 | 		const { client, auth, signInWithTestUser } = await getTestInstance(
1241 | 			{
1242 | 				plugins: [
1243 | 					apiKey({
1244 | 						keyExpiration: {
1245 | 							minExpiresIn: 1,
1246 | 						},
1247 | 					}),
1248 | 				],
1249 | 			},
1250 | 			{
1251 | 				clientOptions: {
1252 | 					plugins: [apiKeyClient()],
1253 | 				},
1254 | 			},
1255 | 		);
1256 | 		const { headers } = await signInWithTestUser();
1257 | 
1258 | 		const { data: firstApiKey } = await client.apiKey.create({}, { headers });
1259 | 
1260 | 		if (!firstApiKey) return;
1261 | 
1262 | 		let result: { data: Partial<ApiKey> | null; error: Err | null } = {
1263 | 			data: null,
1264 | 			error: null,
1265 | 		};
1266 | 		try {
1267 | 			const apiKey = await auth.api.updateApiKey({
1268 | 				body: {
1269 | 					keyId: firstApiKey.id,
1270 | 					expiresIn: 1,
1271 | 				},
1272 | 				headers,
1273 | 			});
1274 | 			result.data = apiKey;
1275 | 		} catch (error: any) {
1276 | 			result.error = error;
1277 | 		}
1278 | 		expect(result.data).toBeNull();
1279 | 		expect(result.error).toBeDefined();
1280 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
1281 | 		expect(result.error?.body.message).toEqual(
1282 | 			ERROR_CODES.EXPIRES_IN_IS_TOO_SMALL,
1283 | 		);
1284 | 	});
1285 | 
1286 | 	it("should fail to update expiresIn value if it's larger than the allowed maximum", async () => {
1287 | 		const { client, auth, signInWithTestUser } = await getTestInstance(
1288 | 			{
1289 | 				plugins: [
1290 | 					apiKey({
1291 | 						keyExpiration: {
1292 | 							maxExpiresIn: 1,
1293 | 						},
1294 | 					}),
1295 | 				],
1296 | 			},
1297 | 			{
1298 | 				clientOptions: {
1299 | 					plugins: [apiKeyClient()],
1300 | 				},
1301 | 			},
1302 | 		);
1303 | 		const { headers } = await signInWithTestUser();
1304 | 
1305 | 		const { data: firstApiKey } = await client.apiKey.create({}, { headers });
1306 | 
1307 | 		if (!firstApiKey) return;
1308 | 
1309 | 		let result: { data: Partial<ApiKey> | null; error: Err | null } = {
1310 | 			data: null,
1311 | 			error: null,
1312 | 		};
1313 | 		try {
1314 | 			const apiKey = await auth.api.updateApiKey({
1315 | 				body: {
1316 | 					keyId: firstApiKey.id,
1317 | 					expiresIn: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
1318 | 				},
1319 | 				headers,
1320 | 			});
1321 | 			result.data = apiKey;
1322 | 		} catch (error: any) {
1323 | 			result.error = error;
1324 | 		}
1325 | 		expect(result.data).toBeNull();
1326 | 		expect(result.error).toBeDefined();
1327 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
1328 | 		expect(result.error?.body.message).toEqual(
1329 | 			ERROR_CODES.EXPIRES_IN_IS_TOO_LARGE,
1330 | 		);
1331 | 	});
1332 | 
1333 | 	it("should update API key remaining count", async () => {
1334 | 		const remaining = 100;
1335 | 		const apiKey = await auth.api.updateApiKey({
1336 | 			body: {
1337 | 				keyId: firstApiKey.id,
1338 | 				remaining: remaining,
1339 | 				userId: user.id,
1340 | 			},
1341 | 		});
1342 | 
1343 | 		expect(apiKey).not.toBeNull();
1344 | 		expect(apiKey.remaining).toEqual(remaining);
1345 | 	});
1346 | 
1347 | 	it("should fail update the refillInterval value since it requires refillAmount as well", async () => {
1348 | 		let result: { data: Partial<ApiKey> | null; error: Err | null } = {
1349 | 			data: null,
1350 | 			error: null,
1351 | 		};
1352 | 		try {
1353 | 			const apiKey = await auth.api.updateApiKey({
1354 | 				body: {
1355 | 					keyId: firstApiKey.id,
1356 | 					refillInterval: 1000,
1357 | 					userId: user.id,
1358 | 				},
1359 | 			});
1360 | 			result.data = apiKey;
1361 | 		} catch (error: any) {
1362 | 			result.error = error;
1363 | 		}
1364 | 		expect(result.data).toBeNull();
1365 | 		expect(result.error).toBeDefined();
1366 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
1367 | 		expect(result.error?.body.message).toEqual(
1368 | 			ERROR_CODES.REFILL_INTERVAL_AND_AMOUNT_REQUIRED,
1369 | 		);
1370 | 	});
1371 | 
1372 | 	it("should fail update the refillAmount value since it requires refillInterval as well", async () => {
1373 | 		let result: { data: Partial<ApiKey> | null; error: Err | null } = {
1374 | 			data: null,
1375 | 			error: null,
1376 | 		};
1377 | 		try {
1378 | 			const apiKey = await auth.api.updateApiKey({
1379 | 				body: {
1380 | 					keyId: firstApiKey.id,
1381 | 					refillAmount: 10,
1382 | 					userId: user.id,
1383 | 				},
1384 | 			});
1385 | 			result.data = apiKey;
1386 | 		} catch (error: any) {
1387 | 			result.error = error;
1388 | 		}
1389 | 		expect(result.data).toBeNull();
1390 | 		expect(result.error).toBeDefined();
1391 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
1392 | 		expect(result.error?.body.message).toEqual(
1393 | 			ERROR_CODES.REFILL_AMOUNT_AND_INTERVAL_REQUIRED,
1394 | 		);
1395 | 	});
1396 | 
1397 | 	it("should update the refillInterval and refillAmount value", async () => {
1398 | 		const refillInterval = 10000;
1399 | 		const refillAmount = 100;
1400 | 		const apiKey = await auth.api.updateApiKey({
1401 | 			body: {
1402 | 				keyId: firstApiKey.id,
1403 | 				refillInterval: refillInterval,
1404 | 				refillAmount: refillAmount,
1405 | 				userId: user.id,
1406 | 			},
1407 | 		});
1408 | 
1409 | 		expect(apiKey).not.toBeNull();
1410 | 		expect(apiKey.refillInterval).toEqual(refillInterval);
1411 | 		expect(apiKey.refillAmount).toEqual(refillAmount);
1412 | 	});
1413 | 
1414 | 	it("should update API key enable value", async () => {
1415 | 		const newValue = false;
1416 | 		const apiKey = await auth.api.updateApiKey({
1417 | 			body: {
1418 | 				keyId: firstApiKey.id,
1419 | 				enabled: newValue,
1420 | 				userId: user.id,
1421 | 			},
1422 | 		});
1423 | 
1424 | 		expect(apiKey).not.toBeNull();
1425 | 		expect(apiKey.enabled).toEqual(newValue);
1426 | 	});
1427 | 
1428 | 	it("should fail to update metadata with invalid metadata type", async () => {
1429 | 		let result: { data: Partial<ApiKey> | null; error: Err | null } = {
1430 | 			data: null,
1431 | 			error: null,
1432 | 		};
1433 | 		try {
1434 | 			const apiKey = await auth.api.updateApiKey({
1435 | 				body: {
1436 | 					keyId: firstApiKey.id,
1437 | 					metadata: "invalid",
1438 | 					userId: user.id,
1439 | 				},
1440 | 			});
1441 | 			result.data = apiKey;
1442 | 		} catch (error: any) {
1443 | 			result.error = error;
1444 | 		}
1445 | 		expect(result.data).toBeNull();
1446 | 		expect(result.error).toBeDefined();
1447 | 		expect(result.error?.status).toEqual("BAD_REQUEST");
1448 | 		expect(result.error?.body.message).toEqual(
1449 | 			ERROR_CODES.INVALID_METADATA_TYPE,
1450 | 		);
1451 | 	});
1452 | 
1453 | 	it("should update metadata with valid metadata type", async () => {
1454 | 		const metadata = {
1455 | 			test: "test-123",
1456 | 		};
1457 | 		const apiKey = await auth.api.updateApiKey({
1458 | 			body: {
1459 | 				keyId: firstApiKey.id,
1460 | 				metadata: metadata,
1461 | 				userId: user.id,
1462 | 			},
1463 | 		});
1464 | 
1465 | 		expect(apiKey).not.toBeNull();
1466 | 		expect(apiKey.metadata).toEqual(metadata);
1467 | 	});
1468 | 
1469 | 	it("update API key's returned metadata should be an object", async () => {
1470 | 		const metadata = {
1471 | 			test: "test-12345",
1472 | 		};
1473 | 		const apiKey = await auth.api.updateApiKey({
1474 | 			body: {
1475 | 				keyId: firstApiKey.id,
1476 | 				metadata: metadata,
1477 | 				userId: user.id,
1478 | 			},
1479 | 		});
1480 | 
1481 | 		expect(apiKey).not.toBeNull();
1482 | 		expect(apiKey.metadata?.test).toBeDefined();
1483 | 		expect(apiKey.metadata?.test).toEqual(metadata.test);
1484 | 	});
1485 | 
1486 | 	// =========================================================================
1487 | 	// GET API KEY
1488 | 	// =========================================================================
1489 | 
1490 | 	it("should get an API key by id", async () => {
1491 | 		const apiKey = await client.apiKey.get({
1492 | 			query: {
1493 | 				id: firstApiKey.id,
1494 | 			},
1495 | 			fetchOptions: {
1496 | 				headers,
1497 | 			},
1498 | 		});
1499 | 		expect(apiKey.data).not.toBeNull();
1500 | 		expect(apiKey.data?.id).toBe(firstApiKey.id);
1501 | 	});
1502 | 
1503 | 	it("should fail to get an API key by ID that doesn't exist", async () => {
1504 | 		const result = await client.apiKey.get(
1505 | 			{
1506 | 				query: {
1507 | 					id: "invalid",
1508 | 				},
1509 | 			},
1510 | 			{ headers },
1511 | 		);
1512 | 		expect(result.data).toBeNull();
1513 | 		expect(result.error).toBeDefined();
1514 | 		expect(result.error?.status).toEqual(404);
1515 | 	});
1516 | 
1517 | 	it("should successfully receive an object metadata from an API key", async () => {
1518 | 		const apiKey = await client.apiKey.get(
1519 | 			{
1520 | 				query: {
1521 | 					id: firstApiKey.id,
1522 | 				},
1523 | 			},
1524 | 			{
1525 | 				headers,
1526 | 			},
1527 | 		);
1528 | 		expect(apiKey).not.toBeNull();
1529 | 		expect(apiKey.data?.metadata).toBeDefined();
1530 | 		expect(apiKey.data?.metadata).toBeInstanceOf(Object);
1531 | 	});
1532 | 
1533 | 	// =========================================================================
1534 | 	// LIST API KEY
1535 | 	// =========================================================================
1536 | 
1537 | 	it("should fail to list API keys without headers", async () => {
1538 | 		let result: { data: Partial<ApiKey>[] | null; error: Err | null } = {
1539 | 			data: null,
1540 | 			error: null,
1541 | 		};
1542 | 		try {
1543 | 			const apiKey = await auth.api.listApiKeys({});
1544 | 			result.data = apiKey;
1545 | 		} catch (error: any) {
1546 | 			result.error = error;
1547 | 		}
1548 | 
1549 | 		expect(result.data).toBeNull();
1550 | 		expect(result.error).toBeDefined();
1551 | 		expect(result.error?.status).toEqual("UNAUTHORIZED");
1552 | 	});
1553 | 
1554 | 	it("should list API keys with headers", async () => {
1555 | 		const apiKeys = await auth.api.listApiKeys({
1556 | 			headers,
1557 | 		});
1558 | 
1559 | 		expect(apiKeys).not.toBeNull();
1560 | 		expect(apiKeys.length).toBeGreaterThan(0);
1561 | 	});
1562 | 
1563 | 	it("should list API keys with metadata as an object", async () => {
1564 | 		const apiKeys = await auth.api.listApiKeys({
1565 | 			headers,
1566 | 		});
1567 | 
1568 | 		expect(apiKeys).not.toBeNull();
1569 | 		expect(apiKeys.length).toBeGreaterThan(0);
1570 | 		apiKeys.map((apiKey) => {
1571 | 			if (apiKey.metadata) {
1572 | 				expect(apiKey.metadata).toBeInstanceOf(Object);
1573 | 			}
1574 | 		});
1575 | 	});
1576 | 
1577 | 	// =========================================================================
1578 | 	// Sessions from API keys
1579 | 	// =========================================================================
1580 | 
1581 | 	it("should get session from an API key", async () => {
1582 | 		const { client, auth, signInWithTestUser } = await getTestInstance(
1583 | 			{
1584 | 				plugins: [
1585 | 					apiKey({
1586 | 						enableSessionForAPIKeys: true,
1587 | 					}),
1588 | 				],
1589 | 			},
1590 | 			{
1591 | 				clientOptions: {
1592 | 					plugins: [apiKeyClient()],
1593 | 				},
1594 | 			},
1595 | 		);
1596 | 
1597 | 		const { headers: userHeaders } = await signInWithTestUser();
1598 | 
1599 | 		const { data: apiKey2 } = await client.apiKey.create(
1600 | 			{},
1601 | 			{ headers: userHeaders },
1602 | 		);
1603 | 		if (!apiKey2) return;
1604 | 		const headers = new Headers();
1605 | 		headers.set("x-api-key", apiKey2.key);
1606 | 
1607 | 		const session = await auth.api.getSession({
1608 | 			headers: headers,
1609 | 		});
1610 | 
1611 | 		expect(session?.session).toBeDefined();
1612 | 	});
1613 | 
1614 | 	// =========================================================================
1615 | 	// DELETE API KEY
1616 | 	// =========================================================================
1617 | 
1618 | 	it("should fail to delete an API key by ID without headers", async () => {
1619 | 		let result: { data: { success: boolean } | null; error: Err | null } = {
1620 | 			data: null,
1621 | 			error: null,
1622 | 		};
1623 | 		try {
1624 | 			const apiKey = await auth.api.deleteApiKey({
1625 | 				body: {
1626 | 					keyId: firstApiKey.id,
1627 | 				},
1628 | 			});
1629 | 			result.data = apiKey;
1630 | 		} catch (error: any) {
1631 | 			result.error = error;
1632 | 		}
1633 | 
1634 | 		expect(result.data).toBeNull();
1635 | 		expect(result.error).toBeDefined();
1636 | 		expect(result.error?.status).toEqual("UNAUTHORIZED");
1637 | 	});
1638 | 
1639 | 	it("should delete an API key by ID with headers", async () => {
1640 | 		const apiKey = await auth.api.deleteApiKey({
1641 | 			body: {
1642 | 				keyId: firstApiKey.id,
1643 | 			},
1644 | 			headers,
1645 | 		});
1646 | 
1647 | 		expect(apiKey).not.toBeNull();
1648 | 		expect(apiKey.success).toEqual(true);
1649 | 	});
1650 | 
1651 | 	it("should delete an API key by ID with headers using auth-client", async () => {
1652 | 		const newApiKey = await client.apiKey.create({}, { headers: headers });
1653 | 		if (!newApiKey.data) return;
1654 | 
1655 | 		const apiKey = await client.apiKey.delete(
1656 | 			{
1657 | 				keyId: newApiKey.data.id,
1658 | 			},
1659 | 			{ headers },
1660 | 		);
1661 | 
1662 | 		if (!apiKey.data?.success) {
1663 | 			console.log(apiKey.error);
1664 | 		}
1665 | 
1666 | 		expect(apiKey).not.toBeNull();
1667 | 		expect(apiKey.data?.success).toEqual(true);
1668 | 	});
1669 | 
1670 | 	it("should fail to delete an API key by ID that doesn't exist", async () => {
1671 | 		let result: { data: { success: boolean } | null; error: Err | null } = {
1672 | 			data: null,
1673 | 			error: null,
1674 | 		};
1675 | 		try {
1676 | 			const apiKey = await auth.api.deleteApiKey({
1677 | 				body: {
1678 | 					keyId: "invalid",
1679 | 				},
1680 | 				headers,
1681 | 			});
1682 | 			result.data = apiKey;
1683 | 		} catch (error: any) {
1684 | 			result.error = error;
1685 | 		}
1686 | 		expect(result.data).toBeNull();
1687 | 		expect(result.error).toBeDefined();
1688 | 		expect(result.error?.status).toEqual("NOT_FOUND");
1689 | 		expect(result.error?.body.message).toEqual(ERROR_CODES.KEY_NOT_FOUND);
1690 | 	});
1691 | 
1692 | 	it("should create an API key with permissions", async () => {
1693 | 		const permissions = {
1694 | 			files: ["read", "write"],
1695 | 			users: ["read"],
1696 | 		};
1697 | 
1698 | 		const apiKey = await auth.api.createApiKey({
1699 | 			body: {
1700 | 				permissions,
1701 | 				userId: user.id,
1702 | 			},
1703 | 		});
1704 | 		expect(apiKey).not.toBeNull();
1705 | 		expect(apiKey.permissions).toEqual(permissions);
1706 | 	});
1707 | 
1708 | 	it("should have permissions as an object from getApiKey", async () => {
1709 | 		const permissions = {
1710 | 			files: ["read", "write"],
1711 | 			users: ["read"],
1712 | 		};
1713 | 
1714 | 		const apiKey = await auth.api.createApiKey({
1715 | 			body: {
1716 | 				permissions,
1717 | 				userId: user.id,
1718 | 			},
1719 | 		});
1720 | 
1721 | 		const apiKeyResults = await auth.api.getApiKey({
1722 | 			query: {
1723 | 				id: apiKey.id,
1724 | 			},
1725 | 			headers,
1726 | 		});
1727 | 
1728 | 		expect(apiKeyResults).not.toBeNull();
1729 | 		expect(apiKeyResults.permissions).toEqual(permissions);
1730 | 	});
1731 | 
1732 | 	it("should have permissions as an object from verifyApiKey", async () => {
1733 | 		const permissions = {
1734 | 			files: ["read", "write"],
1735 | 			users: ["read"],
1736 | 		};
1737 | 
1738 | 		const apiKey = await auth.api.createApiKey({
1739 | 			body: {
1740 | 				permissions,
1741 | 				userId: user.id,
1742 | 			},
1743 | 		});
1744 | 		const apiKeyResults = await auth.api.verifyApiKey({
1745 | 			body: {
1746 | 				key: apiKey.key,
1747 | 				permissions: {
1748 | 					files: ["read"],
1749 | 				},
1750 | 			},
1751 | 			headers,
1752 | 		});
1753 | 
1754 | 		expect(apiKeyResults).not.toBeNull();
1755 | 		expect(apiKeyResults.key?.permissions).toEqual(permissions);
1756 | 	});
1757 | 
1758 | 	it("should create an API key with default permissions", async () => {
1759 | 		const apiKey = await auth.api.createApiKey({
1760 | 			body: {
1761 | 				userId: user.id,
1762 | 			},
1763 | 		});
1764 | 		expect(apiKey).not.toBeNull();
1765 | 		expect(apiKey.permissions).toEqual({
1766 | 			files: ["read"],
1767 | 		});
1768 | 	});
1769 | 
1770 | 	it("should have valid metadata from key verification results", async () => {
1771 | 		const metadata = {
1772 | 			test: "hello-world-123",
1773 | 		};
1774 | 		const apiKey = await auth.api.createApiKey({
1775 | 			body: {
1776 | 				userId: user.id,
1777 | 				metadata: metadata,
1778 | 			},
1779 | 			headers,
1780 | 		});
1781 | 
1782 | 		expect(apiKey).not.toBeNull();
1783 | 		if (apiKey) {
1784 | 			const result = await auth.api.verifyApiKey({
1785 | 				body: {
1786 | 					key: apiKey.key,
1787 | 				},
1788 | 				headers,
1789 | 			});
1790 | 
1791 | 			expect(result.valid).toBe(true);
1792 | 			expect(result.error).toBeNull();
1793 | 			expect(result.key?.metadata).toEqual(metadata);
1794 | 		}
1795 | 	});
1796 | 
1797 | 	it("should verify an API key with matching permissions", async () => {
1798 | 		const permissions = {
1799 | 			files: ["read", "write"],
1800 | 			users: ["read"],
1801 | 		};
1802 | 
1803 | 		const apiKey = await auth.api.createApiKey({
1804 | 			body: {
1805 | 				permissions,
1806 | 				userId: user.id,
1807 | 			},
1808 | 		});
1809 | 
1810 | 		const result = await auth.api.verifyApiKey({
1811 | 			body: {
1812 | 				key: apiKey.key,
1813 | 				permissions: {
1814 | 					files: ["read"],
1815 | 				},
1816 | 			},
1817 | 		});
1818 | 
1819 | 		expect(result.valid).toBe(true);
1820 | 		expect(result.error).toBeNull();
1821 | 		expect(result.key?.permissions).toEqual(permissions);
1822 | 	});
1823 | 
1824 | 	it("should fail to verify an API key with non-matching permissions", async () => {
1825 | 		const permissions = {
1826 | 			files: ["read"],
1827 | 			users: ["read"],
1828 | 		};
1829 | 
1830 | 		const apiKey = await auth.api.createApiKey({
1831 | 			body: {
1832 | 				permissions,
1833 | 				userId: user.id,
1834 | 			},
1835 | 		});
1836 | 
1837 | 		const result = await auth.api.verifyApiKey({
1838 | 			body: {
1839 | 				key: apiKey.key,
1840 | 				permissions: {
1841 | 					files: ["write"],
1842 | 				},
1843 | 			},
1844 | 		});
1845 | 
1846 | 		expect(result.valid).toBe(false);
1847 | 		expect(result.error?.code).toBe("KEY_NOT_FOUND");
1848 | 	});
1849 | 
1850 | 	it("should fail to verify when required permissions are specified but API key has no permissions", async () => {
1851 | 		const apiKey = await auth.api.createApiKey({
1852 | 			body: {
1853 | 				userId: user.id,
1854 | 			},
1855 | 		});
1856 | 
1857 | 		const result = await auth.api.verifyApiKey({
1858 | 			body: {
1859 | 				key: apiKey.key,
1860 | 				permissions: {
1861 | 					files: ["write"],
1862 | 				},
1863 | 			},
1864 | 		});
1865 | 
1866 | 		expect(result.valid).toBe(false);
1867 | 		expect(result.error?.code).toBe("KEY_NOT_FOUND");
1868 | 	});
1869 | 
1870 | 	it("should update an API key with permissions", async () => {
1871 | 		const permissions = {
1872 | 			files: ["read", "write"],
1873 | 			users: ["read"],
1874 | 		};
1875 | 		const createdApiKey = await auth.api.createApiKey({
1876 | 			body: {
1877 | 				userId: user.id,
1878 | 			},
1879 | 		});
1880 | 		expect(createdApiKey.permissions).not.toEqual(permissions);
1881 | 		const apiKey = await auth.api.updateApiKey({
1882 | 			body: {
1883 | 				keyId: createdApiKey.id,
1884 | 				permissions,
1885 | 				userId: user.id,
1886 | 			},
1887 | 		});
1888 | 		expect(apiKey).not.toBeNull();
1889 | 		expect(apiKey.permissions).toEqual(permissions);
1890 | 	});
1891 | 
1892 | 	it("should refill API key credits after refill interval (milliseconds)", async () => {
1893 | 		vi.useRealTimers();
1894 | 
1895 | 		const refillInterval = 3600000; // 1 hour in milliseconds
1896 | 		const refillAmount = 5;
1897 | 		const initialRemaining = 2;
1898 | 
1899 | 		const apiKey = await auth.api.createApiKey({
1900 | 			body: {
1901 | 				userId: user.id,
1902 | 				remaining: initialRemaining,
1903 | 				refillInterval: refillInterval,
1904 | 				refillAmount: refillAmount,
1905 | 			},
1906 | 		});
1907 | 
1908 | 		let result = await auth.api.verifyApiKey({
1909 | 			body: {
1910 | 				key: apiKey.key,
1911 | 			},
1912 | 		});
1913 | 		expect(result.valid).toBe(true);
1914 | 		expect(result.key?.remaining).toBe(initialRemaining - 1);
1915 | 
1916 | 		result = await auth.api.verifyApiKey({
1917 | 			body: {
1918 | 				key: apiKey.key,
1919 | 			},
1920 | 		});
1921 | 		expect(result.valid).toBe(true);
1922 | 		expect(result.key?.remaining).toBe(0);
1923 | 
1924 | 		result = await auth.api.verifyApiKey({
1925 | 			body: {
1926 | 				key: apiKey.key,
1927 | 			},
1928 | 		});
1929 | 		expect(result.valid).toBe(false);
1930 | 		expect(result.error?.code).toBe("USAGE_EXCEEDED");
1931 | 
1932 | 		vi.useFakeTimers();
1933 | 		await vi.advanceTimersByTimeAsync(refillInterval + 1000);
1934 | 
1935 | 		result = await auth.api.verifyApiKey({
1936 | 			body: {
1937 | 				key: apiKey.key,
1938 | 			},
1939 | 		});
1940 | 		expect(result.valid).toBe(true);
1941 | 		expect(result.key?.remaining).toBe(refillAmount - 1);
1942 | 
1943 | 		vi.useRealTimers();
1944 | 	});
1945 | 
1946 | 	it("should not refill API key credits before refill interval expires", async () => {
1947 | 		vi.useRealTimers();
1948 | 
1949 | 		const refillInterval = 86400000; // 24 hours in milliseconds
1950 | 		const refillAmount = 10;
1951 | 		const initialRemaining = 1;
1952 | 
1953 | 		const apiKey = await auth.api.createApiKey({
1954 | 			body: {
1955 | 				userId: user.id,
1956 | 				remaining: initialRemaining,
1957 | 				refillInterval: refillInterval,
1958 | 				refillAmount: refillAmount,
1959 | 			},
1960 | 		});
1961 | 
1962 | 		let result = await auth.api.verifyApiKey({
1963 | 			body: {
1964 | 				key: apiKey.key,
1965 | 			},
1966 | 		});
1967 | 		expect(result.valid).toBe(true);
1968 | 		expect(result.key?.remaining).toBe(0);
1969 | 
1970 | 		vi.useFakeTimers();
1971 | 		await vi.advanceTimersByTimeAsync(refillInterval / 2); // Only advance half the interval
1972 | 
1973 | 		result = await auth.api.verifyApiKey({
1974 | 			body: {
1975 | 				key: apiKey.key,
1976 | 			},
1977 | 		});
1978 | 		expect(result.valid).toBe(false);
1979 | 		expect(result.error?.code).toBe("USAGE_EXCEEDED");
1980 | 
1981 | 		await vi.advanceTimersByTimeAsync(refillInterval / 2 + 1000);
1982 | 
1983 | 		result = await auth.api.verifyApiKey({
1984 | 			body: {
1985 | 				key: apiKey.key,
1986 | 			},
1987 | 		});
1988 | 		expect(result.valid).toBe(true);
1989 | 		expect(result.key?.remaining).toBe(refillAmount - 1);
1990 | 
1991 | 		vi.useRealTimers();
1992 | 	});
1993 | 
1994 | 	it("should handle multiple refill cycles correctly", async () => {
1995 | 		vi.useRealTimers();
1996 | 
1997 | 		const refillInterval = 3600000; // 1 hour in milliseconds
1998 | 		const refillAmount = 3;
1999 | 
2000 | 		const apiKey = await auth.api.createApiKey({
2001 | 			body: {
2002 | 				userId: user.id,
2003 | 				remaining: 1,
2004 | 				refillInterval: refillInterval,
2005 | 				refillAmount: refillAmount,
2006 | 			},
2007 | 		});
2008 | 
2009 | 		let result = await auth.api.verifyApiKey({
2010 | 			body: {
2011 | 				key: apiKey.key,
2012 | 			},
2013 | 		});
2014 | 		expect(result.valid).toBe(true);
2015 | 		expect(result.key?.remaining).toBe(0);
2016 | 
2017 | 		vi.useFakeTimers();
2018 | 
2019 | 		await vi.advanceTimersByTimeAsync(refillInterval + 1000);
2020 | 		result = await auth.api.verifyApiKey({
2021 | 			body: {
2022 | 				key: apiKey.key,
2023 | 			},
2024 | 		});
2025 | 		expect(result.valid).toBe(true);
2026 | 		expect(result.key?.remaining).toBe(refillAmount - 1);
2027 | 
2028 | 		for (let i = 0; i < refillAmount - 1; i++) {
2029 | 			result = await auth.api.verifyApiKey({
2030 | 				body: {
2031 | 					key: apiKey.key,
2032 | 				},
2033 | 			});
2034 | 			expect(result.valid).toBe(true);
2035 | 		}
2036 | 
2037 | 		result = await auth.api.verifyApiKey({
2038 | 			body: {
2039 | 				key: apiKey.key,
2040 | 			},
2041 | 		});
2042 | 		expect(result.valid).toBe(false);
2043 | 		expect(result.error?.code).toBe("USAGE_EXCEEDED");
2044 | 
2045 | 		await vi.advanceTimersByTimeAsync(refillInterval + 1000);
2046 | 		result = await auth.api.verifyApiKey({
2047 | 			body: {
2048 | 				key: apiKey.key,
2049 | 			},
2050 | 		});
2051 | 		expect(result.valid).toBe(true);
2052 | 		expect(result.key?.remaining).toBe(refillAmount - 1);
2053 | 
2054 | 		vi.useRealTimers();
2055 | 	});
2056 | });
2057 | 
```
Page 63/68FirstPrevNextLast