diff --git a/package-lock.json b/package-lock.json index 962a6f2..9dbd6f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "dotenv": "^16.4.7", "duck-duck-scrape": "^2.2.7", "express": "^4.21.2", + "express-validator": "^7.2.1", "hjson": "^3.2.2", "jsdom": "^26.0.0", "node-fetch": "^3.3.2", @@ -4286,6 +4287,18 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-validator": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz", + "integrity": "sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6307,6 +6320,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -8470,6 +8488,14 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 83e8341..d5d8c83 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "dotenv": "^16.4.7", "duck-duck-scrape": "^2.2.7", "express": "^4.21.2", + "express-validator": "^7.2.1", "hjson": "^3.2.2", "jsdom": "^26.0.0", "node-fetch": "^3.3.2", @@ -66,4 +67,4 @@ "optionalDependencies": { "@ai-sdk/google-vertex": "^2.1.12" } -} \ No newline at end of file +} diff --git a/src/app.ts b/src/app.ts index a388ff4..3516f1f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -15,6 +15,7 @@ import { ObjectGeneratorSafe } from "./utils/safe-generator"; import { jsonSchema } from "ai"; // or another converter library import { normalizeHostName } from "./utils/url-tools"; import { logInfo, logError, logDebug, logWarning } from './logging'; +import { body, validationResult } from 'express-validator'; const app = express(); @@ -375,7 +376,21 @@ async function processQueue(streamingState: StreamingState, res: Response, reque streamingState.processingQueue = false; } -app.post('/v1/chat/completions', (async (req: Request, res: Response) => { +const validationRules = [ + // Rule: messages is required and must be a string + body('messages') + .isArray({ min: 1 }) + .withMessage('The "messages" parameter is required.'), +]; + +app.post('/v1/chat/completions', validationRules, (async (req: Request, res: Response) => { + // Validate request body + const errors = validationResult(req); + if (!errors.isEmpty()) { + logError('[chat/completions] Validation errors:', { errors: errors.array() }); + return res.status(400).json({ error: 'Invalid request body', details: errors.array() }); + } + // Check authentication only if secret is set if (secret) { const authHeader = req.headers.authorization;