From adb17c83f86478a67295fc7924c0fc748868a72e Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:04:37 +0200 Subject: [PATCH] fix: load app config on remaining retention-relevant routes The messages routes (branch, artifact, post), share-link create/patch, and the agent chat-abort route read req.config.interfaceConfig but never ran configMiddleware, so req.config was undefined and retention was skipped. Under ephemeral (and all) this let branched/edited/aborted messages and shared links persist without isTemporary/expiredAt, outliving the chat. Apply configMiddleware to those routes so retention is enforced consistently. Add a getSharedLinkExpiration ephemeral test and keep route test middleware mocks in sync. --- .../routes/__tests__/messages-delete.spec.js | 1 + api/server/routes/agents/index.js | 2 +- api/server/routes/messages.js | 8 ++++---- packages/api/src/files/retention.spec.ts | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/api/server/routes/__tests__/messages-delete.spec.js b/api/server/routes/__tests__/messages-delete.spec.js index 36c4e8e9e6..c03fe8d955 100644 --- a/api/server/routes/__tests__/messages-delete.spec.js +++ b/api/server/routes/__tests__/messages-delete.spec.js @@ -49,6 +49,7 @@ jest.mock('~/server/middleware/requireJwtAuth', () => (req, res, next) => next() jest.mock('~/server/middleware', () => ({ requireJwtAuth: (req, res, next) => next(), validateMessageReq: (req, res, next) => next(), + configMiddleware: (req, res, next) => next(), })); jest.mock('~/db/models', () => ({ diff --git a/api/server/routes/agents/index.js b/api/server/routes/agents/index.js index 145a6c0316..16af74f246 100644 --- a/api/server/routes/agents/index.js +++ b/api/server/routes/agents/index.js @@ -220,7 +220,7 @@ router.get('/chat/status/:conversationId', async (req, res) => { * @access Private * @description Mounted before chatRouter to bypass buildEndpointOption middleware */ -router.post('/chat/abort', async (req, res) => { +router.post('/chat/abort', configMiddleware, async (req, res) => { logger.debug(`[AgentStream] ========== ABORT ENDPOINT HIT ==========`); logger.debug(`[AgentStream] Method: ${req.method}, Path: ${req.path}`); logger.debug(`[AgentStream] Body:`, req.body); diff --git a/api/server/routes/messages.js b/api/server/routes/messages.js index 17e740c515..618043a295 100644 --- a/api/server/routes/messages.js +++ b/api/server/routes/messages.js @@ -9,7 +9,7 @@ const { traceIdForMessage, } = require('@librechat/api'); const { findAllArtifacts, replaceArtifactContent } = require('~/server/services/Artifacts/update'); -const { requireJwtAuth, validateMessageReq } = require('~/server/middleware'); +const { requireJwtAuth, validateMessageReq, configMiddleware } = require('~/server/middleware'); const db = require('~/models'); const router = express.Router(); @@ -108,7 +108,7 @@ router.get('/', async (req, res) => { * @param {string} req.body.agentId - The agentId to filter content by * @returns {TMessage} The newly created branch message */ -router.post('/branch', async (req, res) => { +router.post('/branch', configMiddleware, async (req, res) => { try { const { messageId, agentId } = req.body; const userId = req.user.id; @@ -189,7 +189,7 @@ router.post('/branch', async (req, res) => { } }); -router.post('/artifact/:messageId', async (req, res) => { +router.post('/artifact/:messageId', configMiddleware, async (req, res) => { try { const { messageId } = req.params; const { index, original, updated } = req.body; @@ -282,7 +282,7 @@ router.get('/:conversationId', validateMessageReq, async (req, res) => { } }); -router.post('/:conversationId', validateMessageReq, async (req, res) => { +router.post('/:conversationId', validateMessageReq, configMiddleware, async (req, res) => { try { const message = { ...req.body, conversationId: req.params.conversationId }; const reqCtx = { diff --git a/packages/api/src/files/retention.spec.ts b/packages/api/src/files/retention.spec.ts index bfd327508b..1fb8bc213e 100644 --- a/packages/api/src/files/retention.spec.ts +++ b/packages/api/src/files/retention.spec.ts @@ -394,6 +394,22 @@ describe('retention helpers', () => { ).resolves.toBe(expirationDate); }); + it('returns a fresh expiry for retentionMode EPHEMERAL conversations without an expiration', async () => { + dependencies.getConvo.mockResolvedValue({ expiredAt: null }); + + await expect( + getSharedLinkExpiration( + { + req: request({ + config: { interfaceConfig: { retentionMode: RetentionMode.EPHEMERAL } }, + }), + conversationId: 'convo-1', + }, + dependencies, + ), + ).resolves.toBe(expirationDate); + }); + it('returns an expired source conversation date so callers can reject the share', async () => { const expiredAt = new Date(Date.now() - 60 * 60 * 1000); dependencies.getConvo.mockResolvedValue({ expiredAt });