From 0fbc3dc8d1174b062a8a25ec9f580db0f877f742 Mon Sep 17 00:00:00 2001 From: Bereket Engida Date: Fri, 20 Sep 2024 13:31:28 +0300 Subject: [PATCH] x --- demo/nextjs/lib/auth.ts | 11 +- dev/express/.gitignore | 175 +++++++++ dev/express/README.md | 15 + dev/express/auth.ts | 8 + dev/express/index.ts | 12 + dev/express/package.json | 17 + dev/express/tsconfig.json | 27 ++ .../docs/authentication/apple.mdx | 0 .../docs/authentication/discord.mdx | 0 .../docs/authentication/email-password.mdx | 232 ++++++++++++ .../docs/authentication/facebook.mdx | 0 .../docs/authentication/github.mdx | 0 .../docs/authentication/google.mdx | 0 .../docs/authentication/spotify.mdx | 0 .../docs/authentication/twitch.mdx | 0 .../docs/authentication/twitter.mdx | 0 docs/content/docs/basic-usage.mdx | 347 ++++++++++++++++++ docs/content/docs/installation.mdx | 54 ++- docs/content/docs/integrations/next.mdx | 198 ++++++++++ docs/content/docs/introduction.mdx | 6 +- packages/better-auth/package.json | 3 +- packages/better-auth/src/api/index.ts | 38 +- .../better-auth/src/cli/commands/migrate.ts | 1 + packages/better-auth/src/integrations/node.ts | 6 + .../src/plugins/organization/organization.ts | 18 +- .../src/plugins/rate-limiter/get-key.ts | 17 +- .../src/plugins/rate-limiter/index.ts | 97 +++-- .../plugins/rate-limiter/rate-limiter.test.ts | 4 +- packages/better-auth/tsup.config.ts | 1 + pnpm-lock.yaml | 312 ++++++++++++++++ todo.md | 9 +- 31 files changed, 1544 insertions(+), 64 deletions(-) create mode 100644 dev/express/.gitignore create mode 100644 dev/express/README.md create mode 100644 dev/express/auth.ts create mode 100644 dev/express/index.ts create mode 100644 dev/express/package.json create mode 100644 dev/express/tsconfig.json rename docs/{_content => content}/docs/authentication/apple.mdx (100%) rename docs/{_content => content}/docs/authentication/discord.mdx (100%) create mode 100644 docs/content/docs/authentication/email-password.mdx rename docs/{_content => content}/docs/authentication/facebook.mdx (100%) rename docs/{_content => content}/docs/authentication/github.mdx (100%) rename docs/{_content => content}/docs/authentication/google.mdx (100%) rename docs/{_content => content}/docs/authentication/spotify.mdx (100%) rename docs/{_content => content}/docs/authentication/twitch.mdx (100%) rename docs/{_content => content}/docs/authentication/twitter.mdx (100%) create mode 100644 docs/content/docs/basic-usage.mdx create mode 100644 docs/content/docs/integrations/next.mdx create mode 100644 packages/better-auth/src/integrations/node.ts diff --git a/demo/nextjs/lib/auth.ts b/demo/nextjs/lib/auth.ts index f1fdb8fe..106e6ad3 100644 --- a/demo/nextjs/lib/auth.ts +++ b/demo/nextjs/lib/auth.ts @@ -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({ diff --git a/dev/express/.gitignore b/dev/express/.gitignore new file mode 100644 index 00000000..9b1ee42e --- /dev/null +++ b/dev/express/.gitignore @@ -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 diff --git a/dev/express/README.md b/dev/express/README.md new file mode 100644 index 00000000..bbb379a8 --- /dev/null +++ b/dev/express/README.md @@ -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. diff --git a/dev/express/auth.ts b/dev/express/auth.ts new file mode 100644 index 00000000..4744b7fe --- /dev/null +++ b/dev/express/auth.ts @@ -0,0 +1,8 @@ +import { betterAuth } from "better-auth"; + +export const auth = betterAuth({ + database: { + provider: "sqlite", + url: "./db.sqlite", + }, +}); diff --git a/dev/express/index.ts b/dev/express/index.ts new file mode 100644 index 00000000..6bbf6fcb --- /dev/null +++ b/dev/express/index.ts @@ -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}`); +}); diff --git a/dev/express/package.json b/dev/express/package.json new file mode 100644 index 00000000..ce9211d9 --- /dev/null +++ b/dev/express/package.json @@ -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" + } +} \ No newline at end of file diff --git a/dev/express/tsconfig.json b/dev/express/tsconfig.json new file mode 100644 index 00000000..238655f2 --- /dev/null +++ b/dev/express/tsconfig.json @@ -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 + } +} diff --git a/docs/_content/docs/authentication/apple.mdx b/docs/content/docs/authentication/apple.mdx similarity index 100% rename from docs/_content/docs/authentication/apple.mdx rename to docs/content/docs/authentication/apple.mdx diff --git a/docs/_content/docs/authentication/discord.mdx b/docs/content/docs/authentication/discord.mdx similarity index 100% rename from docs/_content/docs/authentication/discord.mdx rename to docs/content/docs/authentication/discord.mdx diff --git a/docs/content/docs/authentication/email-password.mdx b/docs/content/docs/authentication/email-password.mdx new file mode 100644 index 00000000..4216b6ea --- /dev/null +++ b/docs/content/docs/authentication/email-password.mdx @@ -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. + + + If you prefer username-based authentication, check out the username plugin. It extends the email and password authenticator with username support. + + +## 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] +}) +``` + + +If it's not enabled, it'll not allow you to sign in or sign up with email and password. + + + +## 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. + + +Hover over the `data` object to see the shape of the response. + + +### 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 + + + \ No newline at end of file diff --git a/docs/_content/docs/authentication/facebook.mdx b/docs/content/docs/authentication/facebook.mdx similarity index 100% rename from docs/_content/docs/authentication/facebook.mdx rename to docs/content/docs/authentication/facebook.mdx diff --git a/docs/_content/docs/authentication/github.mdx b/docs/content/docs/authentication/github.mdx similarity index 100% rename from docs/_content/docs/authentication/github.mdx rename to docs/content/docs/authentication/github.mdx diff --git a/docs/_content/docs/authentication/google.mdx b/docs/content/docs/authentication/google.mdx similarity index 100% rename from docs/_content/docs/authentication/google.mdx rename to docs/content/docs/authentication/google.mdx diff --git a/docs/_content/docs/authentication/spotify.mdx b/docs/content/docs/authentication/spotify.mdx similarity index 100% rename from docs/_content/docs/authentication/spotify.mdx rename to docs/content/docs/authentication/spotify.mdx diff --git a/docs/_content/docs/authentication/twitch.mdx b/docs/content/docs/authentication/twitch.mdx similarity index 100% rename from docs/_content/docs/authentication/twitch.mdx rename to docs/content/docs/authentication/twitch.mdx diff --git a/docs/_content/docs/authentication/twitter.mdx b/docs/content/docs/authentication/twitter.mdx similarity index 100% rename from docs/_content/docs/authentication/twitter.mdx rename to docs/content/docs/authentication/twitter.mdx diff --git a/docs/content/docs/basic-usage.mdx b/docs/content/docs/basic-usage.mdx new file mode 100644 index 00000000..77523703 --- /dev/null +++ b/docs/content/docs/basic-usage.mdx @@ -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. + + + If you want to use username instead of email, you can use username Plugin. + + +### 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] +}) +``` + + + See the Provider section for more information on how to configure each provider. + + +### 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. + + + + ```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 ( + //... + ) + } + ``` + + + + ```vue title="user.vue" + + ``` + + + + ```svelte title="user.svelte" + + +
+
+ {#if $session} +
+

+ {$session?.user.name} +

+

+ {$session?.user.email} +

+ +
+ {:else} + + {/if} +
+
+ ``` +
+ + + ```tsx title="user.tsx" + import { client } from "~/lib/client"; + import { Show } from 'solid-js'; + + export default function Home() { + const session = client.useSession() + return ( + Log in} + > + + + ); + } + ``` + +
+ +### 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 + }) +} +``` + + + For next js on RSC and server actions you can use import `headers` from `next/headers` and pass it to the `getSession` function. + + +## 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. + + + + +### 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. + + + +### 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 +``` + + + +### 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 +} +``` + + + +Next Setp: See the the two factor plugin documentation. + + \ No newline at end of file diff --git a/docs/content/docs/installation.mdx b/docs/content/docs/installation.mdx index f0059712..8a2d9f74 100644 --- a/docs/content/docs/installation.mdx +++ b/docs/content/docs/installation.mdx @@ -15,7 +15,7 @@ better-auth ``` -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) @@ -42,7 +42,7 @@ BETTER_AUTH_URL=http://localhost:3000 #Base URL of your app ### 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({ ### 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 Kysely 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({ ``` -Better auth uses Kysely 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. +**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 || "", + }), +}); +``` + @@ -131,9 +144,13 @@ You can use even more authentication methods like passkey, username, magic link ### Mount Handler 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). +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). - + +Better auth supports any backend framework with standard Request and Response objects and offers helper functions for popular frameworks. + + + ```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); ``` + + + ```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. + ### 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.) 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`) diff --git a/docs/content/docs/integrations/next.mdx b/docs/content/docs/integrations/next.mdx new file mode 100644 index 00000000..d42505c5 --- /dev/null +++ b/docs/content/docs/integrations/next.mdx @@ -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= +``` + + +## 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 +}) +``` + + +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. + + +### 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); +``` + + + You can change the path on your better-auth configuration but it's recommended to keep it as `/api/[...auth]` + + +### 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
Not authenticated
+ } + return ( +
+

Welcome {session.user.name}

+
+ ) +} +``` + + +## 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"], +}; +``` \ No newline at end of file diff --git a/docs/content/docs/introduction.mdx b/docs/content/docs/introduction.mdx index 00093cc6..1b067769 100644 --- a/docs/content/docs/introduction.mdx +++ b/docs/content/docs/introduction.mdx @@ -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 diff --git a/packages/better-auth/package.json b/packages/better-auth/package.json index e8512929..ea8dd8ad 100644 --- a/packages/better-auth/package.json +++ b/packages/better-auth/package.json @@ -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", diff --git a/packages/better-auth/src/api/index.ts b/packages/better-auth/src/api/index.ts index 8c88bad1..4191844a 100644 --- a/packages/better-auth/src/api/index.ts +++ b/packages/better-auth/src/api/index.ts @@ -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 = ( 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); + } } } }, diff --git a/packages/better-auth/src/cli/commands/migrate.ts b/packages/better-auth/src/cli/commands/migrate.ts index 4b5a4253..d911c47b 100644 --- a/packages/better-auth/src/cli/commands/migrate.ts +++ b/packages/better-auth/src/cli/commands/migrate.ts @@ -20,6 +20,7 @@ export const migrate = new Command("migrate") "--config ", "the path to the configuration file. defaults to the first configuration file found.", ) + .option("--y", "") .action(async (opts) => { const options = z .object({ diff --git a/packages/better-auth/src/integrations/node.ts b/packages/better-auth/src/integrations/node.ts new file mode 100644 index 00000000..788f4198 --- /dev/null +++ b/packages/better-auth/src/integrations/node.ts @@ -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); +}; diff --git a/packages/better-auth/src/plugins/organization/organization.ts b/packages/better-auth/src/plugins/organization/organization.ts index 6f4ec0aa..fc149dce 100644 --- a/packages/better-auth/src/plugins/organization/organization.ts +++ b/packages/better-auth/src/plugins/organization/organization.ts @@ -77,7 +77,7 @@ export interface OrganizationOptions { */ ac?: AccessControl; /** - * + * Custom permissions for roles. */ roles?: { [key in "admin" | "member" | "owner"]?: Role; @@ -143,7 +143,21 @@ export interface OrganizationOptions { request?: Request, ) => Promise; } - +/** + * 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 = (options?: O) => { const endpoints = { createOrganization, diff --git a/packages/better-auth/src/plugins/rate-limiter/get-key.ts b/packages/better-auth/src/plugins/rate-limiter/get-key.ts index 7e718e93..18970a5d 100644 --- a/packages/better-auth/src/plugins/rate-limiter/get-key.ts +++ b/packages/better-auth/src/plugins/rate-limiter/get-key.ts @@ -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); diff --git a/packages/better-auth/src/plugins/rate-limiter/index.ts b/packages/better-auth/src/plugins/rate-limiter/index.ts index 802f4fc7..6aa7a328 100644 --- a/packages/better-auth/src/plugins/rate-limiter/index.ts +++ b/packages/better-auth/src/plugins/rate-limiter/index.ts @@ -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; - storage?: - | "database" - | "memory" - | { - get: (key: string) => Promise; - set: (key: string, value: RateLimit) => Promise; - }; + storage?: { + custom?: { + get: (key: string) => Promise; + set: (key: string, value: RateLimit) => Promise; + }; + /** + * 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(), + }, }); } diff --git a/packages/better-auth/src/plugins/rate-limiter/rate-limiter.test.ts b/packages/better-auth/src/plugins/rate-limiter/rate-limiter.test.ts index 25e77d4b..e83889c4 100644 --- a/packages/better-auth/src/plugins/rate-limiter/rate-limiter.test.ts +++ b/packages/better-auth/src/plugins/rate-limiter/rate-limiter.test.ts @@ -7,7 +7,9 @@ describe("rate-limiter", async () => { plugins: [ rateLimiter({ enabled: true, - storage: "memory", + storage: { + provider: "memory", + }, max: 10, window: 10, }), diff --git a/packages/better-auth/tsup.config.ts b/packages/better-auth/tsup.config.ts index 495b4666..5a51d8a2 100644 --- a/packages/better-auth/tsup.config.ts +++ b/packages/better-auth/tsup.config.ts @@ -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, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b0c50df..0a3d4eb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/todo.md b/todo.md index 19507423..c6724b9b 100644 --- a/todo.md +++ b/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 \ No newline at end of file +[ ] remove the section about using useSession in next with initialValue +[ ] rate limiting docs \ No newline at end of file