mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-07 20:37:44 +00:00
x
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { organization, passkey, twoFactor } from "better-auth/plugins";
|
||||
import {
|
||||
organization,
|
||||
passkey,
|
||||
twoFactor,
|
||||
rateLimiter,
|
||||
} from "better-auth/plugins";
|
||||
import { reactInvitationEmail } from "./email/invitation";
|
||||
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
||||
import { github, google } from "better-auth/social-providers";
|
||||
@@ -7,7 +12,6 @@ import { reactResetPasswordEmail } from "./email/rest-password";
|
||||
import { resend } from "./email/resend";
|
||||
|
||||
const from = process.env.BETTER_AUTH_EMAIL || "delivered@resend.dev";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: new LibsqlDialect({
|
||||
url: process.env.TURSO_DATABASE_URL || "",
|
||||
@@ -35,6 +39,9 @@ export const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
rateLimiter({
|
||||
enabled: true,
|
||||
}),
|
||||
organization({
|
||||
async sendInvitationEmail(data) {
|
||||
const res = await resend.emails.send({
|
||||
|
||||
175
dev/express/.gitignore
vendored
Normal file
175
dev/express/.gitignore
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
15
dev/express/README.md
Normal file
15
dev/express/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# express
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.1.27. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
8
dev/express/auth.ts
Normal file
8
dev/express/auth.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
});
|
||||
12
dev/express/index.ts
Normal file
12
dev/express/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import express from "express";
|
||||
import { toNodeHandler } from "better-auth/node";
|
||||
import { auth } from "./auth";
|
||||
|
||||
const app = express();
|
||||
const port = 3005;
|
||||
|
||||
app.get("/api/auth/*", toNodeHandler(auth));
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Example app listening on port ${port}`);
|
||||
});
|
||||
17
dev/express/package.json
Normal file
17
dev/express/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "express",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/express": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-auth": "workspace:*",
|
||||
"express": "^4.21.0",
|
||||
"tsx": "^4.19.0"
|
||||
}
|
||||
}
|
||||
27
dev/express/tsconfig.json
Normal file
27
dev/express/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
||||
232
docs/content/docs/authentication/email-password.mdx
Normal file
232
docs/content/docs/authentication/email-password.mdx
Normal file
@@ -0,0 +1,232 @@
|
||||
---
|
||||
title: Email & Password
|
||||
description: Implementing email and password authentication with Better Auth
|
||||
---
|
||||
|
||||
Email and password authentication is a common method used by many applications. Better Auth provides a built-in email and password authenticator that you can easily integrate into your project.
|
||||
|
||||
<Callout type="info">
|
||||
If you prefer username-based authentication, check out the <Link href="/docs/plugins/username">username plugin</Link>. It extends the email and password authenticator with username support.
|
||||
</Callout>
|
||||
|
||||
## Setup
|
||||
|
||||
To enable email and password authentication, add the following configuration to your Better Auth instance:
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
emailAndPassword: { // [!code highlight]
|
||||
enabled: true // [!code highlight]
|
||||
} // [!code highlight]
|
||||
})
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
If it's not enabled, it'll not allow you to sign in or sign up with email and password.
|
||||
</Callout>
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### Signup
|
||||
|
||||
To signup a user, you can use the `signUp.email` function provided by the client. The `signUp` function takes an object with the following properties:
|
||||
|
||||
- `email`: The email address of the user.
|
||||
- `password`: The password of the user. It should be at least 8 characters long and max 32 by default.
|
||||
- `name`: The name of the user.
|
||||
- `image`: The image of the user. (optional)
|
||||
- `callbackURL`: The url to redirect to after the user has signed up. (optional)
|
||||
- `dontRememberMe`: If true, the user will be signed out when the browser is closed. (optional)
|
||||
|
||||
```ts title="client.ts" /
|
||||
/**
|
||||
* Make sure to import the client for your framework
|
||||
*/
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
|
||||
const client = createAuthClient()
|
||||
|
||||
const signup = async () => {
|
||||
const data = await client.signUp.email({
|
||||
email: "test@example.com",
|
||||
password: "password1234",
|
||||
name: "test",
|
||||
image: "https://example.com/image.png",
|
||||
callbackURL: "/"
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The function returns a promise that resolves an object with `data` and `error` properties. The `data` property contains the user object that was created, and the `error` property contains any error that occurred during the signup process.
|
||||
|
||||
<Callout type="info">
|
||||
Hover over the `data` object to see the shape of the response.
|
||||
</Callout>
|
||||
|
||||
### Signin
|
||||
|
||||
To signin a user, you can use the `signIn.email` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||
|
||||
- `email`: The email address of the user.
|
||||
- `password`: The password of the user.
|
||||
- `callbackURL`: The url to redirect to after the user has signed in. (optional)
|
||||
- `dontRememberMe`: If true, the user will be signed out when the browser is closed. (optional)
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
|
||||
const client = createAuthClient()
|
||||
|
||||
const signin = async () => {
|
||||
const data = await client.signIn.email({
|
||||
email: "test@example.com",
|
||||
password: "password1234",
|
||||
callbackURL: "/"
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Email Verification
|
||||
|
||||
To enable email verification, you need to configure the email and password authenticator. You can do this by adding the following code to your better auth instance:
|
||||
|
||||
```ts title="auth.ts"
|
||||
|
||||
import { betterAuth } from "better-auth"
|
||||
|
||||
export const auth = await betterAuth({
|
||||
// ---cut-start---
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite"
|
||||
},
|
||||
// ---cut-end---
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
async sendVerificationEmail(email, url){
|
||||
// send email to user.
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
on the client side you can use `sendVerificationEmail` function to send verification link to user.
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
const client = createAuthClient()
|
||||
// ---cut---
|
||||
const verifyEmail = async () => {
|
||||
const data = await client.sendVerificationEmail({
|
||||
email: "test@example.com",
|
||||
callbackURL: "/"
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Password Reset
|
||||
|
||||
|
||||
to reset a password first you need to provider `sendResetPasswordToken` function to the email and password authenticator. The `sendResetPasswordToken` function takes an object with the following properties:
|
||||
|
||||
- `user`: The user object.
|
||||
- `token`: The token that was generated.
|
||||
|
||||
```ts title="auth.ts"
|
||||
async function sendResetEmail(email: string, url: string){
|
||||
// send email to user
|
||||
}
|
||||
// ---cut---
|
||||
import { betterAuth } from "better-auth"
|
||||
|
||||
export const auth = await betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
emailAndPassword: { // [!code highlight]
|
||||
enabled: true, // [!code highlight]
|
||||
async sendResetPasswordToken(token, user) { // [!code highlight]
|
||||
// send email to user // [!code highlight]
|
||||
const url = `https://example.com/reset-password?token=${token}` // [!code highlight]
|
||||
await sendResetEmail(user.email, url) //your function to send email to user // [!code highlight]
|
||||
}, // [!code highlight]
|
||||
} // [!code highlight]
|
||||
})
|
||||
```
|
||||
|
||||
once you configured your server you can call `forgetPassword` function to send reset password link to user.
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
const client = createAuthClient()
|
||||
// ---cut---
|
||||
const forgetPassword = async () => {
|
||||
const data = await client.forgetPassword({
|
||||
email: "test@example.com",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
When user click on the link in the email he will be redirected to the reset password page. You can add the reset password page to your app. Then you can use `resetPassword` function to reset the password. It takes an object with the following properties:
|
||||
|
||||
- `token`: The token that was generated.
|
||||
- `newPassword`: The new password of the user.
|
||||
|
||||
```ts title="client.ts"
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
|
||||
const client = createAuthClient()
|
||||
function useSearchParams() {
|
||||
return {
|
||||
get: (key: string) => "" as string | null,
|
||||
}
|
||||
}
|
||||
// ---cut---
|
||||
const token = useSearchParams().get("token") // get token from url
|
||||
const resetPassword = async () => {
|
||||
if(!token) return
|
||||
const data = await client.resetPassword({
|
||||
token: token,
|
||||
newPassword: "password1234",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
enabled: {
|
||||
description:
|
||||
'Enable email and password authentication',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
minPasswordLength: {
|
||||
description: 'The minimum length of the password.',
|
||||
type: 'number',
|
||||
default: 8,
|
||||
},
|
||||
maxPasswordLength: {
|
||||
description: 'The maximum length of the password.',
|
||||
type: 'number',
|
||||
default: 32,
|
||||
},
|
||||
sendResetPasswordToken: {
|
||||
description: 'send reset password email. It takes a functions that takes two parameters: token and user.',
|
||||
type: 'function',
|
||||
},
|
||||
sendVerificationEmail: {
|
||||
description: 'send verification email. It takes a functions that takes two parameters: email and url.',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
347
docs/content/docs/basic-usage.mdx
Normal file
347
docs/content/docs/basic-usage.mdx
Normal file
@@ -0,0 +1,347 @@
|
||||
---
|
||||
title: Basic Usage
|
||||
description: Getting started with Better Auth
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
Better Auth provides built-in authentication support for:
|
||||
|
||||
- **Email and password**
|
||||
- **Social provider (Google, Github, Apple, and more)**
|
||||
|
||||
You can extend authentication options using plugins, such as: Username-based login, Passkeys, Email magic links, and more.
|
||||
|
||||
### Email and Password Authentication
|
||||
|
||||
To enable email and password authentication:
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
|
||||
export const auth = betterAuth({
|
||||
//...rest of the options
|
||||
emailAndPassword: { // [!code highlight]
|
||||
enabled: true // [!code highlight]
|
||||
} // [!code highlight]
|
||||
})
|
||||
```
|
||||
|
||||
### Signup
|
||||
|
||||
To signup a user, you can use the `signUp.email` function provided by the client. The `signUp` function takes an object with the following properties:
|
||||
|
||||
```ts title="sign-up.ts" twoslash
|
||||
// @filename: client.ts
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
|
||||
export const client = createAuthClient()
|
||||
|
||||
// ---cut---
|
||||
// @filename: signup.ts
|
||||
// ---cut---
|
||||
import { client } from "./client";
|
||||
|
||||
const res = await client.signUp.email({
|
||||
email: "test@example.com", // The email address of the user.
|
||||
password: "password1234", // The password of the user.
|
||||
name: "test", // The name of the user.
|
||||
image: "https://example.com/image.png", // The image of the user. (optional)
|
||||
callbackURL: "/" // The url to redirect to after the user has signed up. (optional)
|
||||
})
|
||||
```
|
||||
|
||||
The function returns a promise that resolves an object with `data` and `error` properties. The `data` property contains the user object that was created, and the `error` property contains any error that occurred during the signup process.
|
||||
|
||||
<Callout type="info">
|
||||
If you want to use username instead of email, you can use <Link href="/docs/plugins/username">username Plugin</Link>.
|
||||
</Callout>
|
||||
|
||||
### Signin
|
||||
|
||||
To signin a user, you can use the `signIn.email` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||
|
||||
```ts title="sing-in.ts" twoslash
|
||||
// @filename: client.ts
|
||||
import { createAuthClient } from "better-auth/client"
|
||||
|
||||
export const client = createAuthClient()
|
||||
|
||||
// ---cut---
|
||||
// @filename: signup.ts
|
||||
// ---cut---
|
||||
import { client } from "./client";
|
||||
|
||||
const data = await client.signIn.email({
|
||||
email: "test@example.com", // The email address of the user.
|
||||
password: "password1234", // The password of the user.
|
||||
callbackURL: "/" // The url to redirect to after the user has signed in. (optional)
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Authentication with Social Providers
|
||||
|
||||
Better Auth supports multiple social providers, including Google, Github, Apple, Discord, and more. To use a social provider, you need to configure the ones you need in the `socialProvider` option on your `auth` object.
|
||||
|
||||
### Configure Social Providers
|
||||
|
||||
To configure social providers, you need to import the provider you want to use and pass it to the `socialProvider` option. For example, to configure the Github provider, you can use the following code:
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
import { github } from "better-auth/social-providers"
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
socialProvider: [ // [!code highlight]
|
||||
github({ // [!code highlight]
|
||||
clientId: GITHUB_CLIENT_ID, // [!code highlight]
|
||||
clientSecret: GITHUB_CLIENT_SECRET, // [!code highlight]
|
||||
}), // [!code highlight]
|
||||
], // [!code highlight]
|
||||
})
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
See the <Link href="/docs/providers">Provider</Link> section for more information on how to configure each provider.
|
||||
</Callout>
|
||||
|
||||
### Signin with social providers
|
||||
|
||||
```ts title="signin.ts"
|
||||
import { client } from "./client"
|
||||
|
||||
const signin = async () => {
|
||||
const data = await client.signIn.social({
|
||||
provider: "github"
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Session
|
||||
|
||||
Once a user is signed in, you'll want to access their session. Better auth allows you easily to access the session data from the server and client side.
|
||||
|
||||
### Client Side
|
||||
|
||||
Better Auth provides a `useSession` hook to easily access session data on the client side. This hook is implemented in a reactive way for each supported framework, ensuring that any changes to the session (such as signing out) are immediately reflected in your UI.
|
||||
|
||||
<Tabs items={["React", "Vue","Svelte", "Solid"]} defaultValue="React">
|
||||
<Tab value="React">
|
||||
```tsx title="user.tsx"
|
||||
//make sure you're using the react client
|
||||
import { createAuthClient } from "better-auth/react"
|
||||
const { useSession } = createAuthClient() // [!code highlight]
|
||||
|
||||
export function User(){
|
||||
const {
|
||||
data: session,
|
||||
isPending, //loading state
|
||||
error //error object
|
||||
} = useSession()
|
||||
returns (
|
||||
//...
|
||||
)
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab value="Vue">
|
||||
```vue title="user.vue"
|
||||
<template>
|
||||
<div>
|
||||
<button v-if="!client.useSession().value" @click="() => client.signIn.social({
|
||||
provider: 'github'
|
||||
})">
|
||||
Continue with github
|
||||
</button>
|
||||
<div>
|
||||
<pre>{{ client.useSession().value }}</pre>
|
||||
<button v-if="client.useSession().value" @click="client.signOut()">
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab value="Svelte">
|
||||
```svelte title="user.svelte"
|
||||
<script lang="ts">
|
||||
import { client } from "$lib/client";
|
||||
const session = client.useSsession;
|
||||
</script>
|
||||
|
||||
<div
|
||||
style="display: flex; flex-direction: column; gap: 10px; border-radius: 10px; border: 1px solid #4B453F; padding: 20px; margin-top: 10px;"
|
||||
>
|
||||
<div>
|
||||
{#if $session}
|
||||
<div>
|
||||
<p>
|
||||
{$session?.user.name}
|
||||
</p>
|
||||
<p>
|
||||
{$session?.user.email}
|
||||
</p>
|
||||
<button
|
||||
on:click={async () => {
|
||||
await client.signOut();
|
||||
}}
|
||||
>
|
||||
Signout
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
on:click={async () => {
|
||||
await client.signIn.social({
|
||||
provider: "github",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Continue with github
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab value="Solid">
|
||||
```tsx title="user.tsx"
|
||||
import { client } from "~/lib/client";
|
||||
import { Show } from 'solid-js';
|
||||
|
||||
export default function Home() {
|
||||
const session = client.useSession()
|
||||
return (
|
||||
<Show
|
||||
when={session()}
|
||||
fallback={<button onClick={toggle}>Log in</button>}
|
||||
>
|
||||
<button onClick={toggle}>Log out</button>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Server Side
|
||||
The server provides a `session` object that you can use to access the session data.
|
||||
|
||||
```ts title="server.ts"
|
||||
// somewhere in your server code
|
||||
import { auth } from "./auth"
|
||||
async function addToCart(request: Request){
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers, //it requies a header to be passed
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
For next js on RSC and server actions you can use import `headers` from `next/headers` and pass it to the `getSession` function.
|
||||
</Callout>
|
||||
|
||||
## Two Factor
|
||||
|
||||
### Introduction to plugins
|
||||
|
||||
One of the unique features of better auth is a plugins ecosystem. It allows you to add complex auth realted functionilty with small lines of code. Better auth come with many 1st party plugins, but you can also create your own plugins.
|
||||
|
||||
Below is an example of how to add two factor authentication using two factor plugin.
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step>
|
||||
### Server Configuration
|
||||
|
||||
To add a plugin, you need to import the plugin and pass it to the `plugins` option of the auth instance. For example, to add two facor authentication, you can use the following code:
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
import { twoFactor } from "better-auth/plugins"
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
//...rest of the options
|
||||
plugins: [ // [!code highlight]
|
||||
twoFactor({ // [!code highlight]
|
||||
issuer: "my-app" // [!code highlight]
|
||||
}) // [!code highlight]
|
||||
] // [!code highlight]
|
||||
})
|
||||
```
|
||||
now two factor related routes and method will be available on the server.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
### Migrate Database
|
||||
|
||||
once you have added the plugin, you need to migrate your database to add the necessary tables and fields. You can do this by running the following command:
|
||||
|
||||
```bash
|
||||
npx better-auth migrate
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
### Client Configuration
|
||||
|
||||
Once we're done with the server, we need to add the plugin to the client. To do this, you need to import the plugin and pass it to the `plugins` option of the auth client. For example, to add two facor authentication, you can use the following code:
|
||||
|
||||
```ts title="client.ts" /
|
||||
import { createAuthClient } from "better-auth/client";
|
||||
import { twoFactorClient } from "better-auth/client/plugins";
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [ // [!code highlight]
|
||||
twoFactorClient({ // [!code highlight]
|
||||
twoFactorPage: "/two-factor" // [!code highlight]
|
||||
}) // [!code highlight]
|
||||
] // [!code highlight]
|
||||
})
|
||||
```
|
||||
|
||||
now two factor related methods will be available on the client.
|
||||
|
||||
```ts title="profile.ts"
|
||||
// @filename: client.ts
|
||||
import { createAuthClient } from "better-auth/client";
|
||||
import { twoFactorClient } from "better-auth/client/plugins";
|
||||
|
||||
export const client = createAuthClient({
|
||||
plugins: [ // [!code highlight]
|
||||
twoFactorClient({ // [!code highlight]
|
||||
twoFactorPage: "/two-factor" // [!code highlight]
|
||||
}) // [!code highlight]
|
||||
] // [!code highlight]
|
||||
})
|
||||
|
||||
// ---cut---
|
||||
|
||||
// @filename: profile.ts
|
||||
// ---cut---
|
||||
import { client } from "./client"
|
||||
|
||||
const enableTwoFactor = async() => {
|
||||
const data = await client.twoFactor.enable() // this will enable two factor authentication for the signed in user
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
Next Setp: See the <Link href="/docs/plugins/2fa">the two factor plugin documentation</Link>.
|
||||
</Step>
|
||||
</Steps>
|
||||
@@ -15,7 +15,7 @@ better-auth
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
If you're using a separate client and server setup, make sure to install Better Auth in both parts of your project.
|
||||
If you're using a separate client and server setup, make sure to install Better Auth in both parts of your project. (currnelty better auth only supports web platforms more clients will be added soon)
|
||||
</Callout>
|
||||
</Step>
|
||||
|
||||
@@ -42,7 +42,7 @@ BETTER_AUTH_URL=http://localhost:3000 #Base URL of your app
|
||||
<Step>
|
||||
### Create A Better Auth Instance
|
||||
|
||||
Create a file named `auth.ts` or `auth.config.ts` in one of these locations:
|
||||
Create a file named `auth.ts` in one of these locations:
|
||||
- Project root
|
||||
- `lib/` folder
|
||||
- `utils/` folder
|
||||
@@ -67,9 +67,8 @@ export const auth = betterAuth({
|
||||
<Step>
|
||||
### Configure Database
|
||||
|
||||
Better Auth requires a database to store user data. It currently only supports `sqlite`, `postgresql` and `mysql`.
|
||||
|
||||
You can pass a database provider (sqlite, mysql, postgresql) and connection string directly to the auth instance.
|
||||
Better Auth requires a database to store user data. It uses <Link href="https://kysely.dev/">Kysely </Link> under the hood to connect to your database.
|
||||
`postgresql`, `mysql`, and `sqlite` are supported out of the box.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
@@ -83,9 +82,23 @@ export const auth = betterAuth({
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
Better auth uses <Link href="https://kysely.dev/">Kysely</Link> under the hood to connect to your database. You can also pass any dialect that is supported by Kysely to the database configration.
|
||||
You can also pass any dialect that is supported by Kysely to the database configration.
|
||||
</Callout>
|
||||
|
||||
**Example with LibsqlDialect:**
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: new LibsqlDialect({
|
||||
url: process.env.TURSO_DATABASE_URL || "",
|
||||
authToken: process.env.TURSO_AUTH_TOKEN || "",
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
@@ -133,7 +146,11 @@ To handle api requests, you need to set up a route handler on your server.
|
||||
|
||||
Create a new file or route in your framework's designated catch-all route handler. This route should handle requests for the path `/api/auth/*` (unless you've configured a different base path).
|
||||
|
||||
<Tabs items={["next-js", "nuxt", "svelte-kit", "solid-start", "hono"]} defaultValue="react">
|
||||
<Callout>
|
||||
Better auth supports any backend framework with standard Request and Response objects and offers helper functions for popular frameworks.
|
||||
</Callout>
|
||||
|
||||
<Tabs items={["next-js", "nuxt", "svelte-kit", "solid-start", "hono", "express"]} defaultValue="react">
|
||||
<Tab value="next-js">
|
||||
```ts title="/app/api/[...auth]/route.ts"
|
||||
import { auth } from "@/lib/auth"; // path to your auth file
|
||||
@@ -192,17 +209,34 @@ Create a new file or route in your framework's designated catch-all route handle
|
||||
serve(app);
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab value="express">
|
||||
```ts title="server.ts"
|
||||
import express from "express";
|
||||
import { toNodeHandler } from "better-auth/node";
|
||||
import { auth } from "./auth";
|
||||
|
||||
const app = express();
|
||||
const port = 8000;
|
||||
|
||||
app.get("/api/auth/*", toNodeHandler(auth));
|
||||
app.listen(port, () => {
|
||||
console.log(`Better Auth app listening on port ${port}`);
|
||||
});
|
||||
```
|
||||
This also works for any other node server framework like express, fastify, hapi, etc.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Create Client Instance
|
||||
|
||||
The client-side library helps you interact with the auth server. Better Auth comes with a client for all the popular frameworks inlcuding for vanilla javascript.
|
||||
The client-side library helps you interact with the auth server. Better Auth comes with a client for all the popular web frameworks inlcuding for vanilla javascript.
|
||||
|
||||
1. Import `createAuthClient` from the package for your framework (e.g., "better-auth/react" for React).
|
||||
2. Call the function to create your client.
|
||||
3. Pass the base url of your auth server to the client.
|
||||
3. Pass the base url of your auth server. (If the auth server is running on the same domain as your client, you can skip this step.)
|
||||
|
||||
<Callout type="info">
|
||||
If you're using a differnt base path other than `/api/auth` make sure to pass the whole url inlcuding the path. (e.g. `http://localhost:3000/custom-path/auth`)
|
||||
|
||||
198
docs/content/docs/integrations/next.mdx
Normal file
198
docs/content/docs/integrations/next.mdx
Normal file
@@ -0,0 +1,198 @@
|
||||
---
|
||||
title: Next JS integration
|
||||
description: Learn how to integrate Better Auth with Next.js
|
||||
---
|
||||
|
||||
Better Auth can be easily integrated with Next.js. It'll also comes with utilities to make it easier to use Better Auth with Next.js.
|
||||
|
||||
## Installation
|
||||
|
||||
First, install Better Auth
|
||||
|
||||
```package-install
|
||||
npm install better-auth
|
||||
```
|
||||
|
||||
## Set Environment Variables
|
||||
|
||||
Create a `.env` file in the root of your project and add the following environment variables:
|
||||
|
||||
**Set Base URL**
|
||||
```txt title=".env"
|
||||
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your app
|
||||
```
|
||||
|
||||
**Set Secret**
|
||||
|
||||
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
|
||||
```txt title=".env"
|
||||
BETTER_AUTH_SECRET=
|
||||
```
|
||||
<GenerateSecret/>
|
||||
|
||||
## Configure Server
|
||||
|
||||
### Create Better Auth instance
|
||||
|
||||
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
|
||||
|
||||
```ts twoslash title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: {
|
||||
provider: "sqlite", //change this to your database provider
|
||||
url: "./db.sqlite", // path to your database or connection string
|
||||
}
|
||||
// Refer to the api documentation for more configuration options
|
||||
})
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
|
||||
</Callout>
|
||||
|
||||
### Create API Route
|
||||
|
||||
We need to mount the handler to an API route. Create a route file inside `/api/[...auth]` directory. And add the following code:
|
||||
|
||||
```ts twoslash title="api/[...auth]/route.ts"
|
||||
//@filename: @/lib/auth.ts
|
||||
import { betterAuth } from "better-auth"
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: {
|
||||
provider: "sqlite", //change this to your database provider
|
||||
url: "./db.sqlite", // path to your database or connection string
|
||||
}
|
||||
// Refer to the api documentation for more configuration options
|
||||
})
|
||||
// ---cut---
|
||||
//@filename: api/[...auth]/route.ts
|
||||
//---cut---
|
||||
import { auth } from "@/lib/auth";
|
||||
import { toNextJsHandler } from "better-auth/next-js";
|
||||
|
||||
export const { GET, POST } = toNextJsHandler(auth.handler);
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
You can change the path on your better-auth configuration but it's recommended to keep it as `/api/[...auth]`
|
||||
</Callout>
|
||||
|
||||
### Migrate the database
|
||||
Run the following command to create the necessary tables in your database:
|
||||
|
||||
```bash
|
||||
npx better-auth migrate
|
||||
```
|
||||
|
||||
## Create a client
|
||||
|
||||
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
|
||||
|
||||
```ts twoslash title="client.ts"
|
||||
import { createAuthClient } from "better-auth/react" // make sure to import from better-auth/react
|
||||
|
||||
export const client = createAuthClient({
|
||||
//you can pass client configuration here
|
||||
})
|
||||
```
|
||||
|
||||
Once you have created the client, you can use it to sign up, sign in, and perform other actions.
|
||||
Some of the actinos are reactive. The client use [nano-store](https://github.com/nanostores/nanostores) to store the state and re-render the components when the state changes.
|
||||
|
||||
The client also uses [better-fetch](https://github.com/bekacru/better-fetch) to make the requests. You can pass the fetch configuration to the client.
|
||||
|
||||
## RSC and Server actions
|
||||
|
||||
The `api` object exported from the auth instance contains all the actions that you can perform on the server. Every endpoint made inside better auth is a invokable as a function. Including plugins endpoints.
|
||||
|
||||
**Example: Getting Session on a server action**
|
||||
|
||||
```tsx twoslash title="server.ts"
|
||||
//@filename: @/lib/auth.ts
|
||||
import { betterAuth } from "better-auth"
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: {
|
||||
provider: "sqlite", //change this to your database provider
|
||||
url: "./db.sqlite", // path to your database or connection string
|
||||
}
|
||||
// Refer to the api documentation for more configuration options
|
||||
})
|
||||
// ---cut---
|
||||
//@filename: server.ts
|
||||
//---cut---
|
||||
import { auth } from "@/lib/auth"
|
||||
import { headers } from "next/headers"
|
||||
|
||||
const someAuthenticatedAction = async () => {
|
||||
"use server";
|
||||
const session = await auth.api.getSession({
|
||||
headers: headers()
|
||||
})
|
||||
};
|
||||
```
|
||||
|
||||
**Example: Getting Session on a RSC**
|
||||
|
||||
```tsx
|
||||
import { auth } from "@/lib/auth"
|
||||
import { headers } from "next/headers"
|
||||
|
||||
export async function ServerComponent() {
|
||||
const session = await auth.api.getSession({
|
||||
headers: headers()
|
||||
})
|
||||
if(!session) {
|
||||
return <div>Not authenticated</div>
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h1>Welcome {session.user.name}</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Middleware
|
||||
|
||||
You can use the `authMiddleware` to protect your routes. It's a wrapper around the Next.js middleware.
|
||||
|
||||
```ts twoslash title="middleware.ts"s
|
||||
import { authMiddleware } from "better-auth/next-js"
|
||||
|
||||
export default authMiddleware({
|
||||
redirectTo: "/sign-in" // redirect to this path if the user is not authenticated
|
||||
})
|
||||
|
||||
export const config = {
|
||||
matcher: ['/dashboard/:path*'],
|
||||
}
|
||||
```
|
||||
|
||||
you can also pass custom redirect function
|
||||
|
||||
```ts
|
||||
import { authMiddleware } from "better-auth/next-js";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export default authMiddleware({
|
||||
customRedirect: async (session, request) => {
|
||||
const baseURL = request.nextUrl.origin;
|
||||
if (request.nextUrl.pathname === "/sign-in" && session) {
|
||||
return NextResponse.redirect(new URL("/dashboard", baseURL));
|
||||
}
|
||||
if (request.nextUrl.pathname === "/dashboard" && !session) {
|
||||
return NextResponse.redirect(new URL("/sign-in", baseURL));
|
||||
}
|
||||
return NextResponse.next();
|
||||
},
|
||||
});
|
||||
|
||||
export const config = {
|
||||
matcher: ["/dashboard", "/sign-in"],
|
||||
};
|
||||
```
|
||||
@@ -3,13 +3,13 @@ title: Introduction
|
||||
description: Introduction to Better Auth.
|
||||
---
|
||||
|
||||
Better Auth is a type-safe, framework-agnostic authentication library for TypeScript. It provides a comprehensive set of features out of the box and includes a plugin ecosystem that simplifies adding advanced functionalities with minimal code. Whether you need 2FA, multi-tenant support, or other complex features. It lets you focus on building your app instead of reinventing the wheel.
|
||||
Better Auth is framework-agnostic authentication and authorization library for TypeScript. It provides a comprehensive set of features out of the box and includes a plugin ecosystem that simplifies adding advanced functionalities with minimal code. Whether you need 2FA, multi-tenant support, or other complex features. It lets you focus on building your app instead of reinventing the wheel.
|
||||
|
||||
## Why Better Auth?
|
||||
|
||||
Authentication feels like a partially solved problem, existing open-source libraries often require a lot of additional code for anything beyond a simple login. Third-party services, while convenient, force you to store user data on their servers, which in some ways strips you of ownership and it also comes with its own set of problems. And obviously, these services aren't free and can get really expensive.
|
||||
> Authentication feels like a partially solved problem, existing open-source libraries often require a lot of additional code for anything beyond a simple login. Third-party services, while convenient, force you to store user data on their servers, which in some ways strips you of ownership and it also comes with its own set of problems. And obviously, these services aren't free and can get really expensive.
|
||||
|
||||
Better Auth offers a different approach. It provides a comprehensive authentication library from the core accompanied by a growings plugin ecosystem, that allows you to add many authentication related features in just minutes. Need multi-factor authentication? Simply use our 2FA plugin. Looking to support workspaces, organizations, member roles, or access control for you multi tenant apps? You're a plugin away from having a fully featured auth system.
|
||||
> Better Auth offers a different approach. It provides a comprehensive authentication library from the core accompanied by a growings plugin ecosystem, that allows you to add many authentication related features in just minutes. Need multi-factor authentication? Simply use our 2FA plugin. Looking to support workspaces, organizations, member roles, or access control for you multi tenant apps? In minutes you'll have a fully featured auth system.
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
"./solid-start": "./dist/solid-start.js",
|
||||
"./svelte": "./dist/svelte.js",
|
||||
"./next-js": "./dist/next-js.js",
|
||||
"./package.json": "./package.json"
|
||||
"./package.json": "./package.json",
|
||||
"./node": "./dist/node.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@simplewebauthn/types": "^10.0.0",
|
||||
|
||||
@@ -29,6 +29,7 @@ import { error } from "./routes/error";
|
||||
import { logger } from "../utils/logger";
|
||||
import { changePassword, updateUser } from "./routes/update-user";
|
||||
import type { BetterAuthPlugin } from "../plugins";
|
||||
import chalk from "chalk";
|
||||
|
||||
export function getEndpoints<
|
||||
C extends AuthContext,
|
||||
@@ -236,7 +237,42 @@ export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
|
||||
if (e instanceof APIError) {
|
||||
logger.warn(e);
|
||||
} else {
|
||||
logger.warn(e);
|
||||
if (typeof e === "object" && e !== null && "message" in e) {
|
||||
const errorMessage = e.message as string;
|
||||
if (!errorMessage || typeof errorMessage !== "string") {
|
||||
logger.warn(e);
|
||||
return;
|
||||
}
|
||||
if (errorMessage.includes("no such table")) {
|
||||
logger.error(
|
||||
`Please run ${chalk.green(
|
||||
"npx better-auth migrate",
|
||||
)} to create the tables. There are missing tables in your SQLite database.`,
|
||||
);
|
||||
} else if (
|
||||
errorMessage.includes("relation") &&
|
||||
errorMessage.includes("does not exist")
|
||||
) {
|
||||
logger.error(
|
||||
`Please run ${chalk.green(
|
||||
"npx better-auth migrate",
|
||||
)} to create the tables. There are missing tables in your PostgreSQL database.`,
|
||||
);
|
||||
} else if (
|
||||
errorMessage.includes("Table") &&
|
||||
errorMessage.includes("doesn't exist")
|
||||
) {
|
||||
logger.error(
|
||||
`Please run ${chalk.green(
|
||||
"npx better-auth migrate",
|
||||
)} to create the tables. There are missing tables in your MySQL database.`,
|
||||
);
|
||||
} else {
|
||||
logger.warn(e);
|
||||
}
|
||||
} else {
|
||||
logger.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -20,6 +20,7 @@ export const migrate = new Command("migrate")
|
||||
"--config <config>",
|
||||
"the path to the configuration file. defaults to the first configuration file found.",
|
||||
)
|
||||
.option("--y", "")
|
||||
.action(async (opts) => {
|
||||
const options = z
|
||||
.object({
|
||||
|
||||
6
packages/better-auth/src/integrations/node.ts
Normal file
6
packages/better-auth/src/integrations/node.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { toNodeHandler as toNode } from "better-call";
|
||||
import type { Auth } from "../auth";
|
||||
|
||||
export const toNodeHandler = (auth: Auth | Auth["handler"]) => {
|
||||
return "handler" in auth ? toNode(auth.handler) : toNode(auth);
|
||||
};
|
||||
@@ -77,7 +77,7 @@ export interface OrganizationOptions {
|
||||
*/
|
||||
ac?: AccessControl;
|
||||
/**
|
||||
*
|
||||
* Custom permissions for roles.
|
||||
*/
|
||||
roles?: {
|
||||
[key in "admin" | "member" | "owner"]?: Role<any>;
|
||||
@@ -143,7 +143,21 @@ export interface OrganizationOptions {
|
||||
request?: Request,
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Organization plugin for Better Auth. Organization allows you to create teams, members,
|
||||
* and manage access control for your users.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const auth = createAuth({
|
||||
* plugins: [
|
||||
* organization({
|
||||
* allowUserToCreateOrganization: true,
|
||||
* }),
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const organization = <O extends OrganizationOptions>(options?: O) => {
|
||||
const endpoints = {
|
||||
createOrganization,
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import { getSession } from "../../api/routes";
|
||||
import { BetterAuthError } from "../../error/better-auth-error";
|
||||
import { getIp } from "../../utils/get-request-ip";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
export async function getRateLimitKey(req: Request) {
|
||||
if (req.headers.get("Authorization") || req.headers.get("cookie")) {
|
||||
const session = await getSession({
|
||||
headers: req.headers,
|
||||
});
|
||||
if (session) {
|
||||
return session.user.id;
|
||||
try {
|
||||
const session = await getSession({
|
||||
headers: req.headers,
|
||||
// @ts-ignore
|
||||
_flag: undefined,
|
||||
});
|
||||
if (session) {
|
||||
return session.user.id;
|
||||
}
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
const ip = getIp(req);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createAuthMiddleware } from "../../api/call";
|
||||
import type { GenericEndpointContext } from "../../types/context";
|
||||
import type { BetterAuthPlugin } from "../../types/plugins";
|
||||
import { getRateLimitKey } from "./get-key";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
interface RateLimit {
|
||||
key: string;
|
||||
@@ -34,13 +35,22 @@ export interface RateLimitOptions {
|
||||
* @default "ip" or "userId" if the user is logged in.
|
||||
*/
|
||||
getKey?: (request: Request) => string | Promise<string>;
|
||||
storage?:
|
||||
| "database"
|
||||
| "memory"
|
||||
| {
|
||||
get: (key: string) => Promise<RateLimit | undefined>;
|
||||
set: (key: string, value: RateLimit) => Promise<void>;
|
||||
};
|
||||
storage?: {
|
||||
custom?: {
|
||||
get: (key: string) => Promise<RateLimit | undefined>;
|
||||
set: (key: string, value: RateLimit) => Promise<void>;
|
||||
};
|
||||
/**
|
||||
* The provider to use for rate limiting.
|
||||
* @default "database"
|
||||
*/
|
||||
provider?: "database" | "memory";
|
||||
/**
|
||||
* The name of the table to use for rate limiting. Only used if provider is "database".
|
||||
* @default "rateLimit"
|
||||
*/
|
||||
tableName?: string;
|
||||
};
|
||||
/**
|
||||
* Custom rate limiting function.
|
||||
*/
|
||||
@@ -91,7 +101,10 @@ export interface RateLimitOptions {
|
||||
*/
|
||||
export const rateLimiter = (options: RateLimitOptions) => {
|
||||
const opts = {
|
||||
storage: "database",
|
||||
storage: {
|
||||
provider: "database",
|
||||
tableName: "rateLimit",
|
||||
},
|
||||
max: 100,
|
||||
window: 15 * 60,
|
||||
specialRules: [
|
||||
@@ -105,7 +118,7 @@ export const rateLimiter = (options: RateLimitOptions) => {
|
||||
...options,
|
||||
} satisfies RateLimitOptions;
|
||||
const schema =
|
||||
opts.storage === "database"
|
||||
opts.storage.provider === "database"
|
||||
? ({
|
||||
rateLimit: {
|
||||
fields: {
|
||||
@@ -135,24 +148,28 @@ export const rateLimiter = (options: RateLimitOptions) => {
|
||||
return result as RateLimit | undefined;
|
||||
},
|
||||
set: async (key: string, value: RateLimit, isNew: boolean = true) => {
|
||||
if (isNew) {
|
||||
await db
|
||||
.insertInto("rateLimit")
|
||||
.values({
|
||||
key,
|
||||
count: value.count,
|
||||
lastRequest: value.lastRequest,
|
||||
})
|
||||
.execute();
|
||||
} else {
|
||||
await db
|
||||
.updateTable("rateLimit")
|
||||
.set({
|
||||
count: value.count,
|
||||
lastRequest: value.lastRequest,
|
||||
})
|
||||
.where("key", "=", key)
|
||||
.execute();
|
||||
try {
|
||||
if (isNew) {
|
||||
await db
|
||||
.insertInto(opts.storage.tableName ?? "rateLimit")
|
||||
.values({
|
||||
key,
|
||||
count: value.count,
|
||||
lastRequest: value.lastRequest,
|
||||
})
|
||||
.execute();
|
||||
} else {
|
||||
await db
|
||||
.updateTable(opts.storage.tableName ?? "rateLimit")
|
||||
.set({
|
||||
count: value.count,
|
||||
lastRequest: value.lastRequest,
|
||||
})
|
||||
.where("key", "=", key)
|
||||
.execute();
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Error setting rate limit", e);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -188,12 +205,11 @@ export const rateLimiter = (options: RateLimitOptions) => {
|
||||
return;
|
||||
}
|
||||
const key = await getRateLimitKey(ctx.request);
|
||||
const storage =
|
||||
opts.storage === "database"
|
||||
const storage = opts.storage.custom
|
||||
? opts.storage.custom
|
||||
: opts.storage.provider === "database"
|
||||
? createDBStorage(ctx)
|
||||
: opts.storage === "memory"
|
||||
? createMemoryStorage()
|
||||
: opts.storage;
|
||||
: createMemoryStorage();
|
||||
const rateLimit = await storage.get(key);
|
||||
if (!rateLimit) {
|
||||
await storage.set(key, {
|
||||
@@ -209,8 +225,21 @@ export const rateLimiter = (options: RateLimitOptions) => {
|
||||
rateLimit.lastRequest >= windowStart &&
|
||||
rateLimit.count >= opts.max
|
||||
) {
|
||||
throw new APIError("TOO_MANY_REQUESTS", {
|
||||
message: "Too many requests",
|
||||
return new Response(null, {
|
||||
status: 429,
|
||||
statusText: "Too Many Requests",
|
||||
headers: {
|
||||
"X-RateLimit-Window": opts.window.toString(),
|
||||
"X-RateLimit-Max": opts.max.toString(),
|
||||
"X-RateLimit-Remaining": (
|
||||
opts.max - rateLimit.count
|
||||
).toString(),
|
||||
"X-RateLimit-Reset": (
|
||||
rateLimit.lastRequest +
|
||||
opts.window * 1000 -
|
||||
now
|
||||
).toString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ describe("rate-limiter", async () => {
|
||||
plugins: [
|
||||
rateLimiter({
|
||||
enabled: true,
|
||||
storage: "memory",
|
||||
storage: {
|
||||
provider: "memory",
|
||||
},
|
||||
max: 10,
|
||||
window: 10,
|
||||
}),
|
||||
|
||||
@@ -16,6 +16,7 @@ export default defineConfig({
|
||||
access: "./src/plugins/organization/access/index.ts",
|
||||
"solid-start": "./src/integrations/solid-start.ts",
|
||||
"next-js": "./src/integrations/next-js.ts",
|
||||
node: "./src/integrations/node.ts",
|
||||
},
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
|
||||
312
pnpm-lock.yaml
generated
312
pnpm-lock.yaml
generated
@@ -543,6 +543,25 @@ importers:
|
||||
specifier: ^5.4.5
|
||||
version: 5.5.4
|
||||
|
||||
dev/express:
|
||||
dependencies:
|
||||
better-auth:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/better-auth
|
||||
express:
|
||||
specifier: ^4.21.0
|
||||
version: 4.21.0
|
||||
tsx:
|
||||
specifier: ^4.19.0
|
||||
version: 4.19.0
|
||||
devDependencies:
|
||||
'@types/bun':
|
||||
specifier: latest
|
||||
version: 1.1.9
|
||||
'@types/express':
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
|
||||
dev/nuxtjs:
|
||||
dependencies:
|
||||
better-auth:
|
||||
@@ -5394,12 +5413,18 @@ packages:
|
||||
'@types/better-sqlite3@7.6.11':
|
||||
resolution: {integrity: sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg==}
|
||||
|
||||
'@types/body-parser@1.19.5':
|
||||
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
||||
|
||||
'@types/braces@3.0.4':
|
||||
resolution: {integrity: sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA==}
|
||||
|
||||
'@types/bun@1.1.9':
|
||||
resolution: {integrity: sha512-SXJRejXpmAc3qxyN/YS4/JGWEzLf4dDBa5fLtRDipQXHqNccuMU4EUYCooXNTsylG0DmwFQsGgEDHxZF+3DqRw==}
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||
|
||||
'@types/cookie@0.6.0':
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
|
||||
@@ -5448,12 +5473,21 @@ packages:
|
||||
'@types/estree@1.0.5':
|
||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||
|
||||
'@types/express-serve-static-core@4.19.5':
|
||||
resolution: {integrity: sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==}
|
||||
|
||||
'@types/express@4.17.21':
|
||||
resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==}
|
||||
|
||||
'@types/hammerjs@2.0.45':
|
||||
resolution: {integrity: sha512-qkcUlZmX6c4J8q45taBKTL3p+LbITgyx7qhlPYOdOHZB7B31K0mXbP5YA7i7SgDeEGuI9MnumiKPEMrxg8j3KQ==}
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||
|
||||
'@types/http-errors@2.0.4':
|
||||
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
|
||||
|
||||
'@types/http-proxy@1.17.15':
|
||||
resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==}
|
||||
|
||||
@@ -5484,6 +5518,9 @@ packages:
|
||||
'@types/micromatch@4.0.9':
|
||||
resolution: {integrity: sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==}
|
||||
|
||||
'@types/mime@1.3.5':
|
||||
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||
|
||||
'@types/ms@0.7.34':
|
||||
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
|
||||
|
||||
@@ -5517,6 +5554,12 @@ packages:
|
||||
'@types/pug@2.0.10':
|
||||
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
|
||||
|
||||
'@types/qs@6.9.16':
|
||||
resolution: {integrity: sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==}
|
||||
|
||||
'@types/range-parser@1.2.7':
|
||||
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
||||
|
||||
'@types/react-dom@18.3.0':
|
||||
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
|
||||
|
||||
@@ -5538,6 +5581,12 @@ packages:
|
||||
'@types/scheduler@0.23.0':
|
||||
resolution: {integrity: sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==}
|
||||
|
||||
'@types/send@0.17.4':
|
||||
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
|
||||
|
||||
'@types/serve-static@1.15.7':
|
||||
resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
|
||||
|
||||
'@types/set-cookie-parser@2.4.10':
|
||||
resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==}
|
||||
|
||||
@@ -6036,6 +6085,9 @@ packages:
|
||||
resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
array-flatten@1.1.1:
|
||||
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
|
||||
|
||||
array-includes@3.1.8:
|
||||
resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -6246,6 +6298,10 @@ packages:
|
||||
bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
|
||||
body-parser@1.20.3:
|
||||
resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
|
||||
boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
|
||||
@@ -6348,6 +6404,10 @@ packages:
|
||||
resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
bytes@3.1.2:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
c12@1.11.1:
|
||||
resolution: {integrity: sha512-KDU0TvSvVdaYcQKQ6iPHATGz/7p/KiVjPg4vQrB6Jg/wX9R0yl5RZxWm9IoZqaIHD2+6PZd81+KMGwRr/lRIUg==}
|
||||
peerDependencies:
|
||||
@@ -6685,12 +6745,23 @@ packages:
|
||||
console-control-strings@1.1.0:
|
||||
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
|
||||
|
||||
content-disposition@0.5.4:
|
||||
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
content-type@1.0.5:
|
||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cookie-es@1.2.2:
|
||||
resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==}
|
||||
|
||||
cookie-signature@1.0.6:
|
||||
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||
|
||||
cookie-signature@1.2.1:
|
||||
resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==}
|
||||
engines: {node: '>=6.6.0'}
|
||||
@@ -7424,6 +7495,10 @@ packages:
|
||||
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
encodeurl@2.0.0:
|
||||
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
end-of-stream@1.4.4:
|
||||
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
|
||||
|
||||
@@ -7863,6 +7938,10 @@ packages:
|
||||
exponential-backoff@3.1.1:
|
||||
resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==}
|
||||
|
||||
express@4.21.0:
|
||||
resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
|
||||
ext@1.7.0:
|
||||
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
|
||||
|
||||
@@ -7968,6 +8047,10 @@ packages:
|
||||
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
finalhandler@1.3.1:
|
||||
resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
find-cache-dir@2.1.0:
|
||||
resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -8031,6 +8114,10 @@ packages:
|
||||
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
@@ -8500,6 +8587,10 @@ packages:
|
||||
hyphenate-style-name@1.1.0:
|
||||
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -9527,6 +9618,10 @@ packages:
|
||||
mdn-data@2.0.30:
|
||||
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
||||
|
||||
media-typer@0.3.0:
|
||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
memfs-browser@3.5.10302:
|
||||
resolution: {integrity: sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==}
|
||||
|
||||
@@ -9551,6 +9646,9 @@ packages:
|
||||
resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==}
|
||||
engines: {node: '>=12.13'}
|
||||
|
||||
merge-descriptors@1.0.3:
|
||||
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
|
||||
|
||||
merge-options@3.0.4:
|
||||
resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -9571,6 +9669,10 @@ packages:
|
||||
meshoptimizer@0.18.1:
|
||||
resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
|
||||
|
||||
methods@1.1.2:
|
||||
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
metro-babel-transformer@0.80.11:
|
||||
resolution: {integrity: sha512-ViWx0rdAIyfX73HiEC1TXW0hs42fKPuPm4VxmbazM8XBIjykGnkk5i2OVm3+uA1YnAC242AzbX6uy7uJh9Pv3w==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -10479,6 +10581,9 @@ packages:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
|
||||
path-to-regexp@0.1.10:
|
||||
resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
|
||||
|
||||
path-to-regexp@6.2.2:
|
||||
resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==}
|
||||
|
||||
@@ -10972,6 +11077,10 @@ packages:
|
||||
protocols@2.0.1:
|
||||
resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
pump@3.0.0:
|
||||
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
|
||||
|
||||
@@ -10993,6 +11102,10 @@ packages:
|
||||
resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==}
|
||||
hasBin: true
|
||||
|
||||
qs@6.13.0:
|
||||
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
query-string@7.1.3:
|
||||
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -11021,6 +11134,10 @@ packages:
|
||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
raw-body@2.5.2:
|
||||
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
rc9@2.1.2:
|
||||
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
|
||||
|
||||
@@ -11573,6 +11690,10 @@ packages:
|
||||
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
send@0.19.0:
|
||||
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
seq-queue@0.0.5:
|
||||
resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==}
|
||||
|
||||
@@ -11600,6 +11721,10 @@ packages:
|
||||
resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
serve-static@1.16.2:
|
||||
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
server-only@0.0.1:
|
||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||
|
||||
@@ -12456,6 +12581,10 @@ packages:
|
||||
resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
type@2.7.3:
|
||||
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
|
||||
|
||||
@@ -19549,12 +19678,21 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 20.14.12
|
||||
|
||||
'@types/body-parser@1.19.5':
|
||||
dependencies:
|
||||
'@types/connect': 3.4.38
|
||||
'@types/node': 20.14.12
|
||||
|
||||
'@types/braces@3.0.4': {}
|
||||
|
||||
'@types/bun@1.1.9':
|
||||
dependencies:
|
||||
bun-types: 1.1.27
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
dependencies:
|
||||
'@types/node': 20.14.12
|
||||
|
||||
'@types/cookie@0.6.0': {}
|
||||
|
||||
'@types/d3-array@3.2.1': {}
|
||||
@@ -19597,12 +19735,28 @@ snapshots:
|
||||
|
||||
'@types/estree@1.0.5': {}
|
||||
|
||||
'@types/express-serve-static-core@4.19.5':
|
||||
dependencies:
|
||||
'@types/node': 20.14.12
|
||||
'@types/qs': 6.9.16
|
||||
'@types/range-parser': 1.2.7
|
||||
'@types/send': 0.17.4
|
||||
|
||||
'@types/express@4.17.21':
|
||||
dependencies:
|
||||
'@types/body-parser': 1.19.5
|
||||
'@types/express-serve-static-core': 4.19.5
|
||||
'@types/qs': 6.9.16
|
||||
'@types/serve-static': 1.15.7
|
||||
|
||||
'@types/hammerjs@2.0.45': {}
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/http-errors@2.0.4': {}
|
||||
|
||||
'@types/http-proxy@1.17.15':
|
||||
dependencies:
|
||||
'@types/node': 20.14.12
|
||||
@@ -19635,6 +19789,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/braces': 3.0.4
|
||||
|
||||
'@types/mime@1.3.5': {}
|
||||
|
||||
'@types/ms@0.7.34': {}
|
||||
|
||||
'@types/node-forge@1.3.11':
|
||||
@@ -19674,6 +19830,10 @@ snapshots:
|
||||
|
||||
'@types/pug@2.0.10': {}
|
||||
|
||||
'@types/qs@6.9.16': {}
|
||||
|
||||
'@types/range-parser@1.2.7': {}
|
||||
|
||||
'@types/react-dom@18.3.0':
|
||||
dependencies:
|
||||
'@types/react': 18.3.3
|
||||
@@ -19701,6 +19861,17 @@ snapshots:
|
||||
|
||||
'@types/scheduler@0.23.0': {}
|
||||
|
||||
'@types/send@0.17.4':
|
||||
dependencies:
|
||||
'@types/mime': 1.3.5
|
||||
'@types/node': 20.14.12
|
||||
|
||||
'@types/serve-static@1.15.7':
|
||||
dependencies:
|
||||
'@types/http-errors': 2.0.4
|
||||
'@types/node': 20.14.12
|
||||
'@types/send': 0.17.4
|
||||
|
||||
'@types/set-cookie-parser@2.4.10':
|
||||
dependencies:
|
||||
'@types/node': 20.14.12
|
||||
@@ -20488,6 +20659,8 @@ snapshots:
|
||||
call-bind: 1.0.7
|
||||
is-array-buffer: 3.0.4
|
||||
|
||||
array-flatten@1.1.1: {}
|
||||
|
||||
array-includes@3.1.8:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
@@ -20855,6 +21028,23 @@ snapshots:
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
body-parser@1.20.3:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
content-type: 1.0.5
|
||||
debug: 2.6.9
|
||||
depd: 2.0.0
|
||||
destroy: 1.2.0
|
||||
http-errors: 2.0.0
|
||||
iconv-lite: 0.4.24
|
||||
on-finished: 2.4.1
|
||||
qs: 6.13.0
|
||||
raw-body: 2.5.2
|
||||
type-is: 1.6.18
|
||||
unpipe: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
|
||||
boxen@7.1.1:
|
||||
@@ -20977,6 +21167,8 @@ snapshots:
|
||||
|
||||
bytes@3.0.0: {}
|
||||
|
||||
bytes@3.1.2: {}
|
||||
|
||||
c12@1.11.1(magicast@0.3.5):
|
||||
dependencies:
|
||||
chokidar: 3.6.0
|
||||
@@ -21349,10 +21541,18 @@ snapshots:
|
||||
|
||||
console-control-strings@1.1.0: {}
|
||||
|
||||
content-disposition@0.5.4:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
content-type@1.0.5: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cookie-es@1.2.2: {}
|
||||
|
||||
cookie-signature@1.0.6: {}
|
||||
|
||||
cookie-signature@1.2.1: {}
|
||||
|
||||
cookie@0.6.0: {}
|
||||
@@ -22040,6 +22240,8 @@ snapshots:
|
||||
|
||||
encodeurl@1.0.2: {}
|
||||
|
||||
encodeurl@2.0.0: {}
|
||||
|
||||
end-of-stream@1.4.4:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
@@ -22812,6 +23014,42 @@ snapshots:
|
||||
|
||||
exponential-backoff@3.1.1: {}
|
||||
|
||||
express@4.21.0:
|
||||
dependencies:
|
||||
accepts: 1.3.8
|
||||
array-flatten: 1.1.1
|
||||
body-parser: 1.20.3
|
||||
content-disposition: 0.5.4
|
||||
content-type: 1.0.5
|
||||
cookie: 0.6.0
|
||||
cookie-signature: 1.0.6
|
||||
debug: 2.6.9
|
||||
depd: 2.0.0
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
finalhandler: 1.3.1
|
||||
fresh: 0.5.2
|
||||
http-errors: 2.0.0
|
||||
merge-descriptors: 1.0.3
|
||||
methods: 1.1.2
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
path-to-regexp: 0.1.10
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.13.0
|
||||
range-parser: 1.2.1
|
||||
safe-buffer: 5.2.1
|
||||
send: 0.19.0
|
||||
serve-static: 1.16.2
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.1
|
||||
type-is: 1.6.18
|
||||
utils-merge: 1.0.1
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
ext@1.7.0:
|
||||
dependencies:
|
||||
type: 2.7.3
|
||||
@@ -22932,6 +23170,18 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
finalhandler@1.3.1:
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
statuses: 2.0.1
|
||||
unpipe: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
find-cache-dir@2.1.0:
|
||||
dependencies:
|
||||
commondir: 1.0.1
|
||||
@@ -22992,6 +23242,8 @@ snapshots:
|
||||
dependencies:
|
||||
fetch-blob: 3.2.0
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
framer-motion@11.3.30(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
@@ -23628,6 +23880,10 @@ snapshots:
|
||||
|
||||
hyphenate-style-name@1.1.0: {}
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
@@ -24740,6 +24996,8 @@ snapshots:
|
||||
|
||||
mdn-data@2.0.30: {}
|
||||
|
||||
media-typer@0.3.0: {}
|
||||
|
||||
memfs-browser@3.5.10302:
|
||||
dependencies:
|
||||
memfs: 3.5.3
|
||||
@@ -24771,6 +25029,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-what: 4.1.16
|
||||
|
||||
merge-descriptors@1.0.3: {}
|
||||
|
||||
merge-options@3.0.4:
|
||||
dependencies:
|
||||
is-plain-obj: 2.1.0
|
||||
@@ -24814,6 +25074,8 @@ snapshots:
|
||||
|
||||
meshoptimizer@0.18.1: {}
|
||||
|
||||
methods@1.1.2: {}
|
||||
|
||||
metro-babel-transformer@0.80.11:
|
||||
dependencies:
|
||||
'@babel/core': 7.25.2
|
||||
@@ -26255,6 +26517,8 @@ snapshots:
|
||||
lru-cache: 10.4.3
|
||||
minipass: 7.1.2
|
||||
|
||||
path-to-regexp@0.1.10: {}
|
||||
|
||||
path-to-regexp@6.2.2: {}
|
||||
|
||||
path-type@4.0.0: {}
|
||||
@@ -26731,6 +26995,11 @@ snapshots:
|
||||
|
||||
protocols@2.0.1: {}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
|
||||
pump@3.0.0:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.4
|
||||
@@ -26748,6 +27017,10 @@ snapshots:
|
||||
|
||||
qrcode-terminal@0.11.0: {}
|
||||
|
||||
qs@6.13.0:
|
||||
dependencies:
|
||||
side-channel: 1.0.6
|
||||
|
||||
query-string@7.1.3:
|
||||
dependencies:
|
||||
decode-uri-component: 0.2.2
|
||||
@@ -26773,6 +27046,13 @@ snapshots:
|
||||
|
||||
range-parser@1.2.1: {}
|
||||
|
||||
raw-body@2.5.2:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
http-errors: 2.0.0
|
||||
iconv-lite: 0.4.24
|
||||
unpipe: 1.0.0
|
||||
|
||||
rc9@2.1.2:
|
||||
dependencies:
|
||||
defu: 6.1.4
|
||||
@@ -27674,6 +27954,24 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
send@0.19.0:
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
depd: 2.0.0
|
||||
destroy: 1.2.0
|
||||
encodeurl: 1.0.2
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
fresh: 0.5.2
|
||||
http-errors: 2.0.0
|
||||
mime: 1.6.0
|
||||
ms: 2.1.3
|
||||
on-finished: 2.4.1
|
||||
range-parser: 1.2.1
|
||||
statuses: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
seq-queue@0.0.5: {}
|
||||
|
||||
serialize-error@2.1.0: {}
|
||||
@@ -27701,6 +27999,15 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
serve-static@1.16.2:
|
||||
dependencies:
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 0.19.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
server-only@0.0.1: {}
|
||||
|
||||
set-blocking@2.0.0: {}
|
||||
@@ -28716,6 +29023,11 @@ snapshots:
|
||||
|
||||
type-fest@3.13.1: {}
|
||||
|
||||
type-is@1.6.18:
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
type@2.7.3: {}
|
||||
|
||||
typed-array-buffer@1.0.2:
|
||||
|
||||
7
todo.md
7
todo.md
@@ -9,15 +9,14 @@
|
||||
[x] add delete organization endpoint
|
||||
[ ] add callback url on otp and backup code verification
|
||||
[ ] fix bun problem
|
||||
[ ] allow enabling two factor automatically for users
|
||||
[ ] change the pg driver to https://www.npmjs.com/package/postgres (maybe)
|
||||
[ ] fix the issue with the client triggers not working fot 2 consecutive calls
|
||||
[x] fix the issue with the client triggers not working fot 2 consecutive calls
|
||||
|
||||
|
||||
## Docs
|
||||
[x] specify everywhere `auth` should be exported
|
||||
[ ] add a note about better-sqlite3 requiring to be added to webpack externals or find alternative that doesn't require it
|
||||
[-] add a note about better-sqlite3 requiring to be added to webpack externals or find alternative that doesn't require it
|
||||
[ ] add a section about updating user and changing password
|
||||
[ ] mention how users can get user and session types
|
||||
[ ] add a doc about account linking
|
||||
[ ] remove the section about using useSession in next with initialValue
|
||||
[ ] rate limiting docs
|
||||
Reference in New Issue
Block a user