const mongoose = require('mongoose'); const { ResourceType, SystemRoles, PrincipalType, PrincipalModel, } = require('librechat-data-provider'); const { MongoMemoryServer } = require('mongodb-memory-server'); const { canAccessAgentFromBody } = require('./canAccessAgentFromBody'); const { User, Role, AclEntry } = require('~/db/models'); const { createAgent } = require('~/models/Agent'); describe('canAccessAgentFromBody middleware', () => { let mongoServer; let req, res, next; let testUser, otherUser; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); await mongoose.connect(mongoServer.getUri()); }); afterAll(async () => { await mongoose.disconnect(); await mongoServer.stop(); }); beforeEach(async () => { await mongoose.connection.dropDatabase(); await Role.create({ name: 'test-role', permissions: { AGENTS: { USE: true, CREATE: true, SHARE: true }, MULTI_CONVO: { USE: true }, }, }); await Role.create({ name: 'no-multi-convo', permissions: { AGENTS: { USE: true, CREATE: true, SHARE: true }, MULTI_CONVO: { USE: false }, }, }); await Role.create({ name: SystemRoles.ADMIN, permissions: { AGENTS: { USE: true, CREATE: true, SHARE: true }, MULTI_CONVO: { USE: true }, }, }); testUser = await User.create({ email: 'test@example.com', name: 'Test User', username: 'testuser', role: 'test-role', }); otherUser = await User.create({ email: 'other@example.com', name: 'Other User', username: 'otheruser', role: 'test-role', }); req = { user: { id: testUser._id, role: testUser.role }, params: {}, body: { endpoint: 'agents', agent_id: 'ephemeral_primary', }, }; res = { status: jest.fn().mockReturnThis(), json: jest.fn(), }; next = jest.fn(); jest.clearAllMocks(); }); describe('middleware factory', () => { test('throws if requiredPermission is missing', () => { expect(() => canAccessAgentFromBody({})).toThrow( 'canAccessAgentFromBody: requiredPermission is required and must be a number', ); }); test('throws if requiredPermission is not a number', () => { expect(() => canAccessAgentFromBody({ requiredPermission: '1' })).toThrow( 'canAccessAgentFromBody: requiredPermission is required and must be a number', ); }); test('returns a middleware function', () => { const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); expect(typeof middleware).toBe('function'); expect(middleware.length).toBe(3); }); }); describe('primary agent checks', () => { test('returns 400 when agent_id is missing on agents endpoint', async () => { req.body.agent_id = undefined; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(400); }); test('proceeds for ephemeral primary agent without addedConvo', async () => { const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); }); test('proceeds for non-agents endpoint (ephemeral fallback)', async () => { req.body.endpoint = 'openAI'; req.body.agent_id = undefined; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); }); describe('addedConvo — absent or invalid shape', () => { test('calls next when addedConvo is absent', async () => { const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); test('calls next when addedConvo is a string', async () => { req.body.addedConvo = 'not-an-object'; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); test('calls next when addedConvo is an array', async () => { req.body.addedConvo = [{ agent_id: 'agent_something' }]; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); }); describe('addedConvo — MULTI_CONVO permission gate', () => { test('returns 403 when user lacks MULTI_CONVO:USE', async () => { req.user.role = 'no-multi-convo'; req.body.addedConvo = { agent_id: 'agent_x', endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(403); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ message: 'Multi-conversation feature is not enabled' }), ); }); test('returns 403 when user.role is missing', async () => { req.user = { id: testUser._id }; req.body.addedConvo = { agent_id: 'agent_x', endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(403); }); test('ADMIN bypasses MULTI_CONVO check', async () => { req.user.role = SystemRoles.ADMIN; req.body.addedConvo = { agent_id: 'ephemeral_x', endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); }); }); describe('addedConvo — agent_id shape validation', () => { test('calls next when agent_id is ephemeral', async () => { req.body.addedConvo = { agent_id: 'ephemeral_xyz', endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); test('calls next when agent_id is absent', async () => { req.body.addedConvo = { endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); test('calls next when agent_id is not a string (object injection)', async () => { req.body.addedConvo = { agent_id: { $gt: '' }, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); }); describe('addedConvo — agent resource ACL (IDOR prevention)', () => { let addedAgent; beforeEach(async () => { addedAgent = await createAgent({ id: `agent_added_${Date.now()}`, name: 'Private Agent', provider: 'openai', model: 'gpt-4', author: otherUser._id, }); await AclEntry.create({ principalType: PrincipalType.USER, principalId: otherUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.AGENT, resourceId: addedAgent._id, permBits: 15, grantedBy: otherUser._id, }); }); test('returns 403 when requester has no ACL for the added agent', async () => { req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(403); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ message: 'Insufficient permissions to access this agent', }), ); }); test('returns 404 when added agent does not exist', async () => { req.body.addedConvo = { agent_id: 'agent_nonexistent_999', endpoint: 'agents', model: 'gpt-4', }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(404); }); test('proceeds when requester has ACL for the added agent', async () => { await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.AGENT, resourceId: addedAgent._id, permBits: 1, grantedBy: otherUser._id, }); req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); }); test('denies when ACL permission bits are insufficient', async () => { await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.AGENT, resourceId: addedAgent._id, permBits: 1, grantedBy: otherUser._id, }); req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 2 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(403); }); test('caches resolved agent on req.resolvedAddedAgent', async () => { await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.AGENT, resourceId: addedAgent._id, permBits: 1, grantedBy: otherUser._id, }); req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(req.resolvedAddedAgent).toBeDefined(); expect(req.resolvedAddedAgent._id.toString()).toBe(addedAgent._id.toString()); }); test('ADMIN bypasses agent resource ACL for addedConvo', async () => { req.user.role = SystemRoles.ADMIN; req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); expect(req.resolvedAddedAgent).toBeUndefined(); }); }); describe('end-to-end: primary real agent + addedConvo real agent', () => { let primaryAgent, addedAgent; beforeEach(async () => { primaryAgent = await createAgent({ id: `agent_primary_${Date.now()}`, name: 'Primary Agent', provider: 'openai', model: 'gpt-4', author: testUser._id, }); await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.AGENT, resourceId: primaryAgent._id, permBits: 15, grantedBy: testUser._id, }); addedAgent = await createAgent({ id: `agent_added_${Date.now()}`, name: 'Added Agent', provider: 'openai', model: 'gpt-4', author: otherUser._id, }); await AclEntry.create({ principalType: PrincipalType.USER, principalId: otherUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.AGENT, resourceId: addedAgent._id, permBits: 15, grantedBy: otherUser._id, }); req.body.agent_id = primaryAgent.id; }); test('both checks pass when user has ACL for both agents', async () => { await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.AGENT, resourceId: addedAgent._id, permBits: 1, grantedBy: otherUser._id, }); req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); expect(req.resolvedAddedAgent).toBeDefined(); }); test('primary passes but addedConvo denied → 403', async () => { req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(403); }); test('primary denied → 403 without reaching addedConvo check', async () => { const foreignAgent = await createAgent({ id: `agent_foreign_${Date.now()}`, name: 'Foreign Agent', provider: 'openai', model: 'gpt-4', author: otherUser._id, }); await AclEntry.create({ principalType: PrincipalType.USER, principalId: otherUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.AGENT, resourceId: foreignAgent._id, permBits: 15, grantedBy: otherUser._id, }); req.body.agent_id = foreignAgent.id; req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(403); }); }); describe('ephemeral primary + real addedConvo agent', () => { let addedAgent; beforeEach(async () => { addedAgent = await createAgent({ id: `agent_added_${Date.now()}`, name: 'Added Agent', provider: 'openai', model: 'gpt-4', author: otherUser._id, }); await AclEntry.create({ principalType: PrincipalType.USER, principalId: otherUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.AGENT, resourceId: addedAgent._id, permBits: 15, grantedBy: otherUser._id, }); }); test('runs full addedConvo ACL check even when primary is ephemeral', async () => { req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(403); }); test('proceeds when user has ACL for added agent (ephemeral primary)', async () => { await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.AGENT, resourceId: addedAgent._id, permBits: 1, grantedBy: otherUser._id, }); req.body.addedConvo = { agent_id: addedAgent.id, endpoint: 'agents', model: 'gpt-4' }; const middleware = canAccessAgentFromBody({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); }); }); });