mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00
🧹 chore: Add Back Agent-Specific File Retrieval and Deletion Permissions
This commit is contained in:
parent
5a2eb74c2d
commit
8d51f450e8
2 changed files with 107 additions and 8 deletions
|
@ -6,6 +6,7 @@ const {
|
||||||
isUUID,
|
isUUID,
|
||||||
CacheKeys,
|
CacheKeys,
|
||||||
FileSources,
|
FileSources,
|
||||||
|
PERMISSION_BITS,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
isAgentsEndpoint,
|
isAgentsEndpoint,
|
||||||
checkOpenAIStorage,
|
checkOpenAIStorage,
|
||||||
|
@ -18,8 +19,10 @@ const {
|
||||||
} = require('~/server/services/Files/process');
|
} = require('~/server/services/Files/process');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
||||||
|
const { checkPermission } = require('~/server/services/PermissionService');
|
||||||
const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
||||||
const { refreshS3FileUrls } = require('~/server/services/Files/S3/crud');
|
const { refreshS3FileUrls } = require('~/server/services/Files/S3/crud');
|
||||||
|
const { hasAccessToFilesViaAgent } = require('~/server/services/Files');
|
||||||
const { getFiles, batchUpdateFiles } = require('~/models/File');
|
const { getFiles, batchUpdateFiles } = require('~/models/File');
|
||||||
const { getAssistant } = require('~/models/Assistant');
|
const { getAssistant } = require('~/models/Assistant');
|
||||||
const { getAgent } = require('~/models/Agent');
|
const { getAgent } = require('~/models/Agent');
|
||||||
|
@ -52,6 +55,61 @@ router.get('/', async (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get files specific to an agent
|
||||||
|
* @route GET /files/agent/:agent_id
|
||||||
|
* @param {string} agent_id - The agent ID to get files for
|
||||||
|
* @returns {Promise<TFile[]>} Array of files attached to the agent
|
||||||
|
*/
|
||||||
|
router.get('/agent/:agent_id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { agent_id } = req.params;
|
||||||
|
const userId = req.user.id;
|
||||||
|
|
||||||
|
if (!agent_id) {
|
||||||
|
return res.status(400).json({ error: 'Agent ID is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const agent = await getAgent({ id: agent_id });
|
||||||
|
if (!agent) {
|
||||||
|
return res.status(200).json([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agent.author.toString() !== userId) {
|
||||||
|
const hasEditPermission = await checkPermission({
|
||||||
|
userId,
|
||||||
|
resourceType: 'agent',
|
||||||
|
resourceId: agent._id,
|
||||||
|
requiredPermission: PERMISSION_BITS.EDIT,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasEditPermission) {
|
||||||
|
return res.status(200).json([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentFileIds = [];
|
||||||
|
if (agent.tool_resources) {
|
||||||
|
for (const [, resource] of Object.entries(agent.tool_resources)) {
|
||||||
|
if (resource?.file_ids && Array.isArray(resource.file_ids)) {
|
||||||
|
agentFileIds.push(...resource.file_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agentFileIds.length === 0) {
|
||||||
|
return res.status(200).json([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await getFiles({ file_id: { $in: agentFileIds } }, null, { text: 0 });
|
||||||
|
|
||||||
|
res.status(200).json(files);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[/files/agent/:agent_id] Error fetching agent files:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to fetch agent files' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/config', async (req, res) => {
|
router.get('/config', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
res.status(200).json(req.app.locals.fileConfig);
|
res.status(200).json(req.app.locals.fileConfig);
|
||||||
|
@ -88,11 +146,55 @@ router.delete('/', async (req, res) => {
|
||||||
|
|
||||||
const fileIds = files.map((file) => file.file_id);
|
const fileIds = files.map((file) => file.file_id);
|
||||||
const dbFiles = await getFiles({ file_id: { $in: fileIds } });
|
const dbFiles = await getFiles({ file_id: { $in: fileIds } });
|
||||||
const unauthorizedFiles = dbFiles.filter((file) => file.user.toString() !== req.user.id);
|
|
||||||
|
const ownedFiles = [];
|
||||||
|
const nonOwnedFiles = [];
|
||||||
|
|
||||||
|
for (const file of dbFiles) {
|
||||||
|
if (file.user.toString() === req.user.id) {
|
||||||
|
ownedFiles.push(file);
|
||||||
|
} else {
|
||||||
|
nonOwnedFiles.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonOwnedFiles.length === 0) {
|
||||||
|
await processDeleteRequest({ req, files: ownedFiles });
|
||||||
|
logger.debug(
|
||||||
|
`[/files] Files deleted successfully: ${ownedFiles
|
||||||
|
.filter((f) => f.file_id)
|
||||||
|
.map((f) => f.file_id)
|
||||||
|
.join(', ')}`,
|
||||||
|
);
|
||||||
|
res.status(200).json({ message: 'Files deleted successfully' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let authorizedFiles = [...ownedFiles];
|
||||||
|
let unauthorizedFiles = [];
|
||||||
|
|
||||||
|
if (req.body.agent_id && nonOwnedFiles.length > 0) {
|
||||||
|
const nonOwnedFileIds = nonOwnedFiles.map((f) => f.file_id);
|
||||||
|
const accessMap = await hasAccessToFilesViaAgent(
|
||||||
|
req.user.id,
|
||||||
|
nonOwnedFileIds,
|
||||||
|
req.body.agent_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const file of nonOwnedFiles) {
|
||||||
|
if (accessMap.get(file.file_id)) {
|
||||||
|
authorizedFiles.push(file);
|
||||||
|
} else {
|
||||||
|
unauthorizedFiles.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unauthorizedFiles = nonOwnedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
if (unauthorizedFiles.length > 0) {
|
if (unauthorizedFiles.length > 0) {
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
message: 'You can only delete your own files',
|
message: 'You can only delete files you have access to',
|
||||||
unauthorizedFiles: unauthorizedFiles.map((f) => f.file_id),
|
unauthorizedFiles: unauthorizedFiles.map((f) => f.file_id),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -133,10 +235,10 @@ router.delete('/', async (req, res) => {
|
||||||
.json({ message: 'File associations removed successfully from Azure Assistant' });
|
.json({ message: 'File associations removed successfully from Azure Assistant' });
|
||||||
}
|
}
|
||||||
|
|
||||||
await processDeleteRequest({ req, files: dbFiles });
|
await processDeleteRequest({ req, files: authorizedFiles });
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`[/files] Files deleted successfully: ${files
|
`[/files] Files deleted successfully: ${authorizedFiles
|
||||||
.filter((f) => f.file_id)
|
.filter((f) => f.file_id)
|
||||||
.map((f) => f.file_id)
|
.map((f) => f.file_id)
|
||||||
.join(', ')}`,
|
.join(', ')}`,
|
||||||
|
|
|
@ -561,7 +561,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
|
||||||
text,
|
text,
|
||||||
bytes,
|
bytes,
|
||||||
// TODO: OCR images support?
|
// TODO: OCR images support?
|
||||||
images,
|
images: _i,
|
||||||
filename,
|
filename,
|
||||||
filepath: ocrFileURL,
|
filepath: ocrFileURL,
|
||||||
} = await uploadOCR({ req, file, loadAuthValues });
|
} = await uploadOCR({ req, file, loadAuthValues });
|
||||||
|
@ -753,9 +753,6 @@ const processOpenAIImageOutput = async ({ req, buffer, file_id, filename, fileEx
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
const formattedDate = currentDate.toISOString();
|
const formattedDate = currentDate.toISOString();
|
||||||
const _file = await convertImage(req, buffer, undefined, `${file_id}${fileExt}`);
|
const _file = await convertImage(req, buffer, undefined, `${file_id}${fileExt}`);
|
||||||
// Determine the correct source for the assistant
|
|
||||||
const source =
|
|
||||||
req.body.endpoint === EModelEndpoint.azureAssistants ? FileSources.azure : FileSources.openai;
|
|
||||||
|
|
||||||
// Create only one file record with the correct information
|
// Create only one file record with the correct information
|
||||||
const file = {
|
const file = {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue