✍️ fix: Validation for Conversation Title Updates (#11099)

* ✍️ fix: Validation for Conversation Title Updates

* fix: Add validateConvoAccess middleware mock in tests
This commit is contained in:
Danny Avila 2025-12-25 12:59:48 -05:00 committed by GitHub
parent b7ea340769
commit bfc981d736
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 40 additions and 7 deletions

View file

@ -6,6 +6,15 @@ const { logViolation, getLogStores } = require('~/cache');
const { USE_REDIS, CONVO_ACCESS_VIOLATION_SCORE: score = 0 } = process.env ?? {}; const { USE_REDIS, CONVO_ACCESS_VIOLATION_SCORE: score = 0 } = process.env ?? {};
/**
* Helper function to get conversationId from different request body structures.
* @param {Object} body - The request body.
* @returns {string|undefined} The conversationId.
*/
const getConversationId = (body) => {
return body.conversationId ?? body.arg?.conversationId;
};
/** /**
* Middleware to validate user's authorization for a conversation. * Middleware to validate user's authorization for a conversation.
* *
@ -24,7 +33,7 @@ const validateConvoAccess = async (req, res, next) => {
const namespace = ViolationTypes.CONVO_ACCESS; const namespace = ViolationTypes.CONVO_ACCESS;
const cache = getLogStores(namespace); const cache = getLogStores(namespace);
const conversationId = req.body.conversationId; const conversationId = getConversationId(req.body);
if (!conversationId || conversationId === Constants.NEW_CONVO) { if (!conversationId || conversationId === Constants.NEW_CONVO) {
return next(); return next();

View file

@ -59,6 +59,7 @@ jest.mock('~/server/middleware', () => ({
forkUserLimiter: (req, res, next) => next(), forkUserLimiter: (req, res, next) => next(),
})), })),
configMiddleware: (req, res, next) => next(), configMiddleware: (req, res, next) => next(),
validateConvoAccess: (req, res, next) => next(),
})); }));
jest.mock('~/server/utils/import/fork', () => ({ jest.mock('~/server/utils/import/fork', () => ({

View file

@ -6,6 +6,7 @@ const { logger } = require('@librechat/data-schemas');
const { CacheKeys, EModelEndpoint } = require('librechat-data-provider'); const { CacheKeys, EModelEndpoint } = require('librechat-data-provider');
const { const {
createImportLimiters, createImportLimiters,
validateConvoAccess,
createForkLimiters, createForkLimiters,
configMiddleware, configMiddleware,
} = require('~/server/middleware'); } = require('~/server/middleware');
@ -151,17 +152,39 @@ router.delete('/all', async (req, res) => {
} }
}); });
router.post('/update', async (req, res) => { /** Maximum allowed length for conversation titles */
const update = req.body.arg; const MAX_CONVO_TITLE_LENGTH = 1024;
if (!update.conversationId) { /**
* Updates a conversation's title.
* @route POST /update
* @param {string} req.body.arg.conversationId - The conversation ID to update.
* @param {string} req.body.arg.title - The new title for the conversation.
* @returns {object} 201 - The updated conversation object.
*/
router.post('/update', validateConvoAccess, async (req, res) => {
const { conversationId, title } = req.body.arg ?? {};
if (!conversationId) {
return res.status(400).json({ error: 'conversationId is required' }); return res.status(400).json({ error: 'conversationId is required' });
} }
if (title === undefined) {
return res.status(400).json({ error: 'title is required' });
}
if (typeof title !== 'string') {
return res.status(400).json({ error: 'title must be a string' });
}
const sanitizedTitle = title.trim().slice(0, MAX_CONVO_TITLE_LENGTH);
try { try {
const dbResponse = await saveConvo(req, update, { const dbResponse = await saveConvo(
context: `POST /api/convos/update ${update.conversationId}`, req,
}); { conversationId, title: sanitizedTitle },
{ context: `POST /api/convos/update ${conversationId}` },
);
res.status(201).json(dbResponse); res.status(201).json(dbResponse);
} catch (error) { } catch (error) {
logger.error('Error updating conversation', error); logger.error('Error updating conversation', error);