feat: add agent roles/permissions

This commit is contained in:
Danny Avila 2024-09-03 22:03:59 -04:00
parent e54352e4c7
commit 2931d1d9cb
No known key found for this signature in database
GPG key ID: 2DD9CC89B9B50364
5 changed files with 84 additions and 13 deletions

View file

@ -4,6 +4,7 @@ const {
roleDefaults,
PermissionTypes,
removeNullishValues,
agentPermissionsSchema,
promptPermissionsSchema,
bookmarkPermissionsSchema,
} = require('librechat-data-provider');
@ -71,6 +72,7 @@ const updateRoleByName = async function (roleName, updates) {
};
const permissionSchemas = {
[PermissionTypes.AGENTS]: agentPermissionsSchema,
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
};

View file

@ -28,6 +28,20 @@ const roleSchema = new mongoose.Schema({
default: true,
},
},
[PermissionTypes.AGENTS]: {
[Permissions.SHARED_GLOBAL]: {
type: Boolean,
default: false,
},
[Permissions.USE]: {
type: Boolean,
default: true,
},
[Permissions.CREATE]: {
type: Boolean,
default: true,
},
},
});
const Role = mongoose.model('Role', roleSchema);

View file

@ -1,5 +1,5 @@
const { nanoid } = require('nanoid');
const { FileContext } = require('librechat-data-provider');
const { FileContext, Constants } = require('librechat-data-provider');
const {
getAgent,
createAgent,
@ -9,6 +9,7 @@ const {
} = require('~/models/Agent');
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { uploadImageBuffer } = require('~/server/services/Files/process');
const { getProjectByName } = require('~/models/Project');
const { updateAgentProjects } = require('~/models/Agent');
const { deleteFileByFilter } = require('~/models/File');
const { logger } = require('~/config');
@ -54,16 +55,31 @@ const createAgentHandler = async (req, res) => {
* @param {object} req - Express Request
* @param {object} req.params - Request params
* @param {string} req.params.id - Agent identifier.
* @returns {Agent} 200 - success response - application/json
* @param {object} req.user - Authenticated user information
* @param {string} req.user.id - User ID
* @returns {Promise<Agent>} 200 - success response - application/json
* @returns {Error} 404 - Agent not found
*/
const getAgentHandler = async (req, res) => {
try {
const id = req.params.id;
const agent = await getAgent({ id });
const author = req.user.id;
let query = { id, author };
const globalProject = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, ['agentIds']);
if (globalProject && (globalProject.agentIds?.length ?? 0) > 0) {
query = {
$or: [{ id, $in: globalProject.agentIds }, query],
};
}
const agent = await getAgent(query);
if (!agent) {
return res.status(404).json({ error: 'Agent not found' });
}
return res.status(200).json(agent);
} catch (error) {
logger.error('[/Agents/:id] Error retrieving agent', error);
@ -127,7 +143,7 @@ const deleteAgentHandler = async (req, res) => {
* @param {object} req - Express Request
* @param {object} req.query - Request query
* @param {string} [req.query.user] - The user ID of the agent's author.
* @returns {AgentListResponse} 200 - success response - application/json
* @returns {Promise<AgentListResponse>} 200 - success response - application/json
*/
const getListAgentsHandler = async (req, res) => {
try {

View file

@ -1,11 +1,30 @@
const multer = require('multer');
const express = require('express');
const { PermissionTypes, Permissions } = require('librechat-data-provider');
const { requireJwtAuth, generateCheckAccess } = require('~/server/middleware');
const v1 = require('~/server/controllers/agents/v1');
const actions = require('./actions');
const upload = multer();
const router = express.Router();
const checkAgentAccess = generateCheckAccess(PermissionTypes.AGENTS, [Permissions.USE]);
const checkAgentCreate = generateCheckAccess(PermissionTypes.AGENTS, [
Permissions.USE,
Permissions.CREATE,
]);
const checkGlobalAgentShare = generateCheckAccess(
PermissionTypes.AGENTS,
[Permissions.USE, Permissions.CREATE],
{
[Permissions.SHARED_GLOBAL]: ['projectIds', 'removeProjectIds'],
},
);
router.use(requireJwtAuth);
router.use(checkAgentAccess);
/**
* Agent actions route.
* @route GET|POST /agents/actions
@ -27,7 +46,7 @@ router.use('/tools', (req, res) => {
* @param {AgentCreateParams} req.body - The agent creation parameters.
* @returns {Agent} 201 - Success response - application/json
*/
router.post('/', v1.createAgent);
router.post('/', checkAgentCreate, v1.createAgent);
/**
* Retrieves an agent.
@ -35,7 +54,7 @@ router.post('/', v1.createAgent);
* @param {string} req.params.id - Agent identifier.
* @returns {Agent} 200 - Success response - application/json
*/
router.get('/:id', v1.getAgent);
router.get('/:id', checkAgentAccess, v1.getAgent);
/**
* Updates an agent.
@ -44,7 +63,7 @@ router.get('/:id', v1.getAgent);
* @param {AgentUpdateParams} req.body - The agent update parameters.
* @returns {Agent} 200 - Success response - application/json
*/
router.patch('/:id', v1.updateAgent);
router.patch('/:id', checkGlobalAgentShare, v1.updateAgent);
/**
* Deletes an agent.
@ -52,7 +71,7 @@ router.patch('/:id', v1.updateAgent);
* @param {string} req.params.id - Agent identifier.
* @returns {Agent} 200 - success response - application/json
*/
router.delete('/:id', v1.deleteAgent);
router.delete('/:id', checkAgentCreate, v1.deleteAgent);
/**
* Returns a list of agents.
@ -60,9 +79,7 @@ router.delete('/:id', v1.deleteAgent);
* @param {AgentListParams} req.query - The agent list parameters for pagination and sorting.
* @returns {AgentListResponse} 200 - success response - application/json
*/
router.get('/', v1.getListAgents);
// TODO: handle private agents
router.get('/', checkAgentAccess, v1.getListAgents);
/**
* Uploads and updates an avatar for a specific agent.
@ -72,6 +89,6 @@ router.get('/', v1.getListAgents);
* @param {string} [req.body.metadata] - Optional metadata for the agent's avatar.
* @returns {Object} 200 - success response - application/json
*/
router.post('/avatar/:agent_id', upload.single('file'), v1.uploadAgentAvatar);
router.post('/avatar/:agent_id', checkAgentAccess, upload.single('file'), v1.uploadAgentAvatar);
module.exports = router;

View file

@ -23,9 +23,13 @@ export enum PermissionTypes {
*/
PROMPTS = 'PROMPTS',
/**
* Type for Bookmarks Permissions
* Type for Bookmark Permissions
*/
BOOKMARKS = 'BOOKMARKS',
/**
* Type for Agent Permissions
*/
AGENTS = 'AGENTS',
}
/**
@ -49,13 +53,22 @@ export const bookmarkPermissionsSchema = z.object({
[Permissions.USE]: z.boolean().default(true),
});
export const agentPermissionsSchema = z.object({
[Permissions.SHARED_GLOBAL]: z.boolean().default(false),
[Permissions.USE]: z.boolean().default(true),
[Permissions.CREATE]: z.boolean().default(true),
[Permissions.SHARE]: z.boolean().default(false),
});
export const roleSchema = z.object({
name: z.string(),
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
[PermissionTypes.AGENTS]: agentPermissionsSchema,
});
export type TRole = z.infer<typeof roleSchema>;
export type TAgentPermissions = z.infer<typeof agentPermissionsSchema>;
export type TPromptPermissions = z.infer<typeof promptPermissionsSchema>;
export type TBookmarkPermissions = z.infer<typeof bookmarkPermissionsSchema>;
@ -71,11 +84,18 @@ const defaultRolesSchema = z.object({
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema.extend({
[Permissions.USE]: z.boolean().default(true),
}),
[PermissionTypes.AGENTS]: agentPermissionsSchema.extend({
[Permissions.SHARED_GLOBAL]: z.boolean().default(true),
[Permissions.USE]: z.boolean().default(true),
[Permissions.CREATE]: z.boolean().default(true),
[Permissions.SHARE]: z.boolean().default(true),
}),
}),
[SystemRoles.USER]: roleSchema.extend({
name: z.literal(SystemRoles.USER),
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
[PermissionTypes.AGENTS]: agentPermissionsSchema,
}),
});
@ -84,10 +104,12 @@ export const roleDefaults = defaultRolesSchema.parse({
name: SystemRoles.ADMIN,
[PermissionTypes.PROMPTS]: {},
[PermissionTypes.BOOKMARKS]: {},
[PermissionTypes.AGENTS]: {},
},
[SystemRoles.USER]: {
name: SystemRoles.USER,
[PermissionTypes.PROMPTS]: {},
[PermissionTypes.BOOKMARKS]: {},
[PermissionTypes.AGENTS]: {},
},
});