diff --git a/api/server/middleware/validate/convoAccess.js b/api/server/middleware/validate/convoAccess.js index ffee70ae61..127bfdc530 100644 --- a/api/server/middleware/validate/convoAccess.js +++ b/api/server/middleware/validate/convoAccess.js @@ -6,6 +6,15 @@ const { logViolation, getLogStores } = require('~/cache'); 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. * @@ -24,7 +33,7 @@ const validateConvoAccess = async (req, res, next) => { const namespace = ViolationTypes.CONVO_ACCESS; const cache = getLogStores(namespace); - const conversationId = req.body.conversationId; + const conversationId = getConversationId(req.body); if (!conversationId || conversationId === Constants.NEW_CONVO) { return next(); diff --git a/api/server/routes/__tests__/convos.spec.js b/api/server/routes/__tests__/convos.spec.js index e1f9469bef..ce43155cb0 100644 --- a/api/server/routes/__tests__/convos.spec.js +++ b/api/server/routes/__tests__/convos.spec.js @@ -59,6 +59,7 @@ jest.mock('~/server/middleware', () => ({ forkUserLimiter: (req, res, next) => next(), })), configMiddleware: (req, res, next) => next(), + validateConvoAccess: (req, res, next) => next(), })); jest.mock('~/server/utils/import/fork', () => ({ diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 90ef13b52d..e862f99ab0 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -6,6 +6,7 @@ const { logger } = require('@librechat/data-schemas'); const { CacheKeys, EModelEndpoint } = require('librechat-data-provider'); const { createImportLimiters, + validateConvoAccess, createForkLimiters, configMiddleware, } = require('~/server/middleware'); @@ -151,17 +152,39 @@ router.delete('/all', async (req, res) => { } }); -router.post('/update', async (req, res) => { - const update = req.body.arg; +/** Maximum allowed length for conversation titles */ +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' }); } + 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 { - const dbResponse = await saveConvo(req, update, { - context: `POST /api/convos/update ${update.conversationId}`, - }); + const dbResponse = await saveConvo( + req, + { conversationId, title: sanitizedTitle }, + { context: `POST /api/convos/update ${conversationId}` }, + ); res.status(201).json(dbResponse); } catch (error) { logger.error('Error updating conversation', error);