Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ You can configure the authentication settings in the `VitNodeAPI` function and `

For example you can change the expiration time of the cookie:

import { TypeTable } from 'fumadocs-ui/components/type-table';

```ts title="src/app/api/[...route]/route.ts"
```ts title="src/vitnode.api.config.ts"
VitNodeAPI({
app,
plugins: [],
Expand All @@ -28,6 +26,8 @@ VitNodeAPI({
});
```

import { TypeTable } from 'fumadocs-ui/components/type-table';

### Options

<TypeTable
Expand Down
4 changes: 4 additions & 0 deletions apps/docs/content/docs/dev/config/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Config",
"pages": ["..."]
}
57 changes: 57 additions & 0 deletions apps/docs/content/docs/dev/config/rate-limiter.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: Rate Limiter
description: Stop brute-force attacks and abuse with rate limiting.
---

Rate limiting is a powerful feature that helps protect your API from abuse and brute-force attacks. It allows you to limit the number of requests a client can make within a specified time frame.

## Usage

```ts title="src/vitnode.api.config.ts"
export const vitNodeApiConfig = buildApiConfig({
// [!code ++:4]
rateLimiter: {
points: 20,
duration: 60,
},
});
```

### Options

import { TypeTable } from 'fumadocs-ui/components/type-table';

<TypeTable
type={{
points: {
description:
'The number of requests allowed within the specified duration.',
type: 'number',
default: '20',
},
duration: {
description:
'The time window in seconds during which the requests are counted.',
type: 'number',
default: '60',
},
execEvenly: {
description:
'If true, the requests are distributed evenly over the duration. If false, the requests are counted in a sliding window.',
type: 'boolean',
default: 'false',
},
execEvenlyMinDelayMs: {
description:
'The minimum delay in milliseconds between requests when execEvenly is true.',
type: 'number',
default: 'duration * 1000 / points',
},
blockDuration: {
description:
'The duration in seconds for which the client is blocked after exceeding the rate limit.',
type: 'number',
default: '0',
},
}}
/>
4 changes: 4 additions & 0 deletions apps/docs/content/docs/dev/deployments/cloud/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Cloud",
"pages": ["..."]
}
5 changes: 5 additions & 0 deletions apps/docs/content/docs/dev/deployments/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"title": "Deployments",
"icon": "HardDriveUpload",
"pages": ["..."]
}
4 changes: 4 additions & 0 deletions apps/docs/content/docs/dev/deployments/self-hosted/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Self-Hosted",
"pages": ["..."]
}
6 changes: 3 additions & 3 deletions apps/docs/content/docs/dev/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
"pages": [
"index",
"contribution",
"structure",
"debugging",
"swagger",
"---Framework---",
"auth",
"deployments",
"---Plugins---",
"plugins",
"rest-api",
Expand All @@ -20,6 +18,8 @@
"layouts-and-pages",
"admin-page",
"i18n",
"---Framework---",
"config",
"---Advanced---",
"..."
]
Expand Down
35 changes: 18 additions & 17 deletions apps/docs/src/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--border: oklch(0.9 0 0);
--input: oklch(0.9 0 0);
--ring: oklch(0.7 0.16 262.61);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
Expand All @@ -45,35 +45,36 @@
}

.dark {
--background: oklch(0.12 0 0);
--foreground: oklch(0.98 0.005 240);
--card: oklch(0.18 0.005 240);
--card-foreground: oklch(0.98 0.005 240);
--popover: oklch(0.18 0.005 240);
--popover-foreground: oklch(0.98 0.005 240);
--background: oklch(0.14 0 0);
--foreground: oklch(0.98 0 0);
--card: oklch(0.18 0 0);
--card-foreground: oklch(0.98 0 0);
--popover: oklch(0.18 0 0);
--popover-foreground: oklch(0.98 0 0);
--primary: oklch(0.51 0.16 262.61);
--primary-foreground: oklch(0.99 0.005 240);
--primary-foreground: oklch(0.99 0.16 262.61);
--secondary: oklch(0.24 0.01 240);
--secondary-foreground: oklch(0.98 0.005 240);
--muted: oklch(0.22 0.01 240);
--muted: oklch(0.22 0 0);
--muted-foreground: oklch(0.7 0 0);
--accent: oklch(0.28 0.015 240);
--accent: oklch(0.28 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(0.269 0.005 240);
--input: oklch(0.269 0.005 240);
--destructive-foreground: oklch(0.704 0.191 22.216);
--border: oklch(0.24 0 0);
--input: oklch(0.24 0 0);
--ring: oklch(0.51 0.16 262.61);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: var(--card);
--sidebar-foreground: oklch(0.98 0.005 240);
--sidebar-foreground: oklch(0.98 0 0);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: oklch(0.22 0.005 240);
--sidebar-accent-foreground: oklch(0.98 0.005 240);
--sidebar-border: oklch(0.269 0.005 240);
--sidebar-accent: oklch(0.22 0 0);
--sidebar-accent-foreground: oklch(0.98 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.51 0.16 262.61);

--dev-color: oklch(0.75 0.18 50);
Expand Down
35 changes: 18 additions & 17 deletions apps/web/src/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--border: oklch(0.9 0 0);
--input: oklch(0.9 0 0);
--ring: oklch(0.7 0.16 262.61);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
Expand All @@ -43,35 +43,36 @@
}

.dark {
--background: oklch(0.12 0 0);
--foreground: oklch(0.98 0.005 240);
--card: oklch(0.18 0.005 240);
--card-foreground: oklch(0.98 0.005 240);
--popover: oklch(0.18 0.005 240);
--popover-foreground: oklch(0.98 0.005 240);
--background: oklch(0.14 0 0);
--foreground: oklch(0.98 0 0);
--card: oklch(0.18 0 0);
--card-foreground: oklch(0.98 0 0);
--popover: oklch(0.18 0 0);
--popover-foreground: oklch(0.98 0 0);
--primary: oklch(0.51 0.16 262.61);
--primary-foreground: oklch(0.99 0.005 240);
--primary-foreground: oklch(0.99 0.16 262.61);
--secondary: oklch(0.24 0.01 240);
--secondary-foreground: oklch(0.98 0.005 240);
--muted: oklch(0.22 0.01 240);
--muted: oklch(0.22 0 0);
--muted-foreground: oklch(0.7 0 0);
--accent: oklch(0.28 0.015 240);
--accent: oklch(0.28 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(0.269 0.005 240);
--input: oklch(0.269 0.005 240);
--destructive-foreground: oklch(0.704 0.191 22.216);
--border: oklch(0.24 0 0);
--input: oklch(0.24 0 0);
--ring: oklch(0.51 0.16 262.61);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: var(--card);
--sidebar-foreground: oklch(0.98 0.005 240);
--sidebar-foreground: oklch(0.98 0 0);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: oklch(0.22 0.005 240);
--sidebar-accent-foreground: oklch(0.98 0.005 240);
--sidebar-border: oklch(0.269 0.005 240);
--sidebar-accent: oklch(0.22 0 0);
--sidebar-accent-foreground: oklch(0.98 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.51 0.16 262.61);
}

Expand Down
24 changes: 14 additions & 10 deletions apps/web/src/vitnode.api.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { blogApiPlugin } from '@vitnode/blog/config.api';
import { NodemailerEmailPlugin } from '@vitnode/core/api/plugins/email/nodemailer';
import { DiscordSSOApiPlugin } from '@vitnode/core/api/plugins/sso/discord';
import { FacebookSSOApiPlugin } from '@vitnode/core/api/plugins/sso/facebook';
import { GoogleSSOApiPlugin } from '@vitnode/core/api/plugins/sso/google';
import { NodemailerEmailAdapter } from '@vitnode/core/api/adapters/email/nodemailer';
import { DiscordSSOApiPlugin } from '@vitnode/core/api/adapters/sso/discord';
import { FacebookSSOApiPlugin } from '@vitnode/core/api/adapters/sso/facebook';
import { GoogleSSOApiPlugin } from '@vitnode/core/api/adapters/sso/google';
import { buildApiConfig } from '@vitnode/core/vitnode.config';
import * as dotenv from 'dotenv';
import { drizzle } from 'drizzle-orm/postgres-js';
Expand All @@ -22,12 +22,16 @@ export const vitNodeApiConfig = buildApiConfig({
connection: POSTGRES_URL,
casing: 'camelCase',
}),
// emailProvider: NodemailerEmailPlugin({
// from: process.env.NODE_MAILER_FROM,
// host: process.env.NODE_MAILER_HOST,
// password: process.env.NODE_MAILER_PASSWORD,
// user: process.env.NOD_EMAILER_USER,
// }),
rateLimiter: {
points: 20, // 20 requests
duration: 60, // per 60 seconds
},
emailAdapter: NodemailerEmailAdapter({
from: process.env.NODE_MAILER_FROM,
host: process.env.NODE_MAILER_HOST,
password: process.env.NODE_MAILER_PASSWORD,
user: process.env.NOD_EMAILER_USER,
}),
authorization: {
ssoProviders: [
DiscordSSOApiPlugin({
Expand Down
17 changes: 9 additions & 8 deletions packages/vitnode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@
"core"
],
"peerDependencies": {
"@hono/zod-openapi": "0.19.x",
"@swc/cli": "0.6.x",
"@swc/core": "1.12.x",
"@types/react": "19.1.x",
"@types/react-dom": "19.1.x",
"drizzle-kit": "0.31.x",
"drizzle-orm": "^0.44.x",
"hono": "4.8.x",
"next": "15.3.x",
"next-intl": "4.1.x",
"react": "19.1.x",
"react-dom": "19.1.x",
"@swc/cli": "0.6.x",
"@hono/zod-openapi": "0.19.x",
"next-intl": "4.1.x",
"react-hook-form": "^7.0.0",
"zod": "3.25.x",
"@swc/core": "1.12.x",
"@types/react": "19.1.x",
"@types/react-dom": "19.1.x",
"typescript": "^5.8.x"
"typescript": "^5.8.x",
"zod": "3.25.x"
},
"devDependencies": {
"@hono/zod-openapi": "^0.19.8",
Expand Down Expand Up @@ -119,6 +119,7 @@
"nodemailer": "^7.0.3",
"postgres": "^3.4.7",
"radix-ui": "^1.4.2",
"rate-limiter-flexible": "^7.1.1",
"react-scan": "^0.3.4",
"resend": "^4.6.0",
"tailwind-merge": "^3.3.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createTransport } from 'nodemailer';

import type { EmailApiPlugin } from '@/api/models/email';

export const NodemailerEmailPlugin = ({
export const NodemailerEmailAdapter = ({
host = '',
port = 587,
secure = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Resend } from 'resend';

import type { EmailApiPlugin } from '@/api/models/email';

export const ResendEmailPlugin = ({
export const ResendEmailAdapter = ({
apiKey,
from,
}: {
Expand Down
6 changes: 4 additions & 2 deletions packages/vitnode/src/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { CONFIG_PLUGIN } from '@/config';
import {
globalAdminMiddleware,
globalMiddleware,
} from './middlewares/global/global';
} from './middlewares/global.middleware';
import { rateLimiterMiddleware } from './middlewares/rate-limiter.middleware';

interface CORSOptions {
allowHeaders?: string[];
Expand Down Expand Up @@ -55,11 +56,12 @@ export function VitNodeAPI({
});
app.use(cors(corsOptions));
app.use(csrf(csrfOptions));
app.use('*', rateLimiterMiddleware(vitNodeApiConfig.rateLimiter));
app.get('/swagger', swaggerUI({ url: `/api/swagger/doc` }));
app.use(
'*',
globalMiddleware({
emailProvider: vitNodeApiConfig.emailProvider,
emailAdapter: vitNodeApiConfig.emailAdapter,
metadata: vitNodeConfig.metadata,
authorization: vitNodeApiConfig.authorization,
dbProvider: vitNodeApiConfig.dbProvider,
Expand Down
2 changes: 1 addition & 1 deletion packages/vitnode/src/api/lib/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createRoute as createRouteHono } from '@hono/zod-openapi';
import {
type EnvVitNode,
pluginMiddleware,
} from '../middlewares/global/global';
} from '../middlewares/global.middleware';

type RoutingPath<P extends string> =
P extends `${infer Head}/{${infer Param}}${infer Tail}`
Expand Down
Loading