Merge pull request #880 from ruturaj-rathod/fix/body-validation-579

validate the request body of api/chat to prevent malformed request body
This commit is contained in:
Kushagra Srivastava
2025-09-27 19:52:14 +05:30
committed by GitHub

View File

@@ -17,35 +17,71 @@ import {
getCustomOpenaiModelName, getCustomOpenaiModelName,
} from '@/lib/config'; } from '@/lib/config';
import { searchHandlers } from '@/lib/search'; import { searchHandlers } from '@/lib/search';
import { z } from 'zod';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
export const dynamic = 'force-dynamic'; export const dynamic = 'force-dynamic';
type Message = { const messageSchema = z.object({
messageId: string; messageId: z.string().min(1, 'Message ID is required'),
chatId: string; chatId: z.string().min(1, 'Chat ID is required'),
content: string; content: z.string().min(1, 'Message content is required'),
}; });
type ChatModel = { const chatModelSchema = z.object({
provider: string; provider: z.string().optional(),
name: string; name: z.string().optional(),
}; });
type EmbeddingModel = { const embeddingModelSchema = z.object({
provider: string; provider: z.string().optional(),
name: string; name: z.string().optional(),
}; });
type Body = { const bodySchema = z.object({
message: Message; message: messageSchema,
optimizationMode: 'speed' | 'balanced' | 'quality'; optimizationMode: z.enum(['speed', 'balanced', 'quality'], {
focusMode: string; errorMap: () => ({
history: Array<[string, string]>; message: 'Optimization mode must be one of: speed, balanced, quality',
files: Array<string>; }),
chatModel: ChatModel; }),
embeddingModel: EmbeddingModel; focusMode: z.string().min(1, 'Focus mode is required'),
systemInstructions: string; history: z
.array(
z.tuple([z.string(), z.string()], {
errorMap: () => ({
message: 'History items must be tuples of two strings',
}),
}),
)
.optional()
.default([]),
files: z.array(z.string()).optional().default([]),
chatModel: chatModelSchema.optional().default({}),
embeddingModel: embeddingModelSchema.optional().default({}),
systemInstructions: z.string().nullable().optional().default(''),
});
type Message = z.infer<typeof messageSchema>;
type Body = z.infer<typeof bodySchema>;
const safeValidateBody = (data: unknown) => {
const result = bodySchema.safeParse(data);
if (!result.success) {
return {
success: false,
error: result.error.errors.map((e) => ({
path: e.path.join('.'),
message: e.message,
})),
};
}
return {
success: true,
data: result.data,
};
}; };
const handleEmitterEvents = async ( const handleEmitterEvents = async (
@@ -190,7 +226,17 @@ const handleHistorySave = async (
export const POST = async (req: Request) => { export const POST = async (req: Request) => {
try { try {
const body = (await req.json()) as Body; const reqBody = (await req.json()) as Body;
const parseBody = safeValidateBody(reqBody);
if (!parseBody.success) {
return Response.json(
{ message: 'Invalid request body', error: parseBody.error },
{ status: 400 },
);
}
const body = parseBody.data as Body;
const { message } = body; const { message } = body;
if (message.content === '') { if (message.content === '') {
@@ -285,7 +331,7 @@ export const POST = async (req: Request) => {
embedding, embedding,
body.optimizationMode, body.optimizationMode,
body.files, body.files,
body.systemInstructions, body.systemInstructions as string,
); );
const responseStream = new TransformStream(); const responseStream = new TransformStream();