mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🔧 refactor: Organize Sharing/Agent Components and Improve Type Safety
refactor: organize Sharing/Agent components, improve type safety for resource types and access role ids, rename enums to PascalCase refactor: organize Sharing/Agent components, improve type safety for resource types and access role ids chore: move sharing related components to dedicated "Sharing" directory chore: remove PublicSharingToggle component and update index exports chore: move non-sidepanel agent components to `~/components/Agents` chore: move AgentCategoryDisplay component with tests chore: remove commented out code refactor: change PERMISSION_BITS from const to enum for better type safety refactor: reorganize imports in GenericGrantAccessDialog and update index exports for hooks refactor: update type definitions to use ACCESS_ROLE_IDS for improved type safety refactor: remove unused canAccessPromptResource middleware and related code refactor: remove unused prompt access roles from createAccessRoleMethods refactor: update resourceType in AclEntry type definition to remove unused 'prompt' value refactor: introduce ResourceType enum and update resourceType usage across data provider files for improved type safety refactor: update resourceType usage to ResourceType enum across sharing and permissions components for improved type safety refactor: standardize resourceType usage to ResourceType enum across agent and prompt models, permissions controller, and middleware for enhanced type safety refactor: update resourceType references from PROMPT_GROUP to PROMPTGROUP for consistency across models, middleware, and components refactor: standardize access role IDs and resource type usage across agent, file, and prompt models for improved type safety and consistency chore: add typedefs for TUpdateResourcePermissionsRequest and TUpdateResourcePermissionsResponse to enhance type definitions chore: move SearchPicker to PeoplePicker dir refactor: implement debouncing for query changes in SearchPicker for improved performance chore: fix typing, import order for agent admin settings fix: agent admin settings, prevent agent form submission refactor: rename `ACCESS_ROLE_IDS` to `AccessRoleIds` refactor: replace PermissionBits with PERMISSION_BITS refactor: replace PERMISSION_BITS with PermissionBits
This commit is contained in:
parent
ae732b2ebc
commit
81b32e400a
96 changed files with 781 additions and 798 deletions
|
|
@ -1,23 +1,19 @@
|
|||
const mongoose = require('mongoose');
|
||||
const crypto = require('node:crypto');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { SystemRoles, Tools, actionDelimiter } = require('librechat-data-provider');
|
||||
const { ResourceType, SystemRoles, Tools, actionDelimiter } = require('librechat-data-provider');
|
||||
const { GLOBAL_PROJECT_NAME, EPHEMERAL_AGENT_ID, mcp_delimiter } =
|
||||
require('librechat-data-provider').Constants;
|
||||
const {
|
||||
getProjectByName,
|
||||
addAgentIdsToProject,
|
||||
removeAgentIdsFromProject,
|
||||
removeAgentFromAllProjects,
|
||||
removeAgentIdsFromProject,
|
||||
addAgentIdsToProject,
|
||||
getProjectByName,
|
||||
} = require('./Project');
|
||||
const { getCachedTools } = require('~/server/services/Config');
|
||||
const { removeAllPermissions } = require('~/server/services/PermissionService');
|
||||
const { Agent } = require('~/db/models');
|
||||
|
||||
/**
|
||||
* Category values are now imported from shared constants
|
||||
*/
|
||||
const { getCachedTools } = require('~/server/services/Config');
|
||||
const { getActions } = require('./Action');
|
||||
const { Agent } = require('~/db/models');
|
||||
|
||||
/**
|
||||
* Create an agent with the provided data.
|
||||
|
|
@ -511,7 +507,7 @@ const deleteAgent = async (searchParameter) => {
|
|||
if (agent) {
|
||||
await removeAgentFromAllProjects(agent.id);
|
||||
await removeAllPermissions({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const mongoose = require('mongoose');
|
|||
const { v4: uuidv4 } = require('uuid');
|
||||
const { agentSchema } = require('@librechat/data-schemas');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||
const {
|
||||
getAgent,
|
||||
loadAgent,
|
||||
|
|
@ -21,14 +22,14 @@ const {
|
|||
updateAgent,
|
||||
deleteAgent,
|
||||
getListAgents,
|
||||
revertAgentVersion,
|
||||
updateAgentProjects,
|
||||
addAgentResourceFile,
|
||||
removeAgentResourceFiles,
|
||||
generateActionMetadataHash,
|
||||
revertAgentVersion,
|
||||
} = require('./Agent');
|
||||
const { getCachedTools } = require('~/server/services/Config');
|
||||
const permissionService = require('~/server/services/PermissionService');
|
||||
const { getCachedTools } = require('~/server/services/Config');
|
||||
const { AclEntry } = require('~/db/models');
|
||||
|
||||
/**
|
||||
|
|
@ -423,10 +424,10 @@ describe('models/Agent', () => {
|
|||
|
||||
// Create necessary access roles for agents
|
||||
await AccessRole.create({
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
name: 'Owner',
|
||||
description: 'Full control over agents',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: 15, // VIEW | EDIT | DELETE | SHARE
|
||||
});
|
||||
}, 20000);
|
||||
|
|
@ -501,15 +502,15 @@ describe('models/Agent', () => {
|
|||
await permissionService.grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: authorId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
// Verify ACL entry exists
|
||||
const aclEntriesBefore = await AclEntry.find({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
});
|
||||
expect(aclEntriesBefore).toHaveLength(1);
|
||||
|
|
@ -523,7 +524,7 @@ describe('models/Agent', () => {
|
|||
|
||||
// Verify ACL entries are removed
|
||||
const aclEntriesAfter = await AclEntry.find({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
});
|
||||
expect(aclEntriesAfter).toHaveLength(0);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
const mongoose = require('mongoose');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const { createModels } = require('@librechat/data-schemas');
|
||||
const { getFiles, createFile } = require('./File');
|
||||
const { createAgent } = require('./Agent');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||
const { grantPermission } = require('~/server/services/PermissionService');
|
||||
const { getFiles, createFile } = require('./File');
|
||||
const { seedDefaultRoles } = require('~/models');
|
||||
const { createAgent } = require('./Agent');
|
||||
|
||||
let File;
|
||||
let Agent;
|
||||
|
|
@ -116,9 +117,9 @@ describe('File Access Control', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
|
|
@ -233,9 +234,9 @@ describe('File Access Control', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
|
|
@ -291,9 +292,9 @@ describe('File Access Control', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
const { ObjectId } = require('mongodb');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { SystemRoles, SystemCategories, Constants } = require('librechat-data-provider');
|
||||
const {
|
||||
getProjectByName,
|
||||
addGroupIdsToProject,
|
||||
removeGroupIdsFromProject,
|
||||
Constants,
|
||||
SystemRoles,
|
||||
ResourceType,
|
||||
SystemCategories,
|
||||
} = require('librechat-data-provider');
|
||||
const {
|
||||
removeGroupFromAllProjects,
|
||||
removeGroupIdsFromProject,
|
||||
addGroupIdsToProject,
|
||||
getProjectByName,
|
||||
} = require('./Project');
|
||||
const { removeAllPermissions } = require('~/server/services/PermissionService');
|
||||
const { PromptGroup, Prompt } = require('~/db/models');
|
||||
|
|
@ -234,7 +239,7 @@ const deletePromptGroup = async ({ _id, author, role }) => {
|
|||
await removeGroupFromAllProjects(_id);
|
||||
|
||||
try {
|
||||
await removeAllPermissions({ resourceType: 'promptGroup', resourceId: _id });
|
||||
await removeAllPermissions({ resourceType: ResourceType.PROMPTGROUP, resourceId: _id });
|
||||
} catch (error) {
|
||||
logger.error('Error removing promptGroup permissions:', error);
|
||||
}
|
||||
|
|
@ -428,16 +433,6 @@ module.exports = {
|
|||
throw new Error('Failed to delete the prompt');
|
||||
}
|
||||
|
||||
// Remove all ACL entries for this prompt
|
||||
try {
|
||||
await removeAllPermissions({
|
||||
resourceType: 'prompt',
|
||||
resourceId: promptId,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error removing prompt permissions:', error);
|
||||
}
|
||||
|
||||
const remainingPrompts = await Prompt.find({ groupId })
|
||||
.select('_id')
|
||||
.sort({ createdAt: 1 })
|
||||
|
|
@ -447,7 +442,7 @@ module.exports = {
|
|||
// Remove all ACL entries for the promptGroup when deleting the last prompt
|
||||
try {
|
||||
await removeAllPermissions({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: groupId,
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
const { ObjectId } = require('mongodb');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const mongoose = require('mongoose');
|
||||
const { SystemRoles } = require('librechat-data-provider');
|
||||
const { logger, PermissionBits } = require('@librechat/data-schemas');
|
||||
const { ObjectId } = require('mongodb');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const {
|
||||
SystemRoles,
|
||||
ResourceType,
|
||||
AccessRoleIds,
|
||||
PermissionBits,
|
||||
} = require('librechat-data-provider');
|
||||
|
||||
// Mock the config/connect module to prevent connection attempts during tests
|
||||
jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true));
|
||||
|
|
@ -49,24 +54,24 @@ async function setupTestData() {
|
|||
// Create access roles for promptGroups
|
||||
testRoles = {
|
||||
viewer: await AccessRole.create({
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
name: 'Viewer',
|
||||
description: 'Can view promptGroups',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: PermissionBits.VIEW,
|
||||
}),
|
||||
editor: await AccessRole.create({
|
||||
accessRoleId: 'promptGroup_editor',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
name: 'Editor',
|
||||
description: 'Can view and edit promptGroups',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: PermissionBits.VIEW | PermissionBits.EDIT,
|
||||
}),
|
||||
owner: await AccessRole.create({
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
name: 'Owner',
|
||||
description: 'Full control over promptGroups',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits:
|
||||
PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
|
||||
}),
|
||||
|
|
@ -148,15 +153,15 @@ describe('Prompt ACL Permissions', () => {
|
|||
await permissionService.grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testGroup._id,
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
// Check ACL entry
|
||||
const aclEntry = await AclEntry.findOne({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testGroup._id,
|
||||
principalType: 'user',
|
||||
principalId: testUsers.owner._id,
|
||||
|
|
@ -192,9 +197,9 @@ describe('Prompt ACL Permissions', () => {
|
|||
await permissionService.grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
});
|
||||
|
|
@ -208,7 +213,7 @@ describe('Prompt ACL Permissions', () => {
|
|||
it('owner should have full access to their prompt', async () => {
|
||||
const hasAccess = await permissionService.checkPermission({
|
||||
userId: testUsers.owner._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
});
|
||||
|
|
@ -217,7 +222,7 @@ describe('Prompt ACL Permissions', () => {
|
|||
|
||||
const canEdit = await permissionService.checkPermission({
|
||||
userId: testUsers.owner._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
|
@ -230,22 +235,22 @@ describe('Prompt ACL Permissions', () => {
|
|||
await permissionService.grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: testUsers.viewer._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
const canView = await permissionService.checkPermission({
|
||||
userId: testUsers.viewer._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
});
|
||||
|
||||
const canEdit = await permissionService.checkPermission({
|
||||
userId: testUsers.viewer._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
|
@ -257,7 +262,7 @@ describe('Prompt ACL Permissions', () => {
|
|||
it('user without permissions should have no access', async () => {
|
||||
const hasAccess = await permissionService.checkPermission({
|
||||
userId: testUsers.noAccess._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
});
|
||||
|
|
@ -270,7 +275,7 @@ describe('Prompt ACL Permissions', () => {
|
|||
// The middleware layer handles admin bypass, not the permission service
|
||||
const hasAccess = await permissionService.checkPermission({
|
||||
userId: testUsers.admin._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
});
|
||||
|
|
@ -278,7 +283,7 @@ describe('Prompt ACL Permissions', () => {
|
|||
// Without explicit permissions, even admin won't have access at this layer
|
||||
expect(hasAccess).toBe(false);
|
||||
|
||||
// The actual admin bypass happens in the middleware layer (canAccessPromptResource)
|
||||
// The actual admin bypass happens in the middleware layer (`canAccessPromptViaGroup`/`canAccessPromptGroupResource`)
|
||||
// which checks req.user.role === SystemRoles.ADMIN
|
||||
});
|
||||
});
|
||||
|
|
@ -352,16 +357,16 @@ describe('Prompt ACL Permissions', () => {
|
|||
await permissionService.grantPermission({
|
||||
principalType: 'group',
|
||||
principalId: testGroups.editors._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
accessRoleId: 'promptGroup_editor',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
// Check if group member has access
|
||||
const hasAccess = await permissionService.checkPermission({
|
||||
userId: testUsers.editor._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
|
@ -371,7 +376,7 @@ describe('Prompt ACL Permissions', () => {
|
|||
// Check that non-member doesn't have access
|
||||
const nonMemberAccess = await permissionService.checkPermission({
|
||||
userId: testUsers.viewer._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
|
@ -420,9 +425,9 @@ describe('Prompt ACL Permissions', () => {
|
|||
await permissionService.grantPermission({
|
||||
principalType: 'public',
|
||||
principalId: null,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: publicPromptGroup._id,
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
|
|
@ -430,9 +435,9 @@ describe('Prompt ACL Permissions', () => {
|
|||
await permissionService.grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: privatePromptGroup._id,
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
});
|
||||
|
|
@ -446,7 +451,7 @@ describe('Prompt ACL Permissions', () => {
|
|||
it('public prompt should be accessible to any user', async () => {
|
||||
const hasAccess = await permissionService.checkPermission({
|
||||
userId: testUsers.noAccess._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: publicPromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
includePublic: true,
|
||||
|
|
@ -458,7 +463,7 @@ describe('Prompt ACL Permissions', () => {
|
|||
it('private prompt should not be accessible to unauthorized users', async () => {
|
||||
const hasAccess = await permissionService.checkPermission({
|
||||
userId: testUsers.noAccess._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: privatePromptGroup._id,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
includePublic: true,
|
||||
|
|
@ -501,15 +506,15 @@ describe('Prompt ACL Permissions', () => {
|
|||
await permissionService.grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
// Verify ACL entry exists
|
||||
const beforeDelete = await AclEntry.find({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
});
|
||||
expect(beforeDelete).toHaveLength(1);
|
||||
|
|
@ -524,7 +529,7 @@ describe('Prompt ACL Permissions', () => {
|
|||
|
||||
// Verify ACL entries are removed
|
||||
const aclEntries = await AclEntry.find({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testPromptGroup._id,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
const { ObjectId } = require('mongodb');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const mongoose = require('mongoose');
|
||||
const { logger, PermissionBits } = require('@librechat/data-schemas');
|
||||
const { Constants } = require('librechat-data-provider');
|
||||
const { ObjectId } = require('mongodb');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const {
|
||||
Constants,
|
||||
ResourceType,
|
||||
AccessRoleIds,
|
||||
PermissionBits,
|
||||
} = require('librechat-data-provider');
|
||||
|
||||
// Mock the config/connect module to prevent connection attempts during tests
|
||||
jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true));
|
||||
|
|
@ -49,27 +54,27 @@ describe('PromptGroup Migration Script', () => {
|
|||
|
||||
// Create promptGroup access roles
|
||||
ownerRole = await AccessRole.create({
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
name: 'Owner',
|
||||
description: 'Full control over promptGroups',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits:
|
||||
PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
|
||||
});
|
||||
|
||||
viewerRole = await AccessRole.create({
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
name: 'Viewer',
|
||||
description: 'Can view promptGroups',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: PermissionBits.VIEW,
|
||||
});
|
||||
|
||||
await AccessRole.create({
|
||||
accessRoleId: 'promptGroup_editor',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
name: 'Editor',
|
||||
description: 'Can view and edit promptGroups',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: PermissionBits.VIEW | PermissionBits.EDIT,
|
||||
});
|
||||
|
||||
|
|
@ -103,7 +108,7 @@ describe('PromptGroup Migration Script', () => {
|
|||
});
|
||||
|
||||
// Create private prompt group (not in any project)
|
||||
const privatePromptGroup = await PromptGroup.create({
|
||||
await PromptGroup.create({
|
||||
name: 'Private Group',
|
||||
author: testOwner._id,
|
||||
authorName: testOwner.name,
|
||||
|
|
@ -151,7 +156,7 @@ describe('PromptGroup Migration Script', () => {
|
|||
|
||||
// Check global promptGroup permissions
|
||||
const globalOwnerEntry = await AclEntry.findOne({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: globalPromptGroup._id,
|
||||
principalType: 'user',
|
||||
principalId: testOwner._id,
|
||||
|
|
@ -160,7 +165,7 @@ describe('PromptGroup Migration Script', () => {
|
|||
expect(globalOwnerEntry.permBits).toBe(ownerRole.permBits);
|
||||
|
||||
const globalPublicEntry = await AclEntry.findOne({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: globalPromptGroup._id,
|
||||
principalType: 'public',
|
||||
});
|
||||
|
|
@ -169,7 +174,7 @@ describe('PromptGroup Migration Script', () => {
|
|||
|
||||
// Check private promptGroup permissions
|
||||
const privateOwnerEntry = await AclEntry.findOne({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: privatePromptGroup._id,
|
||||
principalType: 'user',
|
||||
principalId: testOwner._id,
|
||||
|
|
@ -178,7 +183,7 @@ describe('PromptGroup Migration Script', () => {
|
|||
expect(privateOwnerEntry.permBits).toBe(ownerRole.permBits);
|
||||
|
||||
const privatePublicEntry = await AclEntry.findOne({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: privatePromptGroup._id,
|
||||
principalType: 'public',
|
||||
});
|
||||
|
|
@ -206,7 +211,7 @@ describe('PromptGroup Migration Script', () => {
|
|||
principalType: 'user',
|
||||
principalId: testOwner._id,
|
||||
principalModel: 'User',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: promptGroup1._id,
|
||||
permBits: ownerRole.permBits,
|
||||
roleId: ownerRole._id,
|
||||
|
|
@ -222,7 +227,7 @@ describe('PromptGroup Migration Script', () => {
|
|||
|
||||
// Verify promptGroup2 now has permissions
|
||||
const group2Entry = await AclEntry.findOne({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: promptGroup2._id,
|
||||
});
|
||||
expect(group2Entry).toBeTruthy();
|
||||
|
|
@ -259,7 +264,7 @@ describe('PromptGroup Migration Script', () => {
|
|||
|
||||
// Verify the promptGroup has permissions
|
||||
const groupEntry = await AclEntry.findOne({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: promptGroup._id,
|
||||
});
|
||||
expect(groupEntry).toBeTruthy();
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
|
||||
const mongoose = require('mongoose');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { ResourceType } = require('librechat-data-provider');
|
||||
const {
|
||||
getAvailableRoles,
|
||||
ensurePrincipalExists,
|
||||
getEffectivePermissions,
|
||||
ensureGroupPrincipalExists,
|
||||
bulkUpdateResourcePermissions,
|
||||
ensureGroupPrincipalExists,
|
||||
getEffectivePermissions,
|
||||
ensurePrincipalExists,
|
||||
getAvailableRoles,
|
||||
} = require('~/server/services/PermissionService');
|
||||
const { AclEntry } = require('~/db/models');
|
||||
const {
|
||||
|
|
@ -18,8 +19,8 @@ const {
|
|||
calculateRelevanceScore,
|
||||
} = require('~/models');
|
||||
const {
|
||||
searchEntraIdPrincipals,
|
||||
entraIdPrincipalFeatureEnabled,
|
||||
searchEntraIdPrincipals,
|
||||
} = require('~/server/services/GraphApiService');
|
||||
|
||||
/**
|
||||
|
|
@ -27,6 +28,18 @@ const {
|
|||
* Delegates validation and logic to PermissionService
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates that the resourceType is one of the supported enum values
|
||||
* @param {string} resourceType - The resource type to validate
|
||||
* @throws {Error} If resourceType is not valid
|
||||
*/
|
||||
const validateResourceType = (resourceType) => {
|
||||
const validTypes = Object.values(ResourceType);
|
||||
if (!validTypes.includes(resourceType)) {
|
||||
throw new Error(`Invalid resourceType: ${resourceType}. Valid types: ${validTypes.join(', ')}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bulk update permissions for a resource (grant, update, remove)
|
||||
* @route PUT /api/{resourceType}/{resourceId}/permissions
|
||||
|
|
@ -41,6 +54,8 @@ const {
|
|||
const updateResourcePermissions = async (req, res) => {
|
||||
try {
|
||||
const { resourceType, resourceId } = req.params;
|
||||
validateResourceType(resourceType);
|
||||
|
||||
/** @type {TUpdateResourcePermissionsRequest} */
|
||||
const { updated, removed, public: isPublic, publicAccessRoleId } = req.body;
|
||||
const { id: userId } = req.user;
|
||||
|
|
@ -163,6 +178,7 @@ const updateResourcePermissions = async (req, res) => {
|
|||
const getResourcePermissions = async (req, res) => {
|
||||
try {
|
||||
const { resourceType, resourceId } = req.params;
|
||||
validateResourceType(resourceType);
|
||||
|
||||
// Use aggregation pipeline for efficient single-query data retrieval
|
||||
const results = await AclEntry.aggregate([
|
||||
|
|
@ -278,6 +294,7 @@ const getResourcePermissions = async (req, res) => {
|
|||
const getResourceRoles = async (req, res) => {
|
||||
try {
|
||||
const { resourceType } = req.params;
|
||||
validateResourceType(resourceType);
|
||||
|
||||
const roles = await getAvailableRoles({ resourceType });
|
||||
|
||||
|
|
@ -305,6 +322,8 @@ const getResourceRoles = async (req, res) => {
|
|||
const getUserEffectivePermissions = async (req, res) => {
|
||||
try {
|
||||
const { resourceType, resourceId } = req.params;
|
||||
validateResourceType(resourceType);
|
||||
|
||||
const { id: userId } = req.user;
|
||||
|
||||
const permissionBits = await getEffectivePermissions({
|
||||
|
|
|
|||
|
|
@ -1,30 +1,33 @@
|
|||
const { z } = require('zod');
|
||||
const fs = require('fs').promises;
|
||||
const { nanoid } = require('nanoid');
|
||||
const { logger, PermissionBits } = require('@librechat/data-schemas');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { agentCreateSchema, agentUpdateSchema } = require('@librechat/api');
|
||||
const {
|
||||
Tools,
|
||||
SystemRoles,
|
||||
FileSources,
|
||||
ResourceType,
|
||||
AccessRoleIds,
|
||||
EToolResources,
|
||||
actionDelimiter,
|
||||
PermissionBits,
|
||||
removeNullishValues,
|
||||
} = require('librechat-data-provider');
|
||||
const {
|
||||
getAgent,
|
||||
createAgent,
|
||||
updateAgent,
|
||||
deleteAgent,
|
||||
getListAgentsByAccess,
|
||||
countPromotedAgents,
|
||||
revertAgentVersion,
|
||||
createAgent,
|
||||
updateAgent,
|
||||
deleteAgent,
|
||||
getAgent,
|
||||
} = require('~/models/Agent');
|
||||
const {
|
||||
grantPermission,
|
||||
findAccessibleResources,
|
||||
findPubliclyAccessibleResources,
|
||||
findAccessibleResources,
|
||||
hasPublicPermission,
|
||||
grantPermission,
|
||||
} = require('~/server/services/PermissionService');
|
||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
||||
|
|
@ -79,9 +82,9 @@ const createAgentHandler = async (req, res) => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
grantedBy: userId,
|
||||
});
|
||||
logger.debug(
|
||||
|
|
@ -146,7 +149,7 @@ const getAgentHandler = async (req, res, expandProperties = false) => {
|
|||
|
||||
// Check if agent is public
|
||||
const isPublic = await hasPublicPermission({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
requiredPermissions: PermissionBits.VIEW,
|
||||
});
|
||||
|
|
@ -345,9 +348,9 @@ const duplicateAgentHandler = async (req, res) => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: newAgent._id,
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
grantedBy: userId,
|
||||
});
|
||||
logger.debug(
|
||||
|
|
@ -440,11 +443,11 @@ const getListAgentsHandler = async (req, res) => {
|
|||
// Get agent IDs the user has VIEW access to via ACL
|
||||
const accessibleIds = await findAccessibleResources({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermissions: requiredPermission,
|
||||
});
|
||||
const publiclyAccessibleIds = await findPubliclyAccessibleResources({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermissions: PermissionBits.VIEW,
|
||||
});
|
||||
// Use the new ACL-aware function
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const { logger } = require('@librechat/data-schemas');
|
||||
const { Constants, isAgentsEndpoint } = require('librechat-data-provider');
|
||||
const { Constants, isAgentsEndpoint, ResourceType } = require('librechat-data-provider');
|
||||
const { canAccessResource } = require('./canAccessResource');
|
||||
const { getAgent } = require('~/models/Agent');
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ const canAccessAgentFromBody = (options) => {
|
|||
}
|
||||
|
||||
const agentAccessMiddleware = canAccessResource({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermission,
|
||||
resourceIdParam: 'agent_id', // This will be ignored since we use custom resolver
|
||||
idResolver: () => resolveAgentIdFromBody(agentId),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const { getAgent } = require('~/models/Agent');
|
||||
const { ResourceType } = require('librechat-data-provider');
|
||||
const { canAccessResource } = require('./canAccessResource');
|
||||
const { getAgent } = require('~/models/Agent');
|
||||
|
||||
/**
|
||||
* Agent ID resolver function
|
||||
|
|
@ -46,7 +47,7 @@ const canAccessAgentResource = (options) => {
|
|||
}
|
||||
|
||||
return canAccessResource({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermission,
|
||||
resourceIdParam,
|
||||
idResolver: resolveAgentId,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const mongoose = require('mongoose');
|
||||
const { ResourceType } = require('librechat-data-provider');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const { canAccessAgentResource } = require('./canAccessAgentResource');
|
||||
const { User, Role, AclEntry } = require('~/db/models');
|
||||
|
|
@ -99,7 +100,7 @@ describe('canAccessAgentResource middleware', () => {
|
|||
principalType: 'user',
|
||||
principalId: testUser._id,
|
||||
principalModel: 'User',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
permBits: 15, // All permissions (1+2+4+8)
|
||||
grantedBy: testUser._id,
|
||||
|
|
@ -136,7 +137,7 @@ describe('canAccessAgentResource middleware', () => {
|
|||
principalType: 'user',
|
||||
principalId: otherUser._id,
|
||||
principalModel: 'User',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
permBits: 15, // All permissions
|
||||
grantedBy: otherUser._id,
|
||||
|
|
@ -177,7 +178,7 @@ describe('canAccessAgentResource middleware', () => {
|
|||
principalType: 'user',
|
||||
principalId: testUser._id,
|
||||
principalModel: 'User',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
permBits: 1, // VIEW permission
|
||||
grantedBy: otherUser._id,
|
||||
|
|
@ -214,7 +215,7 @@ describe('canAccessAgentResource middleware', () => {
|
|||
principalType: 'user',
|
||||
principalId: testUser._id,
|
||||
principalModel: 'User',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
permBits: 1, // VIEW permission only
|
||||
grantedBy: otherUser._id,
|
||||
|
|
@ -261,7 +262,7 @@ describe('canAccessAgentResource middleware', () => {
|
|||
principalType: 'user',
|
||||
principalId: testUser._id,
|
||||
principalModel: 'User',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
permBits: 15, // All permissions
|
||||
grantedBy: testUser._id,
|
||||
|
|
@ -297,7 +298,7 @@ describe('canAccessAgentResource middleware', () => {
|
|||
principalType: 'user',
|
||||
principalId: testUser._id,
|
||||
principalModel: 'User',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
permBits: 15, // All permissions (1+2+4+8)
|
||||
grantedBy: testUser._id,
|
||||
|
|
@ -357,7 +358,7 @@ describe('canAccessAgentResource middleware', () => {
|
|||
principalType: 'user',
|
||||
principalId: testUser._id,
|
||||
principalModel: 'User',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
permBits: 15, // All permissions
|
||||
grantedBy: testUser._id,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const { getPromptGroup } = require('~/models/Prompt');
|
||||
const { ResourceType } = require('librechat-data-provider');
|
||||
const { canAccessResource } = require('./canAccessResource');
|
||||
const { getPromptGroup } = require('~/models/Prompt');
|
||||
|
||||
/**
|
||||
* PromptGroup ID resolver function
|
||||
|
|
@ -48,7 +49,7 @@ const canAccessPromptGroupResource = (options) => {
|
|||
}
|
||||
|
||||
return canAccessResource({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
requiredPermission,
|
||||
resourceIdParam,
|
||||
idResolver: resolvePromptGroupId,
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
const { getPrompt } = require('~/models/Prompt');
|
||||
const { canAccessResource } = require('./canAccessResource');
|
||||
|
||||
/**
|
||||
* Prompt ID resolver function
|
||||
* Resolves prompt ID to MongoDB ObjectId
|
||||
*
|
||||
* @param {string} promptId - Prompt ID from route parameter
|
||||
* @returns {Promise<Object|null>} Prompt document with _id field, or null if not found
|
||||
*/
|
||||
const resolvePromptId = async (promptId) => {
|
||||
return await getPrompt({ _id: promptId });
|
||||
};
|
||||
|
||||
/**
|
||||
* Prompt-specific middleware factory that creates middleware to check prompt access permissions.
|
||||
* This middleware extends the generic canAccessResource to handle prompt ID resolution.
|
||||
*
|
||||
* @param {Object} options - Configuration options
|
||||
* @param {number} options.requiredPermission - The permission bit required (1=view, 2=edit, 4=delete, 8=share)
|
||||
* @param {string} [options.resourceIdParam='promptId'] - The name of the route parameter containing the prompt ID
|
||||
* @returns {Function} Express middleware function
|
||||
*
|
||||
* @example
|
||||
* // Basic usage for viewing prompts
|
||||
* router.get('/prompts/:promptId',
|
||||
* canAccessPromptResource({ requiredPermission: 1 }),
|
||||
* getPrompt
|
||||
* );
|
||||
*
|
||||
* @example
|
||||
* // Custom resource ID parameter and edit permission
|
||||
* router.patch('/prompts/:id',
|
||||
* canAccessPromptResource({
|
||||
* requiredPermission: 2,
|
||||
* resourceIdParam: 'id'
|
||||
* }),
|
||||
* updatePrompt
|
||||
* );
|
||||
*/
|
||||
const canAccessPromptResource = (options) => {
|
||||
const { requiredPermission, resourceIdParam = 'promptId' } = options;
|
||||
|
||||
if (!requiredPermission || typeof requiredPermission !== 'number') {
|
||||
throw new Error('canAccessPromptResource: requiredPermission is required and must be a number');
|
||||
}
|
||||
|
||||
return canAccessResource({
|
||||
resourceType: 'prompt',
|
||||
requiredPermission,
|
||||
resourceIdParam,
|
||||
idResolver: resolvePromptId,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
canAccessPromptResource,
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
const { getPrompt } = require('~/models/Prompt');
|
||||
const { ResourceType } = require('librechat-data-provider');
|
||||
const { canAccessResource } = require('./canAccessResource');
|
||||
const { getPrompt } = require('~/models/Prompt');
|
||||
|
||||
/**
|
||||
* Prompt to PromptGroup ID resolver function
|
||||
|
|
@ -42,7 +43,7 @@ const canAccessPromptViaGroup = (options) => {
|
|||
}
|
||||
|
||||
return canAccessResource({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
requiredPermission,
|
||||
resourceIdParam,
|
||||
idResolver: resolvePromptToGroupId,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
const { logger } = require('@librechat/data-schemas');
|
||||
const { PERMISSION_BITS, hasPermissions } = require('librechat-data-provider');
|
||||
const { PermissionBits, hasPermissions, ResourceType } = require('librechat-data-provider');
|
||||
const { getEffectivePermissions } = require('~/server/services/PermissionService');
|
||||
const { getFiles } = require('~/models/File');
|
||||
const { getAgent } = require('~/models/Agent');
|
||||
const { getFiles } = require('~/models/File');
|
||||
|
||||
/**
|
||||
* Checks if user has access to a file through agent permissions
|
||||
|
|
@ -35,11 +35,11 @@ const checkAgentBasedFileAccess = async (userId, fileId) => {
|
|||
try {
|
||||
const permissions = await getEffectivePermissions({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id || agent.id,
|
||||
});
|
||||
|
||||
if (hasPermissions(permissions, PERMISSION_BITS.VIEW)) {
|
||||
if (hasPermissions(permissions, PermissionBits.VIEW)) {
|
||||
logger.debug(`[fileAccess] User ${userId} has VIEW permissions on agent ${agent.id}`);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
const { canAccessResource } = require('./canAccessResource');
|
||||
const { canAccessAgentResource } = require('./canAccessAgentResource');
|
||||
const { canAccessAgentFromBody } = require('./canAccessAgentFromBody');
|
||||
const { canAccessPromptResource } = require('./canAccessPromptResource');
|
||||
const { canAccessPromptViaGroup } = require('./canAccessPromptViaGroup');
|
||||
const { canAccessPromptGroupResource } = require('./canAccessPromptGroupResource');
|
||||
|
||||
|
|
@ -9,7 +8,6 @@ module.exports = {
|
|||
canAccessResource,
|
||||
canAccessAgentResource,
|
||||
canAccessAgentFromBody,
|
||||
canAccessPromptResource,
|
||||
canAccessPromptViaGroup,
|
||||
canAccessPromptGroupResource,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const express = require('express');
|
||||
const { PermissionBits } = require('@librechat/data-schemas');
|
||||
const { ResourceType, PermissionBits } = require('librechat-data-provider');
|
||||
const {
|
||||
getUserEffectivePermissions,
|
||||
updateResourcePermissions,
|
||||
|
|
@ -49,19 +49,17 @@ router.put(
|
|||
// Use middleware that dynamically handles resource type and permissions
|
||||
(req, res, next) => {
|
||||
const { resourceType } = req.params;
|
||||
|
||||
// Define resource-specific middleware based on resourceType
|
||||
let middleware;
|
||||
|
||||
if (resourceType === 'agent') {
|
||||
if (resourceType === ResourceType.AGENT) {
|
||||
middleware = canAccessResource({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermission: PermissionBits.SHARE,
|
||||
resourceIdParam: 'resourceId',
|
||||
});
|
||||
} else if (resourceType === 'promptGroup') {
|
||||
} else if (resourceType === ResourceType.PROMPTGROUP) {
|
||||
middleware = canAccessResource({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
requiredPermission: PermissionBits.SHARE,
|
||||
resourceIdParam: 'resourceId',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
const express = require('express');
|
||||
const { nanoid } = require('nanoid');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { generateCheckAccess } = require('@librechat/api');
|
||||
const { logger, PermissionBits } = require('@librechat/data-schemas');
|
||||
const {
|
||||
Permissions,
|
||||
ResourceType,
|
||||
PermissionTypes,
|
||||
actionDelimiter,
|
||||
PermissionBits,
|
||||
removeNullishValues,
|
||||
} = require('librechat-data-provider');
|
||||
const { encryptMetadata, domainParser } = require('~/server/services/ActionService');
|
||||
const { findAccessibleResources } = require('~/server/services/PermissionService');
|
||||
const { getAgent, updateAgent, getListAgentsByAccess } = require('~/models/Agent');
|
||||
const { updateAction, getActions, deleteAction } = require('~/models/Action');
|
||||
const { isActionDomainAllowed } = require('~/server/services/domains');
|
||||
const { canAccessAgentResource } = require('~/server/middleware');
|
||||
const { getAgent, updateAgent } = require('~/models/Agent');
|
||||
const { getRoleByName } = require('~/models/Role');
|
||||
const { getListAgentsByAccess } = require('~/models/Agent');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ router.get('/', async (req, res) => {
|
|||
const userId = req.user.id;
|
||||
const editableAgentObjectIds = await findAccessibleResources({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermissions: PermissionBits.EDIT,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
const express = require('express');
|
||||
const { PermissionBits } = require('@librechat/data-schemas');
|
||||
const { generateCheckAccess, skipAgentCheck } = require('@librechat/api');
|
||||
const { PermissionTypes, Permissions } = require('librechat-data-provider');
|
||||
const { PermissionTypes, Permissions, PermissionBits } = require('librechat-data-provider');
|
||||
const {
|
||||
setHeaders,
|
||||
moderateText,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
const express = require('express');
|
||||
const { generateCheckAccess } = require('@librechat/api');
|
||||
const { PermissionBits } = require('@librechat/data-schemas');
|
||||
const { PermissionTypes, Permissions } = require('librechat-data-provider');
|
||||
const { PermissionTypes, Permissions, PermissionBits } = require('librechat-data-provider');
|
||||
const { requireJwtAuth, canAccessAgentResource } = require('~/server/middleware');
|
||||
const v1 = require('~/server/controllers/agents/v1');
|
||||
const { getRoleByName } = require('~/models/Role');
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const mongoose = require('mongoose');
|
|||
const { v4: uuidv4 } = require('uuid');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const { createMethods } = require('@librechat/data-schemas');
|
||||
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||
const { createAgent } = require('~/models/Agent');
|
||||
const { createFile } = require('~/models/File');
|
||||
|
||||
|
|
@ -186,9 +187,9 @@ describe('File Routes - Agent Files Endpoint', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: otherUserId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
|
|
@ -241,9 +242,9 @@ describe('File Routes - Agent Files Endpoint', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: otherUserId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ const {
|
|||
isUUID,
|
||||
CacheKeys,
|
||||
FileSources,
|
||||
PERMISSION_BITS,
|
||||
ResourceType,
|
||||
EModelEndpoint,
|
||||
PermissionBits,
|
||||
isAgentsEndpoint,
|
||||
checkOpenAIStorage,
|
||||
} = require('librechat-data-provider');
|
||||
|
|
@ -17,6 +18,7 @@ const {
|
|||
processDeleteRequest,
|
||||
processAgentFileUpload,
|
||||
} = require('~/server/services/Files/process');
|
||||
const { fileAccess } = require('~/server/middleware/accessResources/fileAccess');
|
||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||
const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
||||
const { checkPermission } = require('~/server/services/PermissionService');
|
||||
|
|
@ -24,12 +26,11 @@ const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
|||
const { refreshS3FileUrls } = require('~/server/services/Files/S3/crud');
|
||||
const { hasAccessToFilesViaAgent } = require('~/server/services/Files');
|
||||
const { getFiles, batchUpdateFiles } = require('~/models/File');
|
||||
const { cleanFileName } = require('~/server/utils/files');
|
||||
const { getAssistant } = require('~/models/Assistant');
|
||||
const { getAgent } = require('~/models/Agent');
|
||||
const { cleanFileName } = require('~/server/utils/files');
|
||||
const { getLogStores } = require('~/cache');
|
||||
const { logger } = require('~/config');
|
||||
const { fileAccess } = require('~/server/middleware/accessResources/fileAccess');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
@ -78,9 +79,9 @@ router.get('/agent/:agent_id', async (req, res) => {
|
|||
if (agent.author.toString() !== userId) {
|
||||
const hasEditPermission = await checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
requiredPermission: PERMISSION_BITS.EDIT,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
||||
if (!hasEditPermission) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const mongoose = require('mongoose');
|
|||
const { v4: uuidv4 } = require('uuid');
|
||||
const { createMethods } = require('@librechat/data-schemas');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||
const { createAgent } = require('~/models/Agent');
|
||||
const { createFile } = require('~/models/File');
|
||||
|
||||
|
|
@ -228,9 +229,9 @@ describe('File Routes - Delete with Agent Access', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: otherUserId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
|
|
@ -282,9 +283,9 @@ describe('File Routes - Delete with Agent Access', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: otherUserId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
|
|
@ -348,9 +349,9 @@ describe('File Routes - Delete with Agent Access', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: otherUserId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
|
|
@ -391,9 +392,9 @@ describe('File Routes - Delete with Agent Access', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: otherUserId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: authorId,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
const express = require('express');
|
||||
const { logger, PermissionBits } = require('@librechat/data-schemas');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { generateCheckAccess } = require('@librechat/api');
|
||||
const { Permissions, SystemRoles, PermissionTypes } = require('librechat-data-provider');
|
||||
const {
|
||||
getPrompt,
|
||||
getPrompts,
|
||||
savePrompt,
|
||||
deletePrompt,
|
||||
getPromptGroup,
|
||||
getPromptGroups,
|
||||
Permissions,
|
||||
SystemRoles,
|
||||
ResourceType,
|
||||
AccessRoleIds,
|
||||
PermissionTypes,
|
||||
PermissionBits,
|
||||
} = require('librechat-data-provider');
|
||||
const {
|
||||
makePromptProduction,
|
||||
getAllPromptGroups,
|
||||
updatePromptGroup,
|
||||
deletePromptGroup,
|
||||
createPromptGroup,
|
||||
getAllPromptGroups,
|
||||
// updatePromptLabels,
|
||||
makePromptProduction,
|
||||
getPromptGroups,
|
||||
getPromptGroup,
|
||||
deletePrompt,
|
||||
getPrompts,
|
||||
savePrompt,
|
||||
getPrompt,
|
||||
} = require('~/models/Prompt');
|
||||
const {
|
||||
canAccessPromptGroupResource,
|
||||
|
|
@ -22,10 +28,10 @@ const {
|
|||
requireJwtAuth,
|
||||
} = require('~/server/middleware');
|
||||
const {
|
||||
grantPermission,
|
||||
findPubliclyAccessibleResources,
|
||||
getEffectivePermissions,
|
||||
findAccessibleResources,
|
||||
findPubliclyAccessibleResources,
|
||||
grantPermission,
|
||||
} = require('~/server/services/PermissionService');
|
||||
const { getRoleByName } = require('~/models/Role');
|
||||
|
||||
|
|
@ -92,7 +98,7 @@ router.get('/all', async (req, res) => {
|
|||
// Get promptGroup IDs the user has VIEW access to via ACL
|
||||
const accessibleIds = await findAccessibleResources({
|
||||
userId,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
requiredPermissions: PermissionBits.VIEW,
|
||||
});
|
||||
|
||||
|
|
@ -123,13 +129,13 @@ router.get('/groups', async (req, res) => {
|
|||
// Get promptGroup IDs the user has VIEW access to via ACL
|
||||
const accessibleIds = await findAccessibleResources({
|
||||
userId,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
requiredPermissions: PermissionBits.VIEW,
|
||||
});
|
||||
|
||||
// Get publicly accessible promptGroups
|
||||
const publiclyAccessibleIds = await findPubliclyAccessibleResources({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
requiredPermissions: PermissionBits.VIEW,
|
||||
});
|
||||
|
||||
|
|
@ -185,9 +191,9 @@ const createNewPromptGroup = async (req, res) => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: req.user.id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: result.prompt.groupId,
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: req.user.id,
|
||||
});
|
||||
logger.debug(
|
||||
|
|
@ -327,7 +333,7 @@ router.get('/', async (req, res) => {
|
|||
if (groupId) {
|
||||
const permissions = await getEffectivePermissions({
|
||||
userId: req.user.id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: groupId,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
const request = require('supertest');
|
||||
const express = require('express');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const request = require('supertest');
|
||||
const mongoose = require('mongoose');
|
||||
const { ObjectId } = require('mongodb');
|
||||
const { SystemRoles } = require('librechat-data-provider');
|
||||
const { PermissionBits } = require('@librechat/data-schemas');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const {
|
||||
SystemRoles,
|
||||
ResourceType,
|
||||
AccessRoleIds,
|
||||
PermissionBits,
|
||||
} = require('librechat-data-provider');
|
||||
|
||||
// Mock modules before importing
|
||||
jest.mock('~/server/services/Config', () => ({
|
||||
|
|
@ -18,7 +22,6 @@ jest.mock('~/models/Role', () => ({
|
|||
|
||||
jest.mock('~/server/middleware', () => ({
|
||||
requireJwtAuth: (req, res, next) => next(),
|
||||
canAccessPromptResource: jest.requireActual('~/server/middleware').canAccessPromptResource,
|
||||
canAccessPromptViaGroup: jest.requireActual('~/server/middleware').canAccessPromptViaGroup,
|
||||
canAccessPromptGroupResource:
|
||||
jest.requireActual('~/server/middleware').canAccessPromptGroupResource,
|
||||
|
|
@ -90,21 +93,21 @@ async function setupTestData() {
|
|||
// Create access roles for promptGroups
|
||||
testRoles = {
|
||||
viewer: await AccessRole.create({
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
name: 'Viewer',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: PermissionBits.VIEW,
|
||||
}),
|
||||
editor: await AccessRole.create({
|
||||
accessRoleId: 'promptGroup_editor',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
name: 'Editor',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: PermissionBits.VIEW | PermissionBits.EDIT,
|
||||
}),
|
||||
owner: await AccessRole.create({
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
name: 'Owner',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits:
|
||||
PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
|
||||
}),
|
||||
|
|
@ -218,7 +221,7 @@ describe('Prompt Routes - ACL Permissions', () => {
|
|||
|
||||
// Check ACL entry was created
|
||||
const aclEntry = await AclEntry.findOne({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: response.body.prompt.groupId,
|
||||
principalType: 'user',
|
||||
principalId: testUsers.owner._id,
|
||||
|
|
@ -248,7 +251,7 @@ describe('Prompt Routes - ACL Permissions', () => {
|
|||
|
||||
// Check ACL entry was created for the promptGroup
|
||||
const aclEntry = await AclEntry.findOne({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: response.body.group._id,
|
||||
principalType: 'user',
|
||||
principalId: testUsers.owner._id,
|
||||
|
|
@ -293,9 +296,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testGroup._id,
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
|
|
@ -378,9 +381,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testGroup._id,
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
});
|
||||
|
|
@ -405,7 +408,7 @@ describe('Prompt Routes - ACL Permissions', () => {
|
|||
|
||||
// Verify ACL entries were removed
|
||||
const aclEntries = await AclEntry.find({
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testGroup._id,
|
||||
});
|
||||
expect(aclEntries).toHaveLength(0);
|
||||
|
|
@ -425,9 +428,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: testUsers.viewer._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testGroup._id,
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
grantedBy: testUsers.editor._id,
|
||||
});
|
||||
|
||||
|
|
@ -492,9 +495,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: testUsers.owner._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testGroup._id,
|
||||
accessRoleId: 'promptGroup_editor',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
|
|
@ -530,9 +533,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: testUsers.viewer._id,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: testGroup._id,
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
|
||||
|
|
@ -587,9 +590,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
|||
await grantPermission({
|
||||
principalType: 'public',
|
||||
principalId: null,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: publicGroup._id,
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
grantedBy: testUsers.owner._id,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const { logger } = require('@librechat/data-schemas');
|
||||
const { PERMISSION_BITS } = require('librechat-data-provider');
|
||||
const { PermissionBits, ResourceType } = require('librechat-data-provider');
|
||||
const { checkPermission } = require('~/server/services/PermissionService');
|
||||
const { getAgent } = require('~/models/Agent');
|
||||
|
||||
|
|
@ -32,9 +32,9 @@ const hasAccessToFilesViaAgent = async (userId, fileIds, agentId) => {
|
|||
// Check if user has at least VIEW permission on the agent
|
||||
const hasViewPermission = await checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
requiredPermission: PERMISSION_BITS.VIEW,
|
||||
requiredPermission: PermissionBits.VIEW,
|
||||
});
|
||||
|
||||
if (!hasViewPermission) {
|
||||
|
|
@ -44,9 +44,9 @@ const hasAccessToFilesViaAgent = async (userId, fileIds, agentId) => {
|
|||
// Check if user has EDIT permission (which would indicate collaborative access)
|
||||
const hasEditPermission = await checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
requiredPermission: PERMISSION_BITS.EDIT,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
});
|
||||
|
||||
// If user only has VIEW permission, they can't access files
|
||||
|
|
|
|||
|
|
@ -1,32 +1,45 @@
|
|||
const mongoose = require('mongoose');
|
||||
const { isEnabled } = require('@librechat/api');
|
||||
const { ResourceType } = require('librechat-data-provider');
|
||||
const { getTransactionSupport, logger } = require('@librechat/data-schemas');
|
||||
const { isEnabled } = require('~/server/utils');
|
||||
const {
|
||||
entraIdPrincipalFeatureEnabled,
|
||||
getUserEntraGroups,
|
||||
getUserOwnedEntraGroups,
|
||||
getUserEntraGroups,
|
||||
getGroupMembers,
|
||||
getGroupOwners,
|
||||
} = require('~/server/services/GraphApiService');
|
||||
const {
|
||||
findAccessibleResources: findAccessibleResourcesACL,
|
||||
getEffectivePermissions: getEffectivePermissionsACL,
|
||||
grantPermission: grantPermissionACL,
|
||||
findEntriesByPrincipalsAndResource,
|
||||
findGroupByExternalId,
|
||||
findRoleByIdentifier,
|
||||
getUserPrincipals,
|
||||
hasPermission,
|
||||
createGroup,
|
||||
createUser,
|
||||
updateUser,
|
||||
findUser,
|
||||
grantPermission: grantPermissionACL,
|
||||
findAccessibleResources: findAccessibleResourcesACL,
|
||||
hasPermission,
|
||||
getEffectivePermissions: getEffectivePermissionsACL,
|
||||
findEntriesByPrincipalsAndResource,
|
||||
} = require('~/models');
|
||||
const { AclEntry, AccessRole, Group } = require('~/db/models');
|
||||
|
||||
/** @type {boolean|null} */
|
||||
let transactionSupportCache = null;
|
||||
|
||||
/**
|
||||
* Validates that the resourceType is one of the supported enum values
|
||||
* @param {string} resourceType - The resource type to validate
|
||||
* @throws {Error} If resourceType is not valid
|
||||
*/
|
||||
const validateResourceType = (resourceType) => {
|
||||
const validTypes = Object.values(ResourceType);
|
||||
if (!validTypes.includes(resourceType)) {
|
||||
throw new Error(`Invalid resourceType: ${resourceType}. Valid types: ${validTypes.join(', ')}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @import { TPrincipal } from 'librechat-data-provider'
|
||||
*/
|
||||
|
|
@ -37,7 +50,7 @@ let transactionSupportCache = null;
|
|||
* @param {string|mongoose.Types.ObjectId|null} params.principalId - The ID of the principal (null for 'public')
|
||||
* @param {string} params.resourceType - Type of resource (e.g., 'agent')
|
||||
* @param {string|mongoose.Types.ObjectId} params.resourceId - The ID of the resource
|
||||
* @param {string} params.accessRoleId - The ID of the role (e.g., 'agent_viewer', 'agent_editor')
|
||||
* @param {string} params.accessRoleId - The ID of the role (e.g., AccessRoleIds.AGENT_VIEWER, AccessRoleIds.AGENT_EDITOR)
|
||||
* @param {string|mongoose.Types.ObjectId} params.grantedBy - User ID granting the permission
|
||||
* @param {mongoose.ClientSession} [params.session] - Optional MongoDB session for transactions
|
||||
* @returns {Promise<Object>} The created or updated ACL entry
|
||||
|
|
@ -68,6 +81,8 @@ const grantPermission = async ({
|
|||
throw new Error(`Invalid resource ID: ${resourceId}`);
|
||||
}
|
||||
|
||||
validateResourceType(resourceType);
|
||||
|
||||
// Get the role to determine permission bits
|
||||
const role = await findRoleByIdentifier(accessRoleId);
|
||||
if (!role) {
|
||||
|
|
@ -111,6 +126,8 @@ const checkPermission = async ({ userId, resourceType, resourceId, requiredPermi
|
|||
throw new Error('requiredPermission must be a positive number');
|
||||
}
|
||||
|
||||
validateResourceType(resourceType);
|
||||
|
||||
// Get all principals for the user (user + groups + public)
|
||||
const principals = await getUserPrincipals(userId);
|
||||
|
||||
|
|
@ -139,6 +156,8 @@ const checkPermission = async ({ userId, resourceType, resourceId, requiredPermi
|
|||
*/
|
||||
const getEffectivePermissions = async ({ userId, resourceType, resourceId }) => {
|
||||
try {
|
||||
validateResourceType(resourceType);
|
||||
|
||||
// Get all principals for the user (user + groups + public)
|
||||
const principals = await getUserPrincipals(userId);
|
||||
|
||||
|
|
@ -166,6 +185,8 @@ const findAccessibleResources = async ({ userId, resourceType, requiredPermissio
|
|||
throw new Error('requiredPermissions must be a positive number');
|
||||
}
|
||||
|
||||
validateResourceType(resourceType);
|
||||
|
||||
// Get all principals for the user (user + groups + public)
|
||||
const principalsList = await getUserPrincipals(userId);
|
||||
|
||||
|
|
@ -196,6 +217,8 @@ const findPubliclyAccessibleResources = async ({ resourceType, requiredPermissio
|
|||
throw new Error('requiredPermissions must be a positive number');
|
||||
}
|
||||
|
||||
validateResourceType(resourceType);
|
||||
|
||||
// Find all public ACL entries where the public principal has at least the required permission bits
|
||||
const entries = await AclEntry.find({
|
||||
principalType: 'public',
|
||||
|
|
@ -221,12 +244,9 @@ const findPubliclyAccessibleResources = async ({ resourceType, requiredPermissio
|
|||
* @returns {Promise<Array>} Array of role definitions
|
||||
*/
|
||||
const getAvailableRoles = async ({ resourceType }) => {
|
||||
try {
|
||||
return await AccessRole.find({ resourceType }).lean();
|
||||
} catch (error) {
|
||||
logger.error(`[PermissionService.getAvailableRoles] Error: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
validateResourceType(resourceType);
|
||||
|
||||
return await AccessRole.find({ resourceType }).lean();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -482,6 +502,8 @@ const hasPublicPermission = async ({ resourceType, resourceId, requiredPermissio
|
|||
throw new Error('requiredPermissions must be a positive number');
|
||||
}
|
||||
|
||||
validateResourceType(resourceType);
|
||||
|
||||
// Use public principal to check permissions
|
||||
const publicPrincipal = [{ principalType: 'public' }];
|
||||
|
||||
|
|
@ -707,14 +729,16 @@ const bulkUpdateResourcePermissions = async ({
|
|||
};
|
||||
|
||||
/**
|
||||
* Remove all permissions for a specific resource
|
||||
* @param {Object} params - Parameters for removing permissions
|
||||
* Remove all permissions for a resource (cleanup when resource is deleted)
|
||||
* @param {Object} params - Parameters for removing all permissions
|
||||
* @param {string} params.resourceType - Type of resource (e.g., 'agent', 'prompt')
|
||||
* @param {string|mongoose.Types.ObjectId} params.resourceId - The ID of the resource
|
||||
* @returns {Promise<Object>} Delete result
|
||||
* @returns {Promise<Object>} Result of the deletion operation
|
||||
*/
|
||||
const removeAllPermissions = async ({ resourceType, resourceId }) => {
|
||||
try {
|
||||
validateResourceType(resourceType);
|
||||
|
||||
if (!resourceId || !mongoose.Types.ObjectId.isValid(resourceId)) {
|
||||
throw new Error(`Invalid resource ID: ${resourceId}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const mongoose = require('mongoose');
|
||||
const { RoleBits } = require('@librechat/data-schemas');
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||
const {
|
||||
bulkUpdateResourcePermissions,
|
||||
getEffectivePermissions,
|
||||
|
|
@ -48,49 +49,49 @@ beforeEach(async () => {
|
|||
// Seed some roles for testing
|
||||
await AccessRole.create([
|
||||
{
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
name: 'Agent Viewer',
|
||||
description: 'Can view agents',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER, // VIEW permission
|
||||
},
|
||||
{
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
name: 'Agent Editor',
|
||||
description: 'Can edit agents',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.EDITOR, // VIEW + EDIT permissions
|
||||
},
|
||||
{
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
name: 'Agent Owner',
|
||||
description: 'Full control over agents',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.OWNER, // VIEW + EDIT + DELETE + SHARE permissions
|
||||
},
|
||||
{
|
||||
accessRoleId: 'project_viewer',
|
||||
accessRoleId: AccessRoleIds.PROJECT_VIEWER,
|
||||
name: 'Project Viewer',
|
||||
description: 'Can view projects',
|
||||
resourceType: 'project',
|
||||
permBits: RoleBits.VIEWER,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'project_editor',
|
||||
accessRoleId: AccessRoleIds.PROJECT_EDITOR,
|
||||
name: 'Project Editor',
|
||||
description: 'Can edit projects',
|
||||
resourceType: 'project',
|
||||
permBits: RoleBits.EDITOR,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'project_manager',
|
||||
accessRoleId: AccessRoleIds.PROJECT_MANAGER,
|
||||
name: 'Project Manager',
|
||||
description: 'Can manage projects',
|
||||
resourceType: 'project',
|
||||
permBits: RoleBits.MANAGER,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'project_owner',
|
||||
accessRoleId: AccessRoleIds.PROJECT_OWNER,
|
||||
name: 'Project Owner',
|
||||
description: 'Full control over projects',
|
||||
resourceType: 'project',
|
||||
|
|
@ -117,9 +118,9 @@ describe('PermissionService', () => {
|
|||
const entry = await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -131,7 +132,7 @@ describe('PermissionService', () => {
|
|||
expect(entry.resourceId.toString()).toBe(resourceId.toString());
|
||||
|
||||
// Get the role to verify the permission bits are correctly set
|
||||
const role = await findRoleByIdentifier('agent_viewer');
|
||||
const role = await findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER);
|
||||
expect(entry.permBits).toBe(role.permBits);
|
||||
expect(entry.roleId.toString()).toBe(role._id.toString());
|
||||
expect(entry.grantedBy.toString()).toBe(grantedById.toString());
|
||||
|
|
@ -142,9 +143,9 @@ describe('PermissionService', () => {
|
|||
const entry = await grantPermission({
|
||||
principalType: 'group',
|
||||
principalId: groupId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -154,7 +155,7 @@ describe('PermissionService', () => {
|
|||
expect(entry.principalModel).toBe('Group');
|
||||
|
||||
// Get the role to verify the permission bits are correctly set
|
||||
const role = await findRoleByIdentifier('agent_editor');
|
||||
const role = await findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR);
|
||||
expect(entry.permBits).toBe(role.permBits);
|
||||
expect(entry.roleId.toString()).toBe(role._id.toString());
|
||||
});
|
||||
|
|
@ -163,9 +164,9 @@ describe('PermissionService', () => {
|
|||
const entry = await grantPermission({
|
||||
principalType: 'public',
|
||||
principalId: null,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -175,7 +176,7 @@ describe('PermissionService', () => {
|
|||
expect(entry.principalModel).toBeUndefined();
|
||||
|
||||
// Get the role to verify the permission bits are correctly set
|
||||
const role = await findRoleByIdentifier('agent_viewer');
|
||||
const role = await findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER);
|
||||
expect(entry.permBits).toBe(role.permBits);
|
||||
expect(entry.roleId.toString()).toBe(role._id.toString());
|
||||
});
|
||||
|
|
@ -185,9 +186,9 @@ describe('PermissionService', () => {
|
|||
grantPermission({
|
||||
principalType: 'invalid',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
}),
|
||||
).rejects.toThrow('Invalid principal type: invalid');
|
||||
|
|
@ -198,9 +199,9 @@ describe('PermissionService', () => {
|
|||
grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: null,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
}),
|
||||
).rejects.toThrow('Principal ID is required for user and group principals');
|
||||
|
|
@ -211,7 +212,7 @@ describe('PermissionService', () => {
|
|||
grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'non_existent_role',
|
||||
grantedBy: grantedById,
|
||||
|
|
@ -224,9 +225,9 @@ describe('PermissionService', () => {
|
|||
grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'project_viewer', // Project role for agent resource
|
||||
accessRoleId: AccessRoleIds.PROJECT_VIEWER, // Project role for agent resource
|
||||
grantedBy: grantedById,
|
||||
}),
|
||||
).rejects.toThrow('Role project_viewer is for project resources, not agent');
|
||||
|
|
@ -237,9 +238,9 @@ describe('PermissionService', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -247,13 +248,13 @@ describe('PermissionService', () => {
|
|||
const updated = await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
const editorRole = await findRoleByIdentifier('agent_editor');
|
||||
const editorRole = await findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR);
|
||||
expect(updated.permBits).toBe(editorRole.permBits);
|
||||
expect(updated.roleId.toString()).toBe(editorRole._id.toString());
|
||||
|
||||
|
|
@ -261,7 +262,7 @@ describe('PermissionService', () => {
|
|||
const entries = await AclEntry.find({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
});
|
||||
expect(entries).toHaveLength(1);
|
||||
|
|
@ -279,9 +280,9 @@ describe('PermissionService', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -289,9 +290,9 @@ describe('PermissionService', () => {
|
|||
await grantPermission({
|
||||
principalType: 'group',
|
||||
principalId: groupId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: otherResourceId,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
});
|
||||
|
|
@ -302,7 +303,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const hasViewPermission = await checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||
});
|
||||
|
|
@ -312,7 +313,7 @@ describe('PermissionService', () => {
|
|||
// Check higher permission level that user doesn't have
|
||||
const hasEditPermission = await checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
requiredPermission: 3, // RoleBits.EDITOR = VIEW + EDIT
|
||||
});
|
||||
|
|
@ -330,7 +331,7 @@ describe('PermissionService', () => {
|
|||
// Check original resource (user has access)
|
||||
const hasViewOnOriginal = await checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||
});
|
||||
|
|
@ -340,7 +341,7 @@ describe('PermissionService', () => {
|
|||
// Check other resource (group has access)
|
||||
const hasViewOnOther = await checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: otherResourceId,
|
||||
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||
});
|
||||
|
|
@ -356,9 +357,9 @@ describe('PermissionService', () => {
|
|||
await grantPermission({
|
||||
principalType: 'public',
|
||||
principalId: null,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: publicResourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -371,7 +372,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const hasPublicAccess = await checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: publicResourceId,
|
||||
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||
});
|
||||
|
|
@ -385,7 +386,7 @@ describe('PermissionService', () => {
|
|||
await expect(
|
||||
checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
requiredPermission: 'invalid',
|
||||
}),
|
||||
|
|
@ -393,7 +394,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const nonExistentResource = await checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: new mongoose.Types.ObjectId(),
|
||||
requiredPermission: 1, // RoleBits.VIEWER
|
||||
});
|
||||
|
|
@ -406,7 +407,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const hasPermission = await checkPermission({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
requiredPermission: 1, // RoleBits.VIEWER
|
||||
});
|
||||
|
|
@ -424,18 +425,18 @@ describe('PermissionService', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
await grantPermission({
|
||||
principalType: 'group',
|
||||
principalId: groupId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -444,9 +445,9 @@ describe('PermissionService', () => {
|
|||
await grantPermission({
|
||||
principalType: 'public',
|
||||
principalId: null,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: publicResourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -459,7 +460,7 @@ describe('PermissionService', () => {
|
|||
principalId: userId,
|
||||
resourceType: 'project',
|
||||
resourceId: projectId,
|
||||
accessRoleId: 'project_viewer',
|
||||
accessRoleId: AccessRoleIds.PROJECT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -467,10 +468,10 @@ describe('PermissionService', () => {
|
|||
principalType: 'user',
|
||||
principalId: userId,
|
||||
principalModel: 'User',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: childResourceId,
|
||||
permBits: RoleBits.VIEWER,
|
||||
roleId: (await findRoleByIdentifier('agent_viewer'))._id,
|
||||
roleId: (await findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER))._id,
|
||||
grantedBy: grantedById,
|
||||
grantedAt: new Date(),
|
||||
inheritedFrom: projectId,
|
||||
|
|
@ -486,7 +487,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const effective = await getEffectivePermissions({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
});
|
||||
|
||||
|
|
@ -505,7 +506,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const effective = await getEffectivePermissions({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: childResourceId,
|
||||
});
|
||||
|
||||
|
|
@ -519,7 +520,7 @@ describe('PermissionService', () => {
|
|||
const nonExistentResource = new mongoose.Types.ObjectId();
|
||||
const effective = await getEffectivePermissions({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: nonExistentResource,
|
||||
});
|
||||
|
||||
|
|
@ -532,7 +533,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const effective = await getEffectivePermissions({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
});
|
||||
|
||||
|
|
@ -555,9 +556,9 @@ describe('PermissionService', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: resource1,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -565,9 +566,9 @@ describe('PermissionService', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: resource2,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
|
|
@ -575,9 +576,9 @@ describe('PermissionService', () => {
|
|||
await grantPermission({
|
||||
principalType: 'group',
|
||||
principalId: groupId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: resource3,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
});
|
||||
|
|
@ -588,7 +589,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const viewableResources = await findAccessibleResources({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermissions: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||
});
|
||||
|
||||
|
|
@ -602,7 +603,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const editableResources = await findAccessibleResources({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermissions: 3, // RoleBits.EDITOR = VIEW + EDIT
|
||||
});
|
||||
|
||||
|
|
@ -619,7 +620,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const viewableResources = await findAccessibleResources({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermissions: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||
});
|
||||
|
||||
|
|
@ -633,7 +634,7 @@ describe('PermissionService', () => {
|
|||
await expect(
|
||||
findAccessibleResources({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermissions: 'invalid',
|
||||
}),
|
||||
).rejects.toThrow('requiredPermissions must be a positive number');
|
||||
|
|
@ -652,7 +653,7 @@ describe('PermissionService', () => {
|
|||
|
||||
const resources = await findAccessibleResources({
|
||||
userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
requiredPermissions: 1, // RoleBits.VIEWER
|
||||
});
|
||||
|
||||
|
|
@ -663,12 +664,12 @@ describe('PermissionService', () => {
|
|||
describe('getAvailableRoles', () => {
|
||||
test('should get all roles for a resource type', async () => {
|
||||
const roles = await getAvailableRoles({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
});
|
||||
|
||||
expect(roles).toHaveLength(3);
|
||||
expect(roles.map((r) => r.accessRoleId).sort()).toEqual(
|
||||
['agent_editor', 'agent_owner', 'agent_viewer'].sort(),
|
||||
[AccessRoleIds.AGENT_EDITOR, AccessRoleIds.AGENT_OWNER, AccessRoleIds.AGENT_VIEWER].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -689,27 +690,27 @@ describe('PermissionService', () => {
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
await grantPermission({
|
||||
principalType: 'group',
|
||||
principalId: groupId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
|
||||
await grantPermission({
|
||||
principalType: 'public',
|
||||
principalId: null,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
grantedBy: grantedById,
|
||||
});
|
||||
});
|
||||
|
|
@ -720,22 +721,22 @@ describe('PermissionService', () => {
|
|||
{
|
||||
type: 'user',
|
||||
id: userId,
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
},
|
||||
{
|
||||
type: 'user',
|
||||
id: otherUserId,
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
id: groupId,
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
},
|
||||
];
|
||||
|
||||
const results = await bulkUpdateResourcePermissions({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: newResourceId,
|
||||
updatedPrincipals,
|
||||
grantedBy: grantedById,
|
||||
|
|
@ -748,7 +749,7 @@ describe('PermissionService', () => {
|
|||
|
||||
// Verify permissions were created
|
||||
const aclEntries = await AclEntry.find({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: newResourceId,
|
||||
});
|
||||
expect(aclEntries).toHaveLength(3);
|
||||
|
|
@ -759,21 +760,21 @@ describe('PermissionService', () => {
|
|||
{
|
||||
type: 'user',
|
||||
id: userId,
|
||||
accessRoleId: 'agent_editor', // Upgrade from viewer to editor
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR, // Upgrade from viewer to editor
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
id: groupId,
|
||||
accessRoleId: 'agent_owner', // Upgrade from editor to owner
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER, // Upgrade from editor to owner
|
||||
},
|
||||
{
|
||||
type: 'public',
|
||||
accessRoleId: 'agent_viewer', // Keep same role
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER, // Keep same role
|
||||
},
|
||||
];
|
||||
|
||||
const results = await bulkUpdateResourcePermissions({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
updatedPrincipals,
|
||||
grantedBy: grantedById,
|
||||
|
|
@ -789,18 +790,18 @@ describe('PermissionService', () => {
|
|||
const userEntry = await AclEntry.findOne({
|
||||
principalType: 'user',
|
||||
principalId: userId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
}).populate('roleId', 'accessRoleId');
|
||||
expect(userEntry.roleId.accessRoleId).toBe('agent_editor');
|
||||
expect(userEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR);
|
||||
|
||||
const groupEntry = await AclEntry.findOne({
|
||||
principalType: 'group',
|
||||
principalId: groupId,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
}).populate('roleId', 'accessRoleId');
|
||||
expect(groupEntry.roleId.accessRoleId).toBe('agent_owner');
|
||||
expect(groupEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_OWNER);
|
||||
});
|
||||
|
||||
test('should revoke specified permissions', async () => {
|
||||
|
|
@ -815,7 +816,7 @@ describe('PermissionService', () => {
|
|||
];
|
||||
|
||||
const results = await bulkUpdateResourcePermissions({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
revokedPrincipals,
|
||||
grantedBy: grantedById,
|
||||
|
|
@ -828,7 +829,7 @@ describe('PermissionService', () => {
|
|||
|
||||
// Verify only user permission remains
|
||||
const remainingEntries = await AclEntry.find({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
});
|
||||
expect(remainingEntries).toHaveLength(1);
|
||||
|
|
@ -841,12 +842,12 @@ describe('PermissionService', () => {
|
|||
{
|
||||
type: 'user',
|
||||
id: userId,
|
||||
accessRoleId: 'agent_owner', // Update existing
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER, // Update existing
|
||||
},
|
||||
{
|
||||
type: 'user',
|
||||
id: otherUserId,
|
||||
accessRoleId: 'agent_viewer', // New permission
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER, // New permission
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -861,7 +862,7 @@ describe('PermissionService', () => {
|
|||
];
|
||||
|
||||
const results = await bulkUpdateResourcePermissions({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
updatedPrincipals,
|
||||
revokedPrincipals,
|
||||
|
|
@ -875,19 +876,19 @@ describe('PermissionService', () => {
|
|||
|
||||
// Verify final state
|
||||
const finalEntries = await AclEntry.find({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
}).populate('roleId', 'accessRoleId');
|
||||
|
||||
expect(finalEntries).toHaveLength(2);
|
||||
|
||||
const userEntry = finalEntries.find((e) => e.principalId.toString() === userId.toString());
|
||||
expect(userEntry.roleId.accessRoleId).toBe('agent_owner');
|
||||
expect(userEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_OWNER);
|
||||
|
||||
const otherUserEntry = finalEntries.find(
|
||||
(e) => e.principalId.toString() === otherUserId.toString(),
|
||||
);
|
||||
expect(otherUserEntry.roleId.accessRoleId).toBe('agent_viewer');
|
||||
expect(otherUserEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_VIEWER);
|
||||
});
|
||||
|
||||
test('should handle errors for invalid roles gracefully', async () => {
|
||||
|
|
@ -895,7 +896,7 @@ describe('PermissionService', () => {
|
|||
{
|
||||
type: 'user',
|
||||
id: userId,
|
||||
accessRoleId: 'agent_viewer', // Valid
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER, // Valid
|
||||
},
|
||||
{
|
||||
type: 'user',
|
||||
|
|
@ -905,12 +906,12 @@ describe('PermissionService', () => {
|
|||
{
|
||||
type: 'group',
|
||||
id: groupId,
|
||||
accessRoleId: 'project_viewer', // Wrong resource type
|
||||
accessRoleId: AccessRoleIds.PROJECT_VIEWER, // Wrong resource type
|
||||
},
|
||||
];
|
||||
|
||||
const results = await bulkUpdateResourcePermissions({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
updatedPrincipals,
|
||||
grantedBy: grantedById,
|
||||
|
|
@ -928,7 +929,7 @@ describe('PermissionService', () => {
|
|||
|
||||
test('should handle empty arrays (no operations)', async () => {
|
||||
const results = await bulkUpdateResourcePermissions({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
updatedPrincipals: [],
|
||||
revokedPrincipals: [],
|
||||
|
|
@ -942,7 +943,7 @@ describe('PermissionService', () => {
|
|||
|
||||
// Verify no changes to existing permissions (since no operations were performed)
|
||||
const remainingEntries = await AclEntry.find({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
});
|
||||
expect(remainingEntries).toHaveLength(3); // Original permissions still exist
|
||||
|
|
@ -951,7 +952,7 @@ describe('PermissionService', () => {
|
|||
test('should throw error for invalid updatedPrincipals array', async () => {
|
||||
await expect(
|
||||
bulkUpdateResourcePermissions({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
updatedPrincipals: 'not an array',
|
||||
grantedBy: grantedById,
|
||||
|
|
@ -962,7 +963,7 @@ describe('PermissionService', () => {
|
|||
test('should throw error for invalid resource ID', async () => {
|
||||
await expect(
|
||||
bulkUpdateResourcePermissions({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: 'invalid-id',
|
||||
permissions: [],
|
||||
grantedBy: grantedById,
|
||||
|
|
@ -974,12 +975,12 @@ describe('PermissionService', () => {
|
|||
const updatedPrincipals = [
|
||||
{
|
||||
type: 'public',
|
||||
accessRoleId: 'agent_editor', // Update public permission
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR, // Update public permission
|
||||
},
|
||||
{
|
||||
type: 'user',
|
||||
id: otherUserId,
|
||||
accessRoleId: 'agent_viewer', // New user permission
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER, // New user permission
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -995,7 +996,7 @@ describe('PermissionService', () => {
|
|||
];
|
||||
|
||||
const results = await bulkUpdateResourcePermissions({
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
updatedPrincipals,
|
||||
revokedPrincipals,
|
||||
|
|
@ -1010,12 +1011,12 @@ describe('PermissionService', () => {
|
|||
// Verify public permission was updated
|
||||
const publicEntry = await AclEntry.findOne({
|
||||
principalType: 'public',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId,
|
||||
}).populate('roleId', 'accessRoleId');
|
||||
|
||||
expect(publicEntry).toBeDefined();
|
||||
expect(publicEntry.roleId.accessRoleId).toBe('agent_editor');
|
||||
expect(publicEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR);
|
||||
});
|
||||
|
||||
test('should work with different resource types', async () => {
|
||||
|
|
@ -1025,12 +1026,12 @@ describe('PermissionService', () => {
|
|||
{
|
||||
type: 'user',
|
||||
id: userId,
|
||||
accessRoleId: 'project_viewer',
|
||||
accessRoleId: AccessRoleIds.PROJECT_VIEWER,
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
id: groupId,
|
||||
accessRoleId: 'project_editor',
|
||||
accessRoleId: AccessRoleIds.PROJECT_EDITOR,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1072,6 +1072,19 @@
|
|||
* @memberof typedefs
|
||||
*/
|
||||
|
||||
/** Permissions */
|
||||
/**
|
||||
* @exports TUpdateResourcePermissionsRequest
|
||||
* @typedef {import('librechat-data-provider').TUpdateResourcePermissionsRequest} TUpdateResourcePermissionsRequest
|
||||
* @memberof typedefs
|
||||
*/
|
||||
|
||||
/**
|
||||
* @exports TUpdateResourcePermissionsResponse
|
||||
* @typedef {import('librechat-data-provider').TUpdateResourcePermissionsResponse} TUpdateResourcePermissionsResponse
|
||||
* @memberof typedefs
|
||||
*/
|
||||
|
||||
/**
|
||||
* @exports JsonSchemaType
|
||||
* @typedef {import('@librechat/api').JsonSchemaType} JsonSchemaType
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue