diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 80bd35e08a..1f0b5b212c 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -7,6 +7,7 @@ import { stepCountIs, streamText, } from "ai"; +import { checkBotId } from "botid/server"; import { after } from "next/server"; import { createResumableStreamContext } from "resumable-stream"; import { auth, type UserType } from "@/app/(auth)/auth"; @@ -63,7 +64,14 @@ export async function POST(request: Request) { const { id, message, messages, selectedChatModel, selectedVisibilityType } = requestBody; - const session = await auth(); + const [botResult, session] = await Promise.all([ + checkBotId().catch(() => null), + auth(), + ]); + + if (botResult?.isBot) { + return new ChatbotError("unauthorized:chat").toResponse(); + } if (!session?.user) { return new ChatbotError("unauthorized:chat").toResponse(); diff --git a/instrumentation-client.ts b/instrumentation-client.ts index cb0ff5c3b5..2f899da383 100644 --- a/instrumentation-client.ts +++ b/instrumentation-client.ts @@ -1 +1,10 @@ -export {}; +import { initBotId } from "botid/client/core"; + +initBotId({ + protect: [ + { + path: `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/api/chat`, + method: "POST", + }, + ], +}); diff --git a/package.json b/package.json index 5cccc5ffbf..e1a83f25e8 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.10", "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..4ee98eafaf 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.10 + version: 1.5.10(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.10: + resolution: {integrity: sha512-hhgty1u0CxozqTqLbTQMtYBwmWdzWZTAsBCvN7/qhkN3fM7MlXacmmcMoyc0f+vV+U6RRoLYdlo32td+PhJyew==} + 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.10(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: diff --git a/proxy.ts b/proxy.ts index 397b553e1b..14fb3ff057 100644 --- a/proxy.ts +++ b/proxy.ts @@ -2,13 +2,28 @@ import { type NextRequest, NextResponse } from "next/server"; import { getToken } from "next-auth/jwt"; import { guestRegex, isDevelopmentEnvironment } from "./lib/constants"; +const BOTID_PREFIX = + "/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3"; + export async function proxy(request: NextRequest) { const { pathname } = request.nextUrl; - /* - * Playwright starts the dev server and requires a 200 status to - * begin the tests, so this ensures that the tests can start - */ + if (pathname.startsWith(BOTID_PREFIX)) { + const suffix = pathname.slice(BOTID_PREFIX.length); + if (suffix === "/a-4-a/c.js") { + return NextResponse.rewrite( + new URL( + `https://api.vercel.com/bot-protection/v1/challenge${request.nextUrl.search}`, + ), + ); + } + return NextResponse.rewrite( + new URL( + `https://api.vercel.com/bot-protection/v1/proxy${suffix}${request.nextUrl.search}`, + ), + ); + } + if (pathname.startsWith("/ping")) { return new Response("pong", { status: 200 }); }