| const mongoose = require('mongoose'); |
| const { MongoMemoryServer } = require('mongodb-memory-server'); |
| const { checkAccess, generateCheckAccess } = require('@librechat/api'); |
| const { PermissionTypes, Permissions } = require('librechat-data-provider'); |
| const { getRoleByName } = require('~/models/Role'); |
| const { Role } = require('~/db/models'); |
|
|
| |
| jest.mock('@librechat/data-schemas', () => ({ |
| ...jest.requireActual('@librechat/data-schemas'), |
| logger: { |
| warn: jest.fn(), |
| error: jest.fn(), |
| info: jest.fn(), |
| debug: jest.fn(), |
| }, |
| })); |
|
|
| |
| const mockCache = new Map(); |
| jest.mock('~/cache/getLogStores', () => { |
| return jest.fn(() => ({ |
| get: jest.fn(async (key) => mockCache.get(key)), |
| set: jest.fn(async (key, value) => mockCache.set(key, value)), |
| clear: jest.fn(async () => mockCache.clear()), |
| })); |
| }); |
|
|
| describe('Access Middleware', () => { |
| let mongoServer; |
| let req, res, next; |
|
|
| beforeAll(async () => { |
| mongoServer = await MongoMemoryServer.create(); |
| const mongoUri = mongoServer.getUri(); |
| await mongoose.connect(mongoUri); |
| }); |
|
|
| afterAll(async () => { |
| await mongoose.disconnect(); |
| await mongoServer.stop(); |
| }); |
|
|
| beforeEach(async () => { |
| await mongoose.connection.dropDatabase(); |
| mockCache.clear(); |
|
|
| |
| await Role.create({ |
| name: 'user', |
| permissions: { |
| [PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true }, |
| [PermissionTypes.PROMPTS]: { |
| [Permissions.SHARED_GLOBAL]: false, |
| [Permissions.USE]: true, |
| [Permissions.CREATE]: true, |
| }, |
| [PermissionTypes.MEMORIES]: { |
| [Permissions.USE]: true, |
| [Permissions.CREATE]: true, |
| [Permissions.UPDATE]: true, |
| [Permissions.READ]: true, |
| [Permissions.OPT_OUT]: true, |
| }, |
| [PermissionTypes.AGENTS]: { |
| [Permissions.USE]: true, |
| [Permissions.CREATE]: false, |
| [Permissions.SHARED_GLOBAL]: false, |
| }, |
| [PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true }, |
| [PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: true }, |
| [PermissionTypes.RUN_CODE]: { [Permissions.USE]: true }, |
| [PermissionTypes.WEB_SEARCH]: { [Permissions.USE]: true }, |
| }, |
| }); |
|
|
| await Role.create({ |
| name: 'admin', |
| permissions: { |
| [PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true }, |
| [PermissionTypes.PROMPTS]: { |
| [Permissions.SHARED_GLOBAL]: true, |
| [Permissions.USE]: true, |
| [Permissions.CREATE]: true, |
| }, |
| [PermissionTypes.MEMORIES]: { |
| [Permissions.USE]: true, |
| [Permissions.CREATE]: true, |
| [Permissions.UPDATE]: true, |
| [Permissions.READ]: true, |
| [Permissions.OPT_OUT]: true, |
| }, |
| [PermissionTypes.AGENTS]: { |
| [Permissions.USE]: true, |
| [Permissions.CREATE]: true, |
| [Permissions.SHARED_GLOBAL]: true, |
| }, |
| [PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true }, |
| [PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: true }, |
| [PermissionTypes.RUN_CODE]: { [Permissions.USE]: true }, |
| [PermissionTypes.WEB_SEARCH]: { [Permissions.USE]: true }, |
| }, |
| }); |
|
|
| |
| await Role.create({ |
| name: 'limited', |
| permissions: { |
| |
| [PermissionTypes.AGENTS]: { |
| [Permissions.USE]: false, |
| [Permissions.CREATE]: false, |
| [Permissions.SHARED_GLOBAL]: false, |
| }, |
| |
| [PermissionTypes.PROMPTS]: { |
| [Permissions.USE]: true, |
| }, |
| }, |
| }); |
|
|
| req = { |
| user: { id: 'user123', role: 'user' }, |
| body: {}, |
| originalUrl: '/test', |
| }; |
| res = { |
| status: jest.fn().mockReturnThis(), |
| json: jest.fn(), |
| }; |
| next = jest.fn(); |
| jest.clearAllMocks(); |
| }); |
|
|
| describe('checkAccess', () => { |
| test('should return false if user is not provided', async () => { |
| const result = await checkAccess({ |
| user: null, |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.USE], |
| getRoleByName, |
| }); |
| expect(result).toBe(false); |
| }); |
|
|
| test('should return true if user has required permission', async () => { |
| const result = await checkAccess({ |
| req: {}, |
| user: { id: 'user123', role: 'user' }, |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.USE], |
| getRoleByName, |
| }); |
| expect(result).toBe(true); |
| }); |
|
|
| test('should return false if user lacks required permission', async () => { |
| const result = await checkAccess({ |
| req: {}, |
| user: { id: 'user123', role: 'user' }, |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.CREATE], |
| getRoleByName, |
| }); |
| expect(result).toBe(false); |
| }); |
|
|
| test('should return false if user has only some of multiple permissions', async () => { |
| |
| const result = await checkAccess({ |
| req: {}, |
| user: { id: 'user123', role: 'user' }, |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.CREATE, Permissions.USE], |
| getRoleByName, |
| }); |
| expect(result).toBe(false); |
| }); |
|
|
| test('should return true if user has all of multiple permissions', async () => { |
| |
| const result = await checkAccess({ |
| req: {}, |
| user: { id: 'admin123', role: 'admin' }, |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.CREATE, Permissions.USE], |
| getRoleByName, |
| }); |
| expect(result).toBe(true); |
| }); |
|
|
| test('should check body properties when permission is not directly granted', async () => { |
| const req = { body: { id: 'agent123' } }; |
| const result = await checkAccess({ |
| req, |
| user: { id: 'user123', role: 'user' }, |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.UPDATE], |
| bodyProps: { |
| [Permissions.UPDATE]: ['id'], |
| }, |
| checkObject: req.body, |
| getRoleByName, |
| }); |
| expect(result).toBe(true); |
| }); |
|
|
| test('should return false if role is not found', async () => { |
| const result = await checkAccess({ |
| req: {}, |
| user: { id: 'user123', role: 'nonexistent' }, |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.USE], |
| getRoleByName, |
| }); |
| expect(result).toBe(false); |
| }); |
|
|
| test('should return false if role has no permissions for the requested type', async () => { |
| const result = await checkAccess({ |
| req: {}, |
| user: { id: 'user123', role: 'limited' }, |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.USE], |
| getRoleByName, |
| }); |
| expect(result).toBe(false); |
| }); |
|
|
| test('should handle admin role with all permissions', async () => { |
| const createResult = await checkAccess({ |
| req: {}, |
| user: { id: 'admin123', role: 'admin' }, |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.CREATE], |
| getRoleByName, |
| }); |
| expect(createResult).toBe(true); |
|
|
| const shareResult = await checkAccess({ |
| req: {}, |
| user: { id: 'admin123', role: 'admin' }, |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.SHARED_GLOBAL], |
| getRoleByName, |
| }); |
| expect(shareResult).toBe(true); |
| }); |
| }); |
|
|
| describe('generateCheckAccess', () => { |
| test('should call next() when user has required permission', async () => { |
| const middleware = generateCheckAccess({ |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.USE], |
| getRoleByName, |
| }); |
| await middleware(req, res, next); |
|
|
| expect(next).toHaveBeenCalled(); |
| expect(res.status).not.toHaveBeenCalled(); |
| }); |
|
|
| test('should return 403 when user lacks permission', async () => { |
| const middleware = generateCheckAccess({ |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.CREATE], |
| getRoleByName, |
| }); |
| await middleware(req, res, next); |
|
|
| expect(next).not.toHaveBeenCalled(); |
| expect(res.status).toHaveBeenCalledWith(403); |
| expect(res.json).toHaveBeenCalledWith({ message: 'Forbidden: Insufficient permissions' }); |
| }); |
|
|
| test('should check body properties when configured', async () => { |
| req.body = { agentId: 'agent123', description: 'test' }; |
|
|
| const bodyProps = { |
| [Permissions.CREATE]: ['agentId'], |
| }; |
|
|
| const middleware = generateCheckAccess({ |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.CREATE], |
| bodyProps, |
| getRoleByName, |
| }); |
| await middleware(req, res, next); |
|
|
| expect(next).toHaveBeenCalled(); |
| expect(res.status).not.toHaveBeenCalled(); |
| }); |
|
|
| test('should handle database errors gracefully', async () => { |
| |
| const mockGetRoleByName = jest |
| .fn() |
| .mockRejectedValue(new Error('Database connection failed')); |
|
|
| const middleware = generateCheckAccess({ |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.USE], |
| getRoleByName: mockGetRoleByName, |
| }); |
| await middleware(req, res, next); |
|
|
| expect(next).not.toHaveBeenCalled(); |
| expect(res.status).toHaveBeenCalledWith(500); |
| expect(res.json).toHaveBeenCalledWith({ |
| message: expect.stringContaining('Server error:'), |
| }); |
| }); |
|
|
| test('should work with multiple permission types', async () => { |
| req.user.role = 'admin'; |
|
|
| const middleware = generateCheckAccess({ |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.USE, Permissions.CREATE, Permissions.SHARED_GLOBAL], |
| getRoleByName, |
| }); |
| await middleware(req, res, next); |
|
|
| expect(next).toHaveBeenCalled(); |
| }); |
|
|
| test('should handle missing user gracefully', async () => { |
| req.user = null; |
|
|
| const middleware = generateCheckAccess({ |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.USE], |
| getRoleByName, |
| }); |
| await middleware(req, res, next); |
|
|
| expect(next).not.toHaveBeenCalled(); |
| expect(res.status).toHaveBeenCalledWith(403); |
| expect(res.json).toHaveBeenCalledWith({ message: 'Forbidden: Insufficient permissions' }); |
| }); |
|
|
| test('should handle role with no AGENTS permissions', async () => { |
| await Role.create({ |
| name: 'noaccess', |
| permissions: { |
| |
| [PermissionTypes.AGENTS]: { |
| [Permissions.USE]: false, |
| [Permissions.CREATE]: false, |
| [Permissions.SHARED_GLOBAL]: false, |
| }, |
| }, |
| }); |
| req.user.role = 'noaccess'; |
|
|
| const middleware = generateCheckAccess({ |
| permissionType: PermissionTypes.AGENTS, |
| permissions: [Permissions.USE], |
| getRoleByName, |
| }); |
| await middleware(req, res, next); |
|
|
| expect(next).not.toHaveBeenCalled(); |
| expect(res.status).toHaveBeenCalledWith(403); |
| expect(res.json).toHaveBeenCalledWith({ message: 'Forbidden: Insufficient permissions' }); |
| }); |
| }); |
| }); |
|
|