diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 55db67bd16..1b936ccb5f 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -1,3 +1,4 @@ +import { checkBotId } from "botid/server"; import { geolocation, ipAddress } from "@vercel/functions"; import { convertToModelMessages, @@ -64,7 +65,10 @@ export async function POST(request: Request) { const { id, message, messages, selectedChatModel, selectedVisibilityType } = requestBody; - const session = await auth(); + const [, session] = await Promise.all([ + checkBotId().catch(() => null), + auth(), + ]); if (!session?.user) { return new ChatbotError("unauthorized:chat").toResponse(); @@ -80,10 +84,10 @@ export async function POST(request: Request) { const messageCount = await getMessageCountByUserId({ id: session.user.id, - differenceInHours: 24, + differenceInHours: 1, }); - if (messageCount > entitlementsByUserType[userType].maxMessagesPerDay) { + if (messageCount > entitlementsByUserType[userType].maxMessagesPerHour) { return new ChatbotError("rate_limit:chat").toResponse(); } diff --git a/instrumentation-client.ts b/instrumentation-client.ts index cb0ff5c3b5..d3ed55a747 100644 --- a/instrumentation-client.ts +++ b/instrumentation-client.ts @@ -1 +1,10 @@ -export {}; +import { initBotId } from "botid/client/core"; + +initBotId({ + protect: [ + { + path: "/api/chat", + method: "POST", + }, + ], +}); diff --git a/lib/ai/entitlements.ts b/lib/ai/entitlements.ts index 6afbeebc54..fc456f288b 100644 --- a/lib/ai/entitlements.ts +++ b/lib/ai/entitlements.ts @@ -1,7 +1,7 @@ import type { UserType } from "@/app/(auth)/auth"; type Entitlements = { - maxMessagesPerDay: number; + maxMessagesPerHour: number; }; export const entitlementsByUserType: Record = { @@ -9,14 +9,14 @@ export const entitlementsByUserType: Record = { * For users without an account */ guest: { - maxMessagesPerDay: 10, + maxMessagesPerHour: 10, }, /* * For users with an account */ regular: { - maxMessagesPerDay: 10, + maxMessagesPerHour: 10, }, /* diff --git a/lib/ratelimit.ts b/lib/ratelimit.ts index fa0cc471bb..ca417e92b4 100644 --- a/lib/ratelimit.ts +++ b/lib/ratelimit.ts @@ -3,8 +3,8 @@ import { createClient } from "redis"; import { isProductionEnvironment } from "@/lib/constants"; import { ChatbotError } from "@/lib/errors"; -const MAX_MESSAGES_PER_DAY = 10; -const TTL_SECONDS = 60 * 60 * 24; +const MAX_MESSAGES = 10; +const TTL_SECONDS = 60 * 60; let client: ReturnType | null = null; @@ -37,7 +37,7 @@ export async function checkIpRateLimit(ip: string | undefined) { .expire(key, TTL_SECONDS, "NX") .exec(); - if (typeof count === "number" && count > MAX_MESSAGES_PER_DAY) { + if (typeof count === "number" && count > MAX_MESSAGES) { throw new ChatbotError("rate_limit:chat"); } } catch (error) { diff --git a/next.config.ts b/next.config.ts index 15d4f1aae3..2a42780de1 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,3 +1,4 @@ +import { withBotId } from "botid/next/config"; import type { NextConfig } from "next"; const basePath = "/demo"; @@ -24,4 +25,4 @@ const nextConfig: NextConfig = { }, }; -export default nextConfig; +export default withBotId(nextConfig); diff --git a/package.json b/package.json index 5cccc5ffbf..fb05de2c6e 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@xyflow/react": "^12.10.0", "ai": "6.0.37", "bcrypt-ts": "^5.0.2", + "botid": "^1.5.11", "class-variance-authority": "^0.7.1", "classnames": "^2.5.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52f4ccc1a5..7241157c78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,6 +101,9 @@ importers: bcrypt-ts: specifier: ^5.0.2 version: 5.0.3 + botid: + specifier: ^1.5.11 + version: 1.5.11(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react@19.0.1) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -2504,6 +2507,17 @@ packages: resolution: {integrity: sha512-2FcgD12xPbwCoe5i9/HK0jJ1xA1m+QfC1e6htG9Bl/hNOnLyaFmQSlqLKcfe3QdnoMPKpKEGFCbESBTg+SJNOw==} engines: {node: '>=18'} + botid@1.5.11: + resolution: {integrity: sha512-KOO1A3+/vFVJk5aFoG3sNwiogKFPVR+x4aw4gQ1b2e0XoE+i5xp48/EZn+WqR07jRHeDGwHWQUOtV5WVm7xiww==} + peerDependencies: + next: '*' + react: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + next: + optional: true + react: + optional: true + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -5976,6 +5990,11 @@ snapshots: bcrypt-ts@5.0.3: {} + botid@1.5.11(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react@19.0.1): + optionalDependencies: + next: 16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1) + react: 19.0.1 + buffer-from@1.1.2: {} bufferutil@4.0.9: