mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02: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
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import type t from 'librechat-data-provider';
|
||||
|
||||
import useLocalize from '~/hooks/useLocalize';
|
||||
import { renderAgentAvatar, getContactDisplayName } from '~/utils/agents';
|
||||
import { cn } from '~/utils';
|
||||
import { cn, renderAgentAvatar, getContactDisplayName } from '~/utils';
|
||||
|
||||
interface AgentCardProps {
|
||||
agent: t.Agent; // The agent data to display
|
|
@ -6,7 +6,7 @@ import {
|
|||
QueryKeys,
|
||||
Constants,
|
||||
EModelEndpoint,
|
||||
PERMISSION_BITS,
|
||||
PermissionBits,
|
||||
LocalStorageKeys,
|
||||
AgentListResponse,
|
||||
} from 'librechat-data-provider';
|
||||
|
@ -45,7 +45,7 @@ const AgentDetail: React.FC<AgentDetailProps> = ({ agent, isOpen, onClose }) =>
|
|||
*/
|
||||
const handleStartChat = () => {
|
||||
if (agent) {
|
||||
const keys = [QueryKeys.agents, { requiredPermission: PERMISSION_BITS.EDIT }];
|
||||
const keys = [QueryKeys.agents, { requiredPermission: PermissionBits.EDIT }];
|
||||
const listResp = queryClient.getQueryData<AgentListResponse>(keys);
|
||||
if (listResp != null) {
|
||||
if (!listResp.data.some((a) => a.id === agent.id)) {
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { Button, Spinner } from '@librechat/client';
|
||||
import { PERMISSION_BITS } from 'librechat-data-provider';
|
||||
import { PermissionBits } from 'librechat-data-provider';
|
||||
import type t from 'librechat-data-provider';
|
||||
import { useMarketplaceAgentsInfiniteQuery } from '~/data-provider/Agents';
|
||||
import { useAgentCategories, useLocalize } from '~/hooks';
|
||||
|
@ -33,7 +33,7 @@ const AgentGrid: React.FC<AgentGridProps> = ({ category, searchQuery, onSelectAg
|
|||
limit: number;
|
||||
promoted?: 0 | 1;
|
||||
} = {
|
||||
requiredPermission: PERMISSION_BITS.VIEW, // View permission for marketplace viewing
|
||||
requiredPermission: PermissionBits.VIEW, // View permission for marketplace viewing
|
||||
limit: 6,
|
||||
};
|
||||
|
|
@ -7,7 +7,7 @@ import {
|
|||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from '@librechat/client';
|
||||
import { PERMISSION_BITS } from 'librechat-data-provider';
|
||||
import { PermissionBits } from 'librechat-data-provider';
|
||||
import type { TPromptGroup } from 'librechat-data-provider';
|
||||
import { useLocalize, useSubmitMessage, useCustomLink, useResourcePermissions } from '~/hooks';
|
||||
import VariableDialog from '~/components/Prompts/Groups/VariableDialog';
|
||||
|
@ -35,7 +35,7 @@ function ChatGroupItem({
|
|||
|
||||
// Check permissions for the promptGroup
|
||||
const { hasPermission } = useResourcePermissions('promptGroup', group._id || '');
|
||||
const canEdit = hasPermission(PERMISSION_BITS.EDIT);
|
||||
const canEdit = hasPermission(PermissionBits.EDIT);
|
||||
|
||||
const onCardClick: React.MouseEventHandler<HTMLButtonElement> = () => {
|
||||
const text = group.productionPrompt?.prompt;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { memo, useState, useRef, useMemo, useCallback, KeyboardEvent } from 'react';
|
||||
import { EarthIcon, Pen } from 'lucide-react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { PERMISSION_BITS, type TPromptGroup } from 'librechat-data-provider';
|
||||
import { PermissionBits, type TPromptGroup } from 'librechat-data-provider';
|
||||
import {
|
||||
Input,
|
||||
Label,
|
||||
|
@ -30,8 +30,8 @@ function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps
|
|||
const [nameInputValue, setNameInputValue] = useState(group.name);
|
||||
|
||||
const { hasPermission } = useResourcePermissions('promptGroup', group._id || '');
|
||||
const canEdit = hasPermission(PERMISSION_BITS.EDIT);
|
||||
const canDelete = hasPermission(PERMISSION_BITS.DELETE);
|
||||
const canEdit = hasPermission(PermissionBits.EDIT);
|
||||
const canDelete = hasPermission(PermissionBits.DELETE);
|
||||
|
||||
const isGlobalGroup = useMemo(
|
||||
() => instanceProjectId && group.projectIds?.includes(instanceProjectId),
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Menu, Rocket } from 'lucide-react';
|
|||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import { useParams, useOutletContext } from 'react-router-dom';
|
||||
import { Button, Skeleton, useToastContext } from '@librechat/client';
|
||||
import { Permissions, PermissionTypes, PERMISSION_BITS } from 'librechat-data-provider';
|
||||
import { Permissions, PermissionTypes, PermissionBits } from 'librechat-data-provider';
|
||||
import type { TCreatePrompt, TPrompt, TPromptGroup } from 'librechat-data-provider';
|
||||
import {
|
||||
useGetPrompts,
|
||||
|
@ -186,8 +186,8 @@ const PromptForm = () => {
|
|||
group?._id || '',
|
||||
);
|
||||
|
||||
const canEdit = hasPermission(PERMISSION_BITS.EDIT);
|
||||
const canView = hasPermission(PERMISSION_BITS.VIEW);
|
||||
const canEdit = hasPermission(PermissionBits.EDIT);
|
||||
const canView = hasPermission(PermissionBits.VIEW);
|
||||
|
||||
const methods = useForm({
|
||||
defaultValues: {
|
||||
|
|
|
@ -3,8 +3,9 @@ import { Share2Icon } from 'lucide-react';
|
|||
import {
|
||||
SystemRoles,
|
||||
Permissions,
|
||||
ResourceType,
|
||||
PermissionTypes,
|
||||
PERMISSION_BITS,
|
||||
PermissionBits,
|
||||
} from 'librechat-data-provider';
|
||||
import { Button } from '@librechat/client';
|
||||
import type { TPromptGroup } from 'librechat-data-provider';
|
||||
|
@ -25,7 +26,7 @@ const SharePrompt = React.memo(
|
|||
// The query will be disabled if groupId is empty
|
||||
const groupId = group?._id || '';
|
||||
const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions(
|
||||
'promptGroup',
|
||||
ResourceType.PROMPTGROUP,
|
||||
groupId,
|
||||
);
|
||||
|
||||
|
@ -34,7 +35,7 @@ const SharePrompt = React.memo(
|
|||
return null;
|
||||
}
|
||||
|
||||
const canShareThisPrompt = hasPermission(PERMISSION_BITS.SHARE);
|
||||
const canShareThisPrompt = hasPermission(PermissionBits.SHARE);
|
||||
|
||||
const shouldShowShareButton =
|
||||
(group.author === user?.id || user?.role === SystemRoles.ADMIN || canShareThisPrompt) &&
|
||||
|
@ -49,7 +50,7 @@ const SharePrompt = React.memo(
|
|||
<GenericGrantAccessDialog
|
||||
resourceDbId={groupId}
|
||||
resourceName={group.name}
|
||||
resourceType="promptGroup"
|
||||
resourceType={ResourceType.PROMPTGROUP}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Button
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import * as Ariakit from '@ariakit/react';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { DropdownPopup } from '@librechat/client';
|
||||
import { ACCESS_ROLE_IDS } from 'librechat-data-provider';
|
||||
import { AccessRoleIds, ResourceType } from 'librechat-data-provider';
|
||||
import { useGetAccessRolesQuery } from 'librechat-data-provider/react-query';
|
||||
import type { AccessRole } from 'librechat-data-provider';
|
||||
import type * as t from '~/common';
|
||||
|
@ -10,15 +10,15 @@ import { cn, getRoleLocalizationKeys } from '~/utils';
|
|||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface AccessRolesPickerProps {
|
||||
resourceType?: string;
|
||||
selectedRoleId?: ACCESS_ROLE_IDS;
|
||||
onRoleChange: (roleId: ACCESS_ROLE_IDS) => void;
|
||||
resourceType?: ResourceType;
|
||||
selectedRoleId?: AccessRoleIds;
|
||||
onRoleChange: (roleId: AccessRoleIds) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function AccessRolesPicker({
|
||||
resourceType = 'agent',
|
||||
selectedRoleId = ACCESS_ROLE_IDS.AGENT_VIEWER,
|
||||
resourceType = ResourceType.AGENT,
|
||||
selectedRoleId = AccessRoleIds.AGENT_VIEWER,
|
||||
onRoleChange,
|
||||
className = '',
|
||||
}: AccessRolesPickerProps) {
|
||||
|
@ -27,7 +27,7 @@ export default function AccessRolesPicker({
|
|||
const { data: accessRoles, isLoading: rolesLoading } = useGetAccessRolesQuery(resourceType);
|
||||
|
||||
/** Helper function to get localized role name and description */
|
||||
const getLocalizedRoleInfo = (roleId: ACCESS_ROLE_IDS) => {
|
||||
const getLocalizedRoleInfo = (roleId: AccessRoleIds) => {
|
||||
const keys = getRoleLocalizationKeys(roleId);
|
||||
return {
|
||||
name: localize(keys.name),
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { AccessRoleIds, ResourceType } from 'librechat-data-provider';
|
||||
import { Share2Icon, Users, Loader, Shield, Link, CopyCheck } from 'lucide-react';
|
||||
import {
|
||||
Button,
|
||||
|
@ -10,13 +11,17 @@ import {
|
|||
useToastContext,
|
||||
} from '@librechat/client';
|
||||
import type { TPrincipal } from 'librechat-data-provider';
|
||||
import { useLocalize, useCopyToClipboard } from '~/hooks';
|
||||
import { usePeoplePickerPermissions, useResourcePermissionState } from '~/hooks/Sharing';
|
||||
import {
|
||||
usePeoplePickerPermissions,
|
||||
useResourcePermissionState,
|
||||
useCopyToClipboard,
|
||||
useLocalize,
|
||||
} from '~/hooks';
|
||||
import GenericManagePermissionsDialog from './GenericManagePermissionsDialog';
|
||||
import PeoplePicker from '../SidePanel/Agents/Sharing/PeoplePicker/PeoplePicker';
|
||||
import AccessRolesPicker from '../SidePanel/Agents/Sharing/AccessRolesPicker';
|
||||
import PublicSharingToggle from './PublicSharingToggle';
|
||||
import AccessRolesPicker from './AccessRolesPicker';
|
||||
import { cn, removeFocusOutlines } from '~/utils';
|
||||
import { PeoplePicker } from './PeoplePicker';
|
||||
|
||||
export default function GenericGrantAccessDialog({
|
||||
resourceName,
|
||||
|
@ -30,8 +35,8 @@ export default function GenericGrantAccessDialog({
|
|||
resourceDbId?: string | null;
|
||||
resourceId?: string | null;
|
||||
resourceName?: string;
|
||||
resourceType: string;
|
||||
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole: string) => void;
|
||||
resourceType: ResourceType;
|
||||
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole: AccessRoleIds) => void;
|
||||
disabled?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
|
@ -55,8 +60,8 @@ export default function GenericGrantAccessDialog({
|
|||
} = useResourcePermissionState(resourceType, resourceDbId, isModalOpen);
|
||||
|
||||
const [newShares, setNewShares] = useState<TPrincipal[]>([]);
|
||||
const [defaultPermissionId, setDefaultPermissionId] = useState<string>(
|
||||
config?.defaultViewerRoleId ?? '',
|
||||
const [defaultPermissionId, setDefaultPermissionId] = useState<AccessRoleIds | undefined>(
|
||||
config?.defaultViewerRoleId,
|
||||
);
|
||||
|
||||
const resourceUrl = config?.getResourceUrl ? config?.getResourceUrl(resourceId || '') : '';
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { TPrincipal } from 'librechat-data-provider';
|
||||
import { Settings, Users, Loader, UserCheck, Trash2, Shield } from 'lucide-react';
|
||||
import { useGetAccessRolesQuery } from 'librechat-data-provider/react-query';
|
||||
import { Settings, Users, Loader, UserCheck, Trash2, Shield } from 'lucide-react';
|
||||
import {
|
||||
Button,
|
||||
OGDialog,
|
||||
|
@ -11,9 +10,10 @@ import {
|
|||
OGDialogTrigger,
|
||||
useToastContext,
|
||||
} from '@librechat/client';
|
||||
import SelectedPrincipalsList from '../SidePanel/Agents/Sharing/PeoplePicker/SelectedPrincipalsList';
|
||||
import type { TPrincipal, ResourceType, AccessRoleIds } from 'librechat-data-provider';
|
||||
import { useResourcePermissionState } from '~/hooks/Sharing';
|
||||
import PublicSharingToggle from './PublicSharingToggle';
|
||||
import { SelectedPrincipalsList } from './PeoplePicker';
|
||||
import { cn, removeFocusOutlines } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
|
@ -26,8 +26,12 @@ export default function GenericManagePermissionsDialog({
|
|||
}: {
|
||||
resourceDbId: string;
|
||||
resourceName?: string;
|
||||
resourceType: string;
|
||||
onUpdatePermissions?: (shares: TPrincipal[], isPublic: boolean, publicRole: string) => void;
|
||||
resourceType: ResourceType;
|
||||
onUpdatePermissions?: (
|
||||
shares: TPrincipal[],
|
||||
isPublic: boolean,
|
||||
publicRole: AccessRoleIds,
|
||||
) => void;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { ACCESS_ROLE_IDS, PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||
import { Share2Icon, Users, Loader, Shield, Link, CopyCheck } from 'lucide-react';
|
||||
import { Permissions, ResourceType, PermissionTypes, AccessRoleIds } from 'librechat-data-provider';
|
||||
import {
|
||||
useGetResourcePermissionsQuery,
|
||||
useUpdateResourcePermissionsMutation,
|
||||
|
@ -18,22 +18,22 @@ import type { TPrincipal } from 'librechat-data-provider';
|
|||
import { useLocalize, useCopyToClipboard, useHasAccess } from '~/hooks';
|
||||
import ManagePermissionsDialog from './ManagePermissionsDialog';
|
||||
import PublicSharingToggle from './PublicSharingToggle';
|
||||
import PeoplePicker from './PeoplePicker/PeoplePicker';
|
||||
import AccessRolesPicker from './AccessRolesPicker';
|
||||
import { cn, removeFocusOutlines } from '~/utils';
|
||||
import { PeoplePicker } from './PeoplePicker';
|
||||
|
||||
export default function GrantAccessDialog({
|
||||
agentName,
|
||||
onGrantAccess,
|
||||
resourceType = 'agent',
|
||||
resourceType = ResourceType.AGENT,
|
||||
agentDbId,
|
||||
agentId,
|
||||
}: {
|
||||
agentDbId?: string | null;
|
||||
agentId?: string | null;
|
||||
agentName?: string;
|
||||
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole: string) => void;
|
||||
resourceType?: string;
|
||||
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole: AccessRoleIds) => void;
|
||||
resourceType?: ResourceType;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
|
@ -73,7 +73,7 @@ export default function GrantAccessDialog({
|
|||
|
||||
const [newShares, setNewShares] = useState<TPrincipal[]>([]);
|
||||
const [defaultPermissionId, setDefaultPermissionId] = useState<string>(
|
||||
ACCESS_ROLE_IDS.AGENT_VIEWER,
|
||||
AccessRoleIds.AGENT_VIEWER,
|
||||
);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isCopying, setIsCopying] = useState(false);
|
||||
|
@ -94,10 +94,10 @@ export default function GrantAccessDialog({
|
|||
})) || [];
|
||||
|
||||
const currentIsPublic = permissionsData?.public ?? false;
|
||||
const currentPublicRole = permissionsData?.publicAccessRoleId || ACCESS_ROLE_IDS.AGENT_VIEWER;
|
||||
const currentPublicRole = permissionsData?.publicAccessRoleId || AccessRoleIds.AGENT_VIEWER;
|
||||
|
||||
const [isPublic, setIsPublic] = useState(false);
|
||||
const [publicRole, setPublicRole] = useState<string>(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
||||
const [publicRole, setPublicRole] = useState<AccessRoleIds>(AccessRoleIds.AGENT_VIEWER);
|
||||
|
||||
useEffect(() => {
|
||||
if (permissionsData && isModalOpen) {
|
||||
|
@ -140,9 +140,9 @@ export default function GrantAccessDialog({
|
|||
});
|
||||
|
||||
setNewShares([]);
|
||||
setDefaultPermissionId(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
||||
setDefaultPermissionId(AccessRoleIds.AGENT_VIEWER);
|
||||
setIsPublic(false);
|
||||
setPublicRole(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
||||
setPublicRole(AccessRoleIds.AGENT_VIEWER);
|
||||
setIsModalOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Error granting access:', error);
|
||||
|
@ -155,9 +155,9 @@ export default function GrantAccessDialog({
|
|||
|
||||
const handleCancel = () => {
|
||||
setNewShares([]);
|
||||
setDefaultPermissionId(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
||||
setDefaultPermissionId(AccessRoleIds.AGENT_VIEWER);
|
||||
setIsPublic(false);
|
||||
setPublicRole(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
||||
setPublicRole(AccessRoleIds.AGENT_VIEWER);
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { ACCESS_ROLE_IDS, TPrincipal } from 'librechat-data-provider';
|
||||
import { AccessRoleIds, ResourceType } from 'librechat-data-provider';
|
||||
import { Settings, Users, Loader, UserCheck, Trash2, Shield } from 'lucide-react';
|
||||
import {
|
||||
useGetAccessRolesQuery,
|
||||
useGetResourcePermissionsQuery,
|
||||
useUpdateResourcePermissionsMutation,
|
||||
} from 'librechat-data-provider/react-query';
|
||||
import type { TPrincipal } from 'librechat-data-provider';
|
||||
import {
|
||||
Button,
|
||||
OGDialog,
|
||||
|
@ -15,21 +16,25 @@ import {
|
|||
OGDialogTrigger,
|
||||
useToastContext,
|
||||
} from '@librechat/client';
|
||||
import SelectedPrincipalsList from './PeoplePicker/SelectedPrincipalsList';
|
||||
import { SelectedPrincipalsList } from './PeoplePicker';
|
||||
import PublicSharingToggle from './PublicSharingToggle';
|
||||
import { cn, removeFocusOutlines } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function ManagePermissionsDialog({
|
||||
agentDbId,
|
||||
agentName,
|
||||
resourceType = 'agent',
|
||||
resourceType = ResourceType.AGENT,
|
||||
agentDbId,
|
||||
onUpdatePermissions,
|
||||
}: {
|
||||
agentDbId: string;
|
||||
agentName?: string;
|
||||
resourceType?: string;
|
||||
onUpdatePermissions?: (shares: TPrincipal[], isPublic: boolean, publicRole: string) => void;
|
||||
resourceType?: ResourceType;
|
||||
onUpdatePermissions?: (
|
||||
shares: TPrincipal[],
|
||||
isPublic: boolean,
|
||||
publicRole: AccessRoleIds,
|
||||
) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
|
@ -50,20 +55,22 @@ export default function ManagePermissionsDialog({
|
|||
|
||||
const [managedShares, setManagedShares] = useState<TPrincipal[]>([]);
|
||||
const [managedIsPublic, setManagedIsPublic] = useState(false);
|
||||
const [managedPublicRole, setManagedPublicRole] = useState<string>(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
||||
const [managedPublicRole, setManagedPublicRole] = useState<AccessRoleIds>(
|
||||
AccessRoleIds.AGENT_VIEWER,
|
||||
);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
|
||||
const currentShares: TPrincipal[] = permissionsData?.principals || [];
|
||||
|
||||
const isPublic = permissionsData?.public || false;
|
||||
const publicRole = permissionsData?.publicAccessRoleId || ACCESS_ROLE_IDS.AGENT_VIEWER;
|
||||
const publicRole = permissionsData?.publicAccessRoleId || AccessRoleIds.AGENT_VIEWER;
|
||||
|
||||
useEffect(() => {
|
||||
if (permissionsData) {
|
||||
const shares = permissionsData.principals || [];
|
||||
const isPublicValue = permissionsData.public || false;
|
||||
const publicRoleValue = permissionsData.publicAccessRoleId || ACCESS_ROLE_IDS.AGENT_VIEWER;
|
||||
const publicRoleValue = permissionsData.publicAccessRoleId || AccessRoleIds.AGENT_VIEWER;
|
||||
|
||||
setManagedShares(shares);
|
||||
setManagedIsPublic(isPublicValue);
|
||||
|
@ -85,7 +92,7 @@ export default function ManagePermissionsDialog({
|
|||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const handleRoleChange = (idOnTheSource: string, newRole: string) => {
|
||||
const handleRoleChange = (idOnTheSource: string, newRole: AccessRoleIds) => {
|
||||
setManagedShares(
|
||||
managedShares.map((s) =>
|
||||
s.idOnTheSource === idOnTheSource ? { ...s, accessRoleId: newRole } : s,
|
||||
|
@ -160,10 +167,10 @@ export default function ManagePermissionsDialog({
|
|||
setManagedIsPublic(isPublic);
|
||||
setHasChanges(true);
|
||||
if (!isPublic) {
|
||||
setManagedPublicRole(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
||||
setManagedPublicRole(AccessRoleIds.AGENT_VIEWER);
|
||||
}
|
||||
};
|
||||
const handlePublicRoleChange = (role: string) => {
|
||||
const handlePublicRoleChange = (role: AccessRoleIds) => {
|
||||
setManagedPublicRole(role);
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
@ -172,8 +179,8 @@ export default function ManagePermissionsDialog({
|
|||
|
||||
/** Check if there's at least one owner (user, group, or public with owner role) */
|
||||
const hasAtLeastOneOwner =
|
||||
managedShares.some((share) => share.accessRoleId === ACCESS_ROLE_IDS.AGENT_OWNER) ||
|
||||
(managedIsPublic && managedPublicRole === ACCESS_ROLE_IDS.AGENT_OWNER);
|
||||
managedShares.some((share) => share.accessRoleId === AccessRoleIds.AGENT_OWNER) ||
|
||||
(managedIsPublic && managedPublicRole === AccessRoleIds.AGENT_OWNER);
|
||||
|
||||
let peopleLabel = localize('com_ui_people');
|
||||
if (managedShares.length === 1) {
|
|
@ -3,7 +3,7 @@ import type { TPrincipal, PrincipalSearchParams } from 'librechat-data-provider'
|
|||
import { useSearchPrincipalsQuery } from 'librechat-data-provider/react-query';
|
||||
import PeoplePickerSearchItem from './PeoplePickerSearchItem';
|
||||
import SelectedPrincipalsList from './SelectedPrincipalsList';
|
||||
import { SearchPicker } from '~/components/ui/SearchPicker';
|
||||
import { SearchPicker } from './SearchPicker';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface PeoplePickerProps {
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import * as Ariakit from '@ariakit/react';
|
||||
import { Spinner, Skeleton } from '@librechat/client';
|
||||
|
@ -36,10 +37,31 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
|
|||
const localize = useLocalize();
|
||||
const [_open, setOpen] = React.useState(false);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
const [localQuery, setLocalQuery] = React.useState(query);
|
||||
const combobox = Ariakit.useComboboxStore({
|
||||
resetValueOnHide,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
setLocalQuery(query);
|
||||
}, [query]);
|
||||
|
||||
const debouncedOnQueryChange = React.useMemo(
|
||||
() =>
|
||||
debounce((value: string) => {
|
||||
onQueryChange(value);
|
||||
}, 500),
|
||||
[onQueryChange],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
debouncedOnQueryChange.cancel();
|
||||
};
|
||||
}, [debouncedOnQueryChange]);
|
||||
|
||||
const onPickHandler = (option: TOption) => {
|
||||
setLocalQuery('');
|
||||
onQueryChange('');
|
||||
onPick(option);
|
||||
setOpen(false);
|
||||
|
@ -47,9 +69,11 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
|
|||
inputRef.current.focus();
|
||||
}
|
||||
};
|
||||
const showClearIcon = query.trim().length > 0;
|
||||
const showClearIcon = localQuery.trim().length > 0;
|
||||
const clearText = () => {
|
||||
setLocalQuery('');
|
||||
onQueryChange('');
|
||||
debouncedOnQueryChange.cancel();
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
|
@ -77,7 +101,9 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
|
|||
if (e.key === 'Escape' && combobox.getState().open) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setLocalQuery('');
|
||||
onQueryChange('');
|
||||
debouncedOnQueryChange.cancel();
|
||||
setOpen(false);
|
||||
}
|
||||
}}
|
||||
|
@ -85,9 +111,11 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
|
|||
setValueOnClick={false}
|
||||
setValueOnChange={false}
|
||||
onChange={(e) => {
|
||||
onQueryChange(e.target.value);
|
||||
const value = e.target.value;
|
||||
setLocalQuery(value);
|
||||
debouncedOnQueryChange(value);
|
||||
}}
|
||||
value={query}
|
||||
value={localQuery}
|
||||
// autoSelect
|
||||
placeholder={placeholder || localize('com_ui_select_options')}
|
||||
className="m-0 mr-0 w-full rounded-md border-none bg-transparent p-0 py-2 pl-9 pr-3 text-sm leading-tight text-text-primary placeholder-text-secondary placeholder-opacity-100 focus:outline-none focus-visible:outline-none group-focus-within:placeholder-text-primary group-hover:placeholder-text-primary"
|
||||
|
@ -115,7 +143,7 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
|
|||
open={
|
||||
isLoading ||
|
||||
options.length > 0 ||
|
||||
(query.trim().length >= minQueryLengthForNoResults && !isLoading)
|
||||
(localQuery.trim().length >= minQueryLengthForNoResults && !isLoading)
|
||||
}
|
||||
store={combobox}
|
||||
unmountOnHide
|
||||
|
@ -162,7 +190,7 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
|
|||
));
|
||||
}
|
||||
|
||||
if (query.trim().length >= minQueryLengthForNoResults) {
|
||||
if (localQuery.trim().length >= minQueryLengthForNoResults) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
|
@ -2,7 +2,7 @@ import React, { useState, useId } from 'react';
|
|||
import * as Menu from '@ariakit/react/menu';
|
||||
import { Button, DropdownPopup } from '@librechat/client';
|
||||
import { Users, X, ExternalLink, ChevronDown } from 'lucide-react';
|
||||
import type { TPrincipal, TAccessRole, ACCESS_ROLE_IDS } from 'librechat-data-provider';
|
||||
import type { TPrincipal, TAccessRole, AccessRoleIds } from 'librechat-data-provider';
|
||||
import { getRoleLocalizationKeys } from '~/utils';
|
||||
import PrincipalAvatar from '../PrincipalAvatar';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
@ -10,7 +10,7 @@ import { useLocalize } from '~/hooks';
|
|||
interface SelectedPrincipalsListProps {
|
||||
principles: TPrincipal[];
|
||||
onRemoveHandler: (idOnTheSource: string) => void;
|
||||
onRoleChange?: (idOnTheSource: string, newRoleId: string) => void;
|
||||
onRoleChange?: (idOnTheSource: string, newRoleId: AccessRoleIds) => void;
|
||||
availableRoles?: Omit<TAccessRole, 'resourceType'>[];
|
||||
className?: string;
|
||||
}
|
||||
|
@ -98,8 +98,8 @@ export default function SelectedPrincipalsList({
|
|||
}
|
||||
|
||||
interface RoleSelectorProps {
|
||||
currentRole: ACCESS_ROLE_IDS;
|
||||
onRoleChange: (newRole: ACCESS_ROLE_IDS) => void;
|
||||
currentRole: AccessRoleIds;
|
||||
onRoleChange: (newRole: AccessRoleIds) => void;
|
||||
availableRoles: Omit<TAccessRole, 'resourceType'>[];
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,7 @@ function RoleSelector({ currentRole, onRoleChange, availableRoles }: RoleSelecto
|
|||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const localize = useLocalize();
|
||||
|
||||
const getLocalizedRoleName = (roleId: ACCESS_ROLE_IDS) => {
|
||||
const getLocalizedRoleName = (roleId: AccessRoleIds) => {
|
||||
const keys = getRoleLocalizationKeys(roleId);
|
||||
return localize(keys.name);
|
||||
};
|
3
client/src/components/Sharing/PeoplePicker/index.ts
Normal file
3
client/src/components/Sharing/PeoplePicker/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export { default as PeoplePicker } from './PeoplePicker';
|
||||
export { default as PeoplePickerSearchItem } from './PeoplePickerSearchItem';
|
||||
export { default as SelectedPrincipalsList } from './SelectedPrincipalsList';
|
|
@ -1,7 +1,9 @@
|
|||
import React from 'react';
|
||||
import { Globe, Shield } from 'lucide-react';
|
||||
import { Switch } from '@librechat/client';
|
||||
import AccessRolesPicker from '../SidePanel/Agents/Sharing/AccessRolesPicker';
|
||||
import { Globe, Shield } from 'lucide-react';
|
||||
import type { AccessRoleIds } from 'librechat-data-provider';
|
||||
import { ResourceType } from 'librechat-data-provider';
|
||||
import AccessRolesPicker from './AccessRolesPicker';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function PublicSharingToggle({
|
||||
|
@ -9,13 +11,13 @@ export default function PublicSharingToggle({
|
|||
publicRole,
|
||||
onPublicToggle,
|
||||
onPublicRoleChange,
|
||||
resourceType = 'agent',
|
||||
resourceType = ResourceType.AGENT,
|
||||
}: {
|
||||
isPublic: boolean;
|
||||
publicRole: string;
|
||||
publicRole: AccessRoleIds;
|
||||
onPublicToggle: (isPublic: boolean) => void;
|
||||
onPublicRoleChange: (role: string) => void;
|
||||
resourceType?: string;
|
||||
onPublicRoleChange: (role: AccessRoleIds) => void;
|
||||
resourceType?: ResourceType;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
export { default as AccessRolesPicker } from './AccessRolesPicker';
|
||||
export { default as GenericGrantAccessDialog } from './GenericGrantAccessDialog';
|
||||
export { default as GenericManagePermissionsDialog } from './GenericManagePermissionsDialog';
|
||||
export { default as ManagePermissionsDialog } from './ManagePermissionsDialog';
|
||||
export { default as PrincipalAvatar } from './PrincipalAvatar';
|
||||
export { default as PublicSharingToggle } from './PublicSharingToggle';
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import * as Ariakit from '@ariakit/react';
|
||||
import { useMemo, useEffect, useState } from 'react';
|
||||
import * as Ariakit from '@ariakit/react';
|
||||
import { ShieldEllipsis } from 'lucide-react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { Permissions, SystemRoles, roleDefaults, PermissionTypes } from 'librechat-data-provider';
|
||||
import {
|
||||
Button,
|
||||
Switch,
|
||||
OGDialog,
|
||||
DropdownPopup,
|
||||
OGDialogTitle,
|
||||
OGDialogContent,
|
||||
OGDialogTrigger,
|
||||
Button,
|
||||
Switch,
|
||||
DropdownPopup,
|
||||
useToastContext,
|
||||
} from '@librechat/client';
|
||||
import type { Control, UseFormSetValue, UseFormGetValues } from 'react-hook-form';
|
||||
|
@ -64,8 +64,8 @@ const LabelController: React.FC<LabelControllerProps> = ({
|
|||
|
||||
const AdminSettings = () => {
|
||||
const localize = useLocalize();
|
||||
const { user, roles } = useAuthContext();
|
||||
const { showToast } = useToastContext();
|
||||
const { user, roles } = useAuthContext();
|
||||
const { mutate, isLoading } = useUpdateAgentPermissionsMutation({
|
||||
onSuccess: () => {
|
||||
showToast({ status: 'success', message: localize('com_ui_saved') });
|
||||
|
@ -79,8 +79,9 @@ const AdminSettings = () => {
|
|||
const [selectedRole, setSelectedRole] = useState<SystemRoles>(SystemRoles.USER);
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
if (roles?.[selectedRole]?.permissions) {
|
||||
return roles[selectedRole].permissions[PermissionTypes.AGENTS];
|
||||
const rolePerms = roles?.[selectedRole]?.permissions;
|
||||
if (rolePerms) {
|
||||
return rolePerms[PermissionTypes.AGENTS];
|
||||
}
|
||||
return roleDefaults[selectedRole].permissions[PermissionTypes.AGENTS];
|
||||
}, [roles, selectedRole]);
|
||||
|
@ -98,8 +99,9 @@ const AdminSettings = () => {
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (roles?.[selectedRole]?.permissions?.[PermissionTypes.AGENTS]) {
|
||||
reset(roles[selectedRole].permissions[PermissionTypes.AGENTS]);
|
||||
const value = roles?.[selectedRole]?.permissions?.[PermissionTypes.AGENTS];
|
||||
if (value) {
|
||||
reset(value);
|
||||
} else {
|
||||
reset(roleDefaults[selectedRole].permissions[PermissionTypes.AGENTS]);
|
||||
}
|
||||
|
@ -211,7 +213,8 @@ const AdminSettings = () => {
|
|||
</div>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
type="button"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
disabled={isSubmitting || isLoading}
|
||||
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
|
||||
>
|
||||
|
|
|
@ -3,8 +3,9 @@ import { useWatch, useFormContext } from 'react-hook-form';
|
|||
import {
|
||||
SystemRoles,
|
||||
Permissions,
|
||||
ResourceType,
|
||||
PermissionTypes,
|
||||
PERMISSION_BITS,
|
||||
PermissionBits,
|
||||
} from 'librechat-data-provider';
|
||||
import type { AgentForm, AgentPanelProps } from '~/common';
|
||||
import { useLocalize, useAuthContext, useHasAccess, useResourcePermissions } from '~/hooks';
|
||||
|
@ -46,8 +47,8 @@ export default function AgentFooter({
|
|||
agent?._id || '',
|
||||
);
|
||||
|
||||
const canShareThisAgent = hasPermission(PERMISSION_BITS.SHARE);
|
||||
const canDeleteThisAgent = hasPermission(PERMISSION_BITS.DELETE);
|
||||
const canShareThisAgent = hasPermission(PermissionBits.SHARE);
|
||||
const canDeleteThisAgent = hasPermission(PermissionBits.DELETE);
|
||||
const renderSaveButton = () => {
|
||||
if (createMutation.isLoading || updateMutation.isLoading) {
|
||||
return <Spinner className="icon-md" aria-hidden="true" />;
|
||||
|
@ -84,7 +85,7 @@ export default function AgentFooter({
|
|||
resourceDbId={agent?._id}
|
||||
resourceId={agent_id}
|
||||
resourceName={agent?.name ?? ''}
|
||||
resourceType="agent"
|
||||
resourceType={ResourceType.AGENT}
|
||||
/>
|
||||
)}
|
||||
{agent && agent.author === user?.id && <DuplicateAgent agent_id={agent_id} />}
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
Constants,
|
||||
SystemRoles,
|
||||
EModelEndpoint,
|
||||
PERMISSION_BITS,
|
||||
PermissionBits,
|
||||
isAssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type { AgentForm, StringOption } from '~/common';
|
||||
|
@ -57,7 +57,7 @@ export default function AgentPanel() {
|
|||
basicAgentQuery.data?._id || '',
|
||||
);
|
||||
|
||||
const canEdit = hasPermission(PERMISSION_BITS.EDIT);
|
||||
const canEdit = hasPermission(PermissionBits.EDIT);
|
||||
|
||||
const expandedAgentQuery = useGetExpandedAgentByIdQuery(current_agent_id ?? '', {
|
||||
enabled:
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Controller, useFormContext } from 'react-hook-form';
|
|||
import type { TSpecialVarLabel } from 'librechat-data-provider';
|
||||
import type { AgentForm } from '~/common';
|
||||
import { cn, defaultTextProps, removeFocusOutlines } from '~/utils';
|
||||
// import { ControlCombobox } from '@librechat/client';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const inputClass = cn(
|
||||
|
@ -49,26 +48,6 @@ export default function Instructions() {
|
|||
{localize('com_ui_instructions')}
|
||||
</label>
|
||||
<div className="ml-auto" title="Add variables to instructions">
|
||||
{/* ControlCombobox implementation
|
||||
<ControlCombobox
|
||||
selectedValue=""
|
||||
displayValue="Add variables"
|
||||
items={variableOptions.map((option) => ({
|
||||
label: option.label,
|
||||
value: option.value,
|
||||
}))}
|
||||
setValue={handleAddVariable}
|
||||
ariaLabel="Add variable to instructions"
|
||||
searchPlaceholder="Search variables"
|
||||
selectPlaceholder="Add"
|
||||
isCollapsed={false}
|
||||
SelectIcon={<PlusCircle className="h-3 w-3 text-text-secondary" />}
|
||||
containerClassName="w-fit"
|
||||
className="h-7 gap-1 rounded-md border border-border-medium bg-surface-secondary px-2 py-0 text-sm text-text-primary transition-colors duration-200 hover:bg-surface-tertiary"
|
||||
iconSide="left"
|
||||
showCarat={false}
|
||||
/>
|
||||
*/}
|
||||
<DropdownPopup
|
||||
portal={true}
|
||||
mountByState={true}
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Globe } from 'lucide-react';
|
||||
import { Switch } from '@librechat/client';
|
||||
import AccessRolesPicker from './AccessRolesPicker';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface PublicSharingToggleProps {
|
||||
isPublic: boolean;
|
||||
publicRole: string;
|
||||
onPublicToggle: (isPublic: boolean) => void;
|
||||
onPublicRoleChange: (role: string) => void;
|
||||
className?: string;
|
||||
resourceType?: string;
|
||||
}
|
||||
|
||||
export default function PublicSharingToggle({
|
||||
isPublic,
|
||||
publicRole,
|
||||
onPublicToggle,
|
||||
onPublicRoleChange,
|
||||
className = '',
|
||||
resourceType = 'agent',
|
||||
}: PublicSharingToggleProps) {
|
||||
const localize = useLocalize();
|
||||
|
||||
return (
|
||||
<div className={`space-y-3 border-t pt-4 ${className}`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="flex items-center gap-2 text-sm font-medium">
|
||||
<Globe className="h-4 w-4" />
|
||||
{localize('com_ui_share_with_everyone')}
|
||||
</h3>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{localize('com_ui_make_agent_available_all_users')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={isPublic}
|
||||
onCheckedChange={onPublicToggle}
|
||||
aria-label={localize('com_ui_share_with_everyone')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isPublic && (
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium">
|
||||
{localize('com_ui_public_access_level')}
|
||||
</label>
|
||||
<AccessRolesPicker
|
||||
resourceType={resourceType}
|
||||
selectedRoleId={publicRole}
|
||||
onRoleChange={onPublicRoleChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -3,7 +3,7 @@ import { SystemRoles } from 'librechat-data-provider';
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import type { Agent, AgentCreateParams, TUser } from 'librechat-data-provider';
|
||||
import type { Agent, AgentCreateParams, TUser, ResourceType } from 'librechat-data-provider';
|
||||
import AgentFooter from '../AgentFooter';
|
||||
import { Panel } from '~/common';
|
||||
|
||||
|
@ -171,7 +171,7 @@ jest.mock('~/components/Sharing', () => ({
|
|||
resourceDbId: string;
|
||||
resourceId: string;
|
||||
resourceName: string;
|
||||
resourceType: string;
|
||||
resourceType: ResourceType;
|
||||
}) => (
|
||||
<div
|
||||
data-testid="grant-access-dialog"
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export * from './ui';
|
||||
export * from './Plugins';
|
||||
export * from './svg';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { dataService, MutationKeys, PERMISSION_BITS, QueryKeys } from 'librechat-data-provider';
|
||||
import { dataService, MutationKeys, PermissionBits, QueryKeys } from 'librechat-data-provider';
|
||||
import type * as t from 'librechat-data-provider';
|
||||
import type { QueryClient, UseMutationResult } from '@tanstack/react-query';
|
||||
|
||||
|
@ -7,8 +7,8 @@ import type { QueryClient, UseMutationResult } from '@tanstack/react-query';
|
|||
* AGENTS
|
||||
*/
|
||||
export const allAgentViewAndEditQueryKeys: t.AgentListParams[] = [
|
||||
{ requiredPermission: PERMISSION_BITS.VIEW },
|
||||
{ requiredPermission: PERMISSION_BITS.EDIT },
|
||||
{ requiredPermission: PermissionBits.VIEW },
|
||||
{ requiredPermission: PermissionBits.EDIT },
|
||||
];
|
||||
/**
|
||||
* Create a new agent
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
dataService,
|
||||
Permissions,
|
||||
EModelEndpoint,
|
||||
PERMISSION_BITS,
|
||||
PermissionBits,
|
||||
PermissionTypes,
|
||||
} from 'librechat-data-provider';
|
||||
import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
@ -26,7 +26,7 @@ export const useAgentListingDefaultPermissionLevel = () => {
|
|||
|
||||
// When marketplace is active: EDIT permissions (builder mode)
|
||||
// When marketplace is not active: VIEW permissions (browse mode)
|
||||
return hasMarketplaceAccess ? PERMISSION_BITS.EDIT : PERMISSION_BITS.VIEW;
|
||||
return hasMarketplaceAccess ? PermissionBits.EDIT : PermissionBits.VIEW;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -34,7 +34,7 @@ export const useAgentListingDefaultPermissionLevel = () => {
|
|||
*/
|
||||
export const defaultAgentParams: t.AgentListParams = {
|
||||
limit: 10,
|
||||
requiredPermission: PERMISSION_BITS.EDIT,
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
};
|
||||
/**
|
||||
* Hook for getting all available tools for A
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
isAgentsEndpoint,
|
||||
getConfigDefaults,
|
||||
isAssistantsEndpoint,
|
||||
PERMISSION_BITS,
|
||||
PermissionBits,
|
||||
} from 'librechat-data-provider';
|
||||
import type { TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider';
|
||||
import type { MentionOption } from '~/common';
|
||||
|
@ -81,7 +81,7 @@ export default function useMentions({
|
|||
[startupConfig?.interface],
|
||||
);
|
||||
const { data: agentsList = null } = useListAgentsQuery(
|
||||
{ requiredPermission: PERMISSION_BITS.VIEW },
|
||||
{ requiredPermission: PermissionBits.VIEW },
|
||||
{
|
||||
enabled: hasAgentAccess && interfaceConfig.modelSelect === true,
|
||||
select: (res) => {
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
isAgentsEndpoint,
|
||||
tQueryParamsSchema,
|
||||
isAssistantsEndpoint,
|
||||
PERMISSION_BITS,
|
||||
PermissionBits,
|
||||
} from 'librechat-data-provider';
|
||||
import type {
|
||||
TPreset,
|
||||
|
@ -80,7 +80,7 @@ const processValidSettings = (queryParams: Record<string, string>) => {
|
|||
};
|
||||
|
||||
const injectAgentIntoAgentsMap = (queryClient: QueryClient, agent: any) => {
|
||||
const editCacheKey = [QueryKeys.agents, { requiredPermission: PERMISSION_BITS.EDIT }];
|
||||
const editCacheKey = [QueryKeys.agents, { requiredPermission: PermissionBits.EDIT }];
|
||||
const editCache = queryClient.getQueryData<AgentListResponse>(editCacheKey);
|
||||
|
||||
if (editCache?.data && !editCache.data.some((cachedAgent) => cachedAgent.id === agent.id)) {
|
||||
|
|
|
@ -3,18 +3,18 @@ import {
|
|||
useGetResourcePermissionsQuery,
|
||||
useUpdateResourcePermissionsMutation,
|
||||
} from 'librechat-data-provider/react-query';
|
||||
import type { TPrincipal } from 'librechat-data-provider';
|
||||
import type { TPrincipal, ResourceType, AccessRoleIds } from 'librechat-data-provider';
|
||||
import { getResourceConfig } from '~/utils';
|
||||
|
||||
/**
|
||||
* Hook to manage resource permission state including current shares, public access, and mutations
|
||||
* @param resourceType - Type of resource (e.g., 'agent', 'promptGroup')
|
||||
* @param resourceType - Type of resource (e.g., ResourceType.AGENT, ResourceType.PROMPTGROUP)
|
||||
* @param resourceDbId - Database ID of the resource
|
||||
* @param isModalOpen - Whether the modal is open (for effect dependencies)
|
||||
* @returns Object with permission state and update mutation
|
||||
*/
|
||||
export const useResourcePermissionState = (
|
||||
resourceType: string,
|
||||
resourceType: ResourceType,
|
||||
resourceDbId: string | null | undefined,
|
||||
isModalOpen: boolean = false,
|
||||
) => {
|
||||
|
@ -52,13 +52,15 @@ export const useResourcePermissionState = (
|
|||
|
||||
// State for managing public access
|
||||
const [isPublic, setIsPublic] = useState(false);
|
||||
const [publicRole, setPublicRole] = useState<string>(config?.defaultViewerRoleId ?? '');
|
||||
const [publicRole, setPublicRole] = useState<AccessRoleIds | undefined>(
|
||||
config?.defaultViewerRoleId,
|
||||
);
|
||||
|
||||
// Sync state with permissions data when modal opens
|
||||
useEffect(() => {
|
||||
if (permissionsData && isModalOpen) {
|
||||
setIsPublic(currentIsPublic ?? false);
|
||||
setPublicRole(currentPublicRole ?? '');
|
||||
setPublicRole(currentPublicRole);
|
||||
}
|
||||
}, [permissionsData, isModalOpen, currentIsPublic, currentPublicRole]);
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ export * from './Messages';
|
|||
export * from './Plugins';
|
||||
export * from './Prompts';
|
||||
export * from './Roles';
|
||||
export * from './Sharing';
|
||||
export * from './SSE';
|
||||
export * from './AuthContext';
|
||||
export * from './ScreenshotContext';
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import {
|
||||
useGetEffectivePermissionsQuery,
|
||||
hasPermissions,
|
||||
useGetEffectivePermissionsQuery,
|
||||
} from 'librechat-data-provider/react-query';
|
||||
import type { ResourceType } from 'librechat-data-provider';
|
||||
|
||||
/**
|
||||
* fetches resource permissions once and returns a function to check any permission
|
||||
* More efficient when checking multiple permissions for the same resource
|
||||
* @param resourceType - Type of resource (e.g., 'agent')
|
||||
* @param resourceType - Type of resource (e.g., ResourceType.AGENT)
|
||||
* @param resourceId - ID of the resource
|
||||
* @returns Object with hasPermission function and loading state
|
||||
*/
|
||||
export const useResourcePermissions = (resourceType: string, resourceId: string) => {
|
||||
export const useResourcePermissions = (resourceType: ResourceType, resourceId: string) => {
|
||||
const { data, isLoading } = useGetEffectivePermissionsQuery(resourceType, resourceId);
|
||||
|
||||
const hasPermission = (requiredPermission: number): boolean => {
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
TwoFactorScreen,
|
||||
RequestPasswordReset,
|
||||
} from '~/components/Auth';
|
||||
import AgentMarketplace from '~/components/Agents/Marketplace';
|
||||
import { OAuthSuccess, OAuthError } from '~/components/OAuth';
|
||||
import { AuthContextProvider } from '~/hooks/AuthContext';
|
||||
import RouteErrorBoundary from './RouteErrorBoundary';
|
||||
|
@ -18,7 +19,6 @@ import ShareRoute from './ShareRoute';
|
|||
import ChatRoute from './ChatRoute';
|
||||
import Search from './Search';
|
||||
import Root from './Root';
|
||||
import AgentMarketplace from '~/components/SidePanel/Agents/AgentMarketplace';
|
||||
|
||||
const AuthLayout = () => (
|
||||
<AuthContextProvider>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { ACCESS_ROLE_IDS } from 'librechat-data-provider';
|
||||
import { AccessRoleIds, ResourceType } from 'librechat-data-provider';
|
||||
|
||||
export interface ResourceConfig {
|
||||
resourceType: string;
|
||||
defaultViewerRoleId: string;
|
||||
defaultEditorRoleId: string;
|
||||
defaultOwnerRoleId: string;
|
||||
resourceType: ResourceType;
|
||||
defaultViewerRoleId: AccessRoleIds;
|
||||
defaultEditorRoleId: AccessRoleIds;
|
||||
defaultOwnerRoleId: AccessRoleIds;
|
||||
getResourceUrl?: (resourceId: string) => string;
|
||||
getResourceName: (resourceName?: string) => string;
|
||||
getShareMessage: (resourceName?: string) => string;
|
||||
|
@ -12,12 +12,12 @@ export interface ResourceConfig {
|
|||
getCopyUrlMessage: () => string;
|
||||
}
|
||||
|
||||
export const RESOURCE_CONFIGS: Record<string, ResourceConfig> = {
|
||||
agent: {
|
||||
resourceType: 'agent',
|
||||
defaultViewerRoleId: ACCESS_ROLE_IDS.AGENT_VIEWER,
|
||||
defaultEditorRoleId: ACCESS_ROLE_IDS.AGENT_EDITOR,
|
||||
defaultOwnerRoleId: ACCESS_ROLE_IDS.AGENT_OWNER,
|
||||
export const RESOURCE_CONFIGS: Record<ResourceType, ResourceConfig> = {
|
||||
[ResourceType.AGENT]: {
|
||||
resourceType: ResourceType.AGENT,
|
||||
defaultViewerRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
defaultEditorRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
defaultOwnerRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
getResourceUrl: (agentId: string) => `${window.location.origin}/c/new?agent_id=${agentId}`,
|
||||
getResourceName: (name?: string) => (name && name !== '' ? `"${name}"` : 'agent'),
|
||||
getShareMessage: (name?: string) => (name && name !== '' ? `"${name}"` : 'agent'),
|
||||
|
@ -25,11 +25,11 @@ export const RESOURCE_CONFIGS: Record<string, ResourceConfig> = {
|
|||
`Manage permissions for ${name && name !== '' ? `"${name}"` : 'agent'}`,
|
||||
getCopyUrlMessage: () => 'Agent URL copied',
|
||||
},
|
||||
promptGroup: {
|
||||
resourceType: 'promptGroup',
|
||||
defaultViewerRoleId: ACCESS_ROLE_IDS.PROMPTGROUP_VIEWER,
|
||||
defaultEditorRoleId: ACCESS_ROLE_IDS.PROMPTGROUP_EDITOR,
|
||||
defaultOwnerRoleId: ACCESS_ROLE_IDS.PROMPTGROUP_OWNER,
|
||||
[ResourceType.PROMPTGROUP]: {
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
defaultViewerRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
defaultEditorRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
defaultOwnerRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
getResourceName: (name?: string) => (name && name !== '' ? `"${name}"` : 'prompt'),
|
||||
getShareMessage: (name?: string) => (name && name !== '' ? `"${name}"` : 'prompt'),
|
||||
getManageMessage: (name?: string) =>
|
||||
|
@ -38,6 +38,6 @@ export const RESOURCE_CONFIGS: Record<string, ResourceConfig> = {
|
|||
},
|
||||
};
|
||||
|
||||
export const getResourceConfig = (resourceType: string): ResourceConfig | undefined => {
|
||||
export const getResourceConfig = (resourceType: ResourceType): ResourceConfig | undefined => {
|
||||
return RESOURCE_CONFIGS[resourceType];
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { ACCESS_ROLE_IDS } from 'librechat-data-provider';
|
||||
import type { AccessRoleIds } from 'librechat-data-provider';
|
||||
import type { TranslationKeys } from '~/hooks/useLocalize';
|
||||
|
||||
/**
|
||||
|
@ -43,7 +43,7 @@ export const ROLE_LOCALIZATIONS = {
|
|||
* @returns Object with name and description localization keys, or unknown keys if not found
|
||||
*/
|
||||
export const getRoleLocalizationKeys = (
|
||||
roleId: ACCESS_ROLE_IDS,
|
||||
roleId: AccessRoleIds,
|
||||
): {
|
||||
name: TranslationKeys;
|
||||
description: TranslationKeys;
|
||||
|
|
|
@ -4,6 +4,7 @@ const path = require('path');
|
|||
const { logger } = require('@librechat/data-schemas');
|
||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||
|
||||
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||
const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
|
||||
const connect = require('./connect');
|
||||
|
||||
|
@ -164,9 +165,9 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: agent.author,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
grantedBy: agent.author,
|
||||
});
|
||||
results.ownerGrants++;
|
||||
|
@ -178,12 +179,12 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100
|
|||
if (isGlobal) {
|
||||
if (isCollab) {
|
||||
// Global project + collaborative = Public EDIT access
|
||||
publicRoleId = 'agent_editor';
|
||||
publicRoleId = AccessRoleIds.AGENT_EDITOR;
|
||||
description = 'Global Edit';
|
||||
results.publicEditGrants++;
|
||||
} else {
|
||||
// Global project + not collaborative = Public VIEW access
|
||||
publicRoleId = 'agent_viewer';
|
||||
publicRoleId = AccessRoleIds.AGENT_VIEWER;
|
||||
description = 'Global View';
|
||||
results.publicViewGrants++;
|
||||
}
|
||||
|
@ -192,7 +193,7 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100
|
|||
await grantPermission({
|
||||
principalType: 'public',
|
||||
principalId: null,
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: agent._id,
|
||||
accessRoleId: publicRoleId,
|
||||
grantedBy: agent.author,
|
||||
|
|
|
@ -2,6 +2,7 @@ const path = require('path');
|
|||
const { logger } = require('@librechat/data-schemas');
|
||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||
|
||||
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||
const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
|
||||
const connect = require('./connect');
|
||||
|
||||
|
@ -146,9 +147,9 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100
|
|||
await grantPermission({
|
||||
principalType: 'user',
|
||||
principalId: group.author,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: group._id,
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
grantedBy: group.author,
|
||||
});
|
||||
results.ownerGrants++;
|
||||
|
@ -158,9 +159,9 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100
|
|||
await grantPermission({
|
||||
principalType: 'public',
|
||||
principalId: null,
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
resourceId: group._id,
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
grantedBy: group.author,
|
||||
});
|
||||
results.publicViewGrants++;
|
||||
|
|
|
@ -25,26 +25,35 @@ export type TPrincipalSource = 'local' | 'entra';
|
|||
*/
|
||||
export type TAccessLevel = 'none' | 'viewer' | 'editor' | 'owner';
|
||||
|
||||
/**
|
||||
* Resource types for permission system
|
||||
*/
|
||||
export enum ResourceType {
|
||||
AGENT = 'agent',
|
||||
PROMPTGROUP = 'promptGroup',
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission bit constants for bitwise operations
|
||||
*/
|
||||
export const PERMISSION_BITS = {
|
||||
VIEW: 1, // 001 - Can view and use agent
|
||||
EDIT: 2, // 010 - Can modify agent settings
|
||||
DELETE: 4, // 100 - Can delete agent
|
||||
SHARE: 8, // 1000 - Can share agent with others (future)
|
||||
} as const;
|
||||
export enum PermissionBits {
|
||||
/** 001 - Can view and use agent */
|
||||
VIEW = 1,
|
||||
/** 010 - Can modify agent settings */
|
||||
EDIT = 2,
|
||||
/** 100 - Can delete agent */
|
||||
DELETE = 4,
|
||||
/** 1000 - Can share agent with others (future) */
|
||||
SHARE = 8,
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard access role IDs
|
||||
*/
|
||||
export enum ACCESS_ROLE_IDS {
|
||||
export enum AccessRoleIds {
|
||||
AGENT_VIEWER = 'agent_viewer',
|
||||
AGENT_EDITOR = 'agent_editor',
|
||||
AGENT_OWNER = 'agent_owner', // Future use
|
||||
PROMPT_VIEWER = 'prompt_viewer',
|
||||
PROMPT_EDITOR = 'prompt_editor',
|
||||
PROMPT_OWNER = 'prompt_owner',
|
||||
AGENT_OWNER = 'agent_owner',
|
||||
PROMPTGROUP_VIEWER = 'promptGroup_viewer',
|
||||
PROMPTGROUP_EDITOR = 'promptGroup_editor',
|
||||
PROMPTGROUP_OWNER = 'promptGroup_owner',
|
||||
|
@ -64,7 +73,7 @@ export const principalSchema = z.object({
|
|||
avatar: z.string().optional(), // for user and group types
|
||||
description: z.string().optional(), // for group type
|
||||
idOnTheSource: z.string().optional(), // Entra ID for users/groups
|
||||
accessRoleId: z.nativeEnum(ACCESS_ROLE_IDS).optional(), // Access role ID for permissions
|
||||
accessRoleId: z.nativeEnum(AccessRoleIds).optional(), // Access role ID for permissions
|
||||
memberCount: z.number().optional(), // for group type
|
||||
});
|
||||
|
||||
|
@ -72,10 +81,10 @@ export const principalSchema = z.object({
|
|||
* Access role schema - defines named permission sets
|
||||
*/
|
||||
export const accessRoleSchema = z.object({
|
||||
accessRoleId: z.nativeEnum(ACCESS_ROLE_IDS),
|
||||
accessRoleId: z.nativeEnum(AccessRoleIds),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
resourceType: z.string().default('agent'),
|
||||
resourceType: z.nativeEnum(ResourceType).default(ResourceType.AGENT),
|
||||
permBits: z.number(),
|
||||
});
|
||||
|
||||
|
@ -98,7 +107,7 @@ export const permissionEntrySchema = z.object({
|
|||
* Resource permissions response schema
|
||||
*/
|
||||
export const resourcePermissionsResponseSchema = z.object({
|
||||
resourceType: z.string(),
|
||||
resourceType: z.nativeEnum(ResourceType),
|
||||
resourceId: z.string(),
|
||||
permissions: z.array(permissionEntrySchema),
|
||||
});
|
||||
|
@ -210,7 +219,7 @@ export type TPrincipalSearchResponse = {
|
|||
* Available roles response
|
||||
*/
|
||||
export type TAvailableRolesResponse = {
|
||||
resourceType: string;
|
||||
resourceType: ResourceType;
|
||||
roles: TAccessRole[];
|
||||
};
|
||||
|
||||
|
@ -219,11 +228,11 @@ export type TAvailableRolesResponse = {
|
|||
* This matches the enhanced aggregation-based endpoint response format
|
||||
*/
|
||||
export const getResourcePermissionsResponseSchema = z.object({
|
||||
resourceType: z.string(),
|
||||
resourceId: z.string(),
|
||||
resourceType: z.nativeEnum(ResourceType),
|
||||
resourceId: z.nativeEnum(AccessRoleIds),
|
||||
principals: z.array(principalSchema),
|
||||
public: z.boolean(),
|
||||
publicAccessRoleId: z.string().optional(),
|
||||
publicAccessRoleId: z.nativeEnum(AccessRoleIds).optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -265,9 +274,9 @@ export interface TPermissionCheck {
|
|||
* Convert permission bits to access level
|
||||
*/
|
||||
export function permBitsToAccessLevel(permBits: number): TAccessLevel {
|
||||
if ((permBits & PERMISSION_BITS.DELETE) > 0) return 'owner';
|
||||
if ((permBits & PERMISSION_BITS.EDIT) > 0) return 'editor';
|
||||
if ((permBits & PERMISSION_BITS.VIEW) > 0) return 'viewer';
|
||||
if ((permBits & PermissionBits.DELETE) > 0) return 'owner';
|
||||
if ((permBits & PermissionBits.EDIT) > 0) return 'editor';
|
||||
if ((permBits & PermissionBits.VIEW) > 0) return 'viewer';
|
||||
return 'none';
|
||||
}
|
||||
|
||||
|
@ -276,14 +285,14 @@ export function permBitsToAccessLevel(permBits: number): TAccessLevel {
|
|||
*/
|
||||
export function accessRoleToPermBits(accessRoleId: string): number {
|
||||
switch (accessRoleId) {
|
||||
case ACCESS_ROLE_IDS.AGENT_VIEWER:
|
||||
return PERMISSION_BITS.VIEW;
|
||||
case ACCESS_ROLE_IDS.AGENT_EDITOR:
|
||||
return PERMISSION_BITS.VIEW | PERMISSION_BITS.EDIT;
|
||||
case ACCESS_ROLE_IDS.AGENT_OWNER:
|
||||
return PERMISSION_BITS.VIEW | PERMISSION_BITS.EDIT | PERMISSION_BITS.DELETE;
|
||||
case AccessRoleIds.AGENT_VIEWER:
|
||||
return PermissionBits.VIEW;
|
||||
case AccessRoleIds.AGENT_EDITOR:
|
||||
return PermissionBits.VIEW | PermissionBits.EDIT;
|
||||
case AccessRoleIds.AGENT_OWNER:
|
||||
return PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE;
|
||||
default:
|
||||
return PERMISSION_BITS.VIEW;
|
||||
return PermissionBits.VIEW;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { AssistantsEndpoint } from './schemas';
|
||||
import * as q from './types/queries';
|
||||
import { ResourceType } from './accessPermissions';
|
||||
|
||||
// Testing this buildQuery function
|
||||
const buildQuery = (params: Record<string, unknown>): string => {
|
||||
|
@ -323,15 +324,16 @@ export const searchPrincipals = (params: q.PrincipalSearchParams) => {
|
|||
return url;
|
||||
};
|
||||
|
||||
export const getAccessRoles = (resourceType: string) => `/api/permissions/${resourceType}/roles`;
|
||||
export const getAccessRoles = (resourceType: ResourceType) =>
|
||||
`/api/permissions/${resourceType}/roles`;
|
||||
|
||||
export const getResourcePermissions = (resourceType: string, resourceId: string) =>
|
||||
export const getResourcePermissions = (resourceType: ResourceType, resourceId: string) =>
|
||||
`/api/permissions/${resourceType}/${resourceId}`;
|
||||
|
||||
export const updateResourcePermissions = (resourceType: string, resourceId: string) =>
|
||||
export const updateResourcePermissions = (resourceType: ResourceType, resourceId: string) =>
|
||||
`/api/permissions/${resourceType}/${resourceId}`;
|
||||
|
||||
export const getEffectivePermissions = (resourceType: string, resourceId: string) =>
|
||||
export const getEffectivePermissions = (resourceType: ResourceType, resourceId: string) =>
|
||||
`/api/permissions/${resourceType}/${resourceId}/effective`;
|
||||
|
||||
// SharePoint Graph API Token
|
||||
|
|
|
@ -909,19 +909,21 @@ export function searchPrincipals(
|
|||
return request.get(endpoints.searchPrincipals(params));
|
||||
}
|
||||
|
||||
export function getAccessRoles(resourceType: string): Promise<q.AccessRolesResponse> {
|
||||
export function getAccessRoles(
|
||||
resourceType: permissions.ResourceType,
|
||||
): Promise<q.AccessRolesResponse> {
|
||||
return request.get(endpoints.getAccessRoles(resourceType));
|
||||
}
|
||||
|
||||
export function getResourcePermissions(
|
||||
resourceType: string,
|
||||
resourceType: permissions.ResourceType,
|
||||
resourceId: string,
|
||||
): Promise<permissions.TGetResourcePermissionsResponse> {
|
||||
return request.get(endpoints.getResourcePermissions(resourceType, resourceId));
|
||||
}
|
||||
|
||||
export function updateResourcePermissions(
|
||||
resourceType: string,
|
||||
resourceType: permissions.ResourceType,
|
||||
resourceId: string,
|
||||
data: permissions.TUpdateResourcePermissionsRequest,
|
||||
): Promise<permissions.TUpdateResourcePermissionsResponse> {
|
||||
|
@ -929,7 +931,7 @@ export function updateResourcePermissions(
|
|||
}
|
||||
|
||||
export function getEffectivePermissions(
|
||||
resourceType: string,
|
||||
resourceType: permissions.ResourceType,
|
||||
resourceId: string,
|
||||
): Promise<permissions.TEffectivePermissionsResponse> {
|
||||
return request.get(endpoints.getEffectivePermissions(resourceType, resourceId));
|
||||
|
|
|
@ -14,6 +14,7 @@ import { QueryKeys } from '../keys';
|
|||
import * as s from '../schemas';
|
||||
import * as t from '../types';
|
||||
import * as permissions from '../accessPermissions';
|
||||
import { ResourceType } from '../accessPermissions';
|
||||
|
||||
export { hasPermissions } from '../accessPermissions';
|
||||
|
||||
|
@ -405,7 +406,7 @@ export const useSearchPrincipalsQuery = (
|
|||
};
|
||||
|
||||
export const useGetAccessRolesQuery = (
|
||||
resourceType: string,
|
||||
resourceType: ResourceType,
|
||||
config?: UseQueryOptions<q.AccessRolesResponse>,
|
||||
): QueryObserverResult<q.AccessRolesResponse> => {
|
||||
return useQuery<q.AccessRolesResponse>(
|
||||
|
@ -423,7 +424,7 @@ export const useGetAccessRolesQuery = (
|
|||
};
|
||||
|
||||
export const useGetResourcePermissionsQuery = (
|
||||
resourceType: string,
|
||||
resourceType: ResourceType,
|
||||
resourceId: string,
|
||||
config?: UseQueryOptions<permissions.TGetResourcePermissionsResponse>,
|
||||
): QueryObserverResult<permissions.TGetResourcePermissionsResponse> => {
|
||||
|
@ -445,7 +446,7 @@ export const useUpdateResourcePermissionsMutation = (): UseMutationResult<
|
|||
permissions.TUpdateResourcePermissionsResponse,
|
||||
Error,
|
||||
{
|
||||
resourceType: string;
|
||||
resourceType: ResourceType;
|
||||
resourceId: string;
|
||||
data: permissions.TUpdateResourcePermissionsRequest;
|
||||
}
|
||||
|
@ -472,7 +473,7 @@ export const useUpdateResourcePermissionsMutation = (): UseMutationResult<
|
|||
};
|
||||
|
||||
export const useGetEffectivePermissionsQuery = (
|
||||
resourceType: string,
|
||||
resourceType: ResourceType,
|
||||
resourceId: string,
|
||||
config?: UseQueryOptions<permissions.TEffectivePermissionsResponse>,
|
||||
): QueryObserverResult<permissions.TEffectivePermissionsResponse> => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { InfiniteData } from '@tanstack/react-query';
|
||||
import type { ACCESS_ROLE_IDS } from '../accessPermissions';
|
||||
import type { AccessRoleIds } from '../accessPermissions';
|
||||
import type * as a from '../types/agents';
|
||||
import type * as s from '../schemas';
|
||||
import type * as t from '../types';
|
||||
|
@ -159,7 +159,7 @@ export type PrincipalSearchResponse = {
|
|||
};
|
||||
|
||||
export type AccessRole = {
|
||||
accessRoleId: ACCESS_ROLE_IDS;
|
||||
accessRoleId: AccessRoleIds;
|
||||
name: string;
|
||||
description: string;
|
||||
permBits: number;
|
||||
|
|
|
@ -1,16 +1,4 @@
|
|||
/**
|
||||
* Permission bit flags
|
||||
*/
|
||||
export enum PermissionBits {
|
||||
/** 0001 - Can view/access the resource */
|
||||
VIEW = 1,
|
||||
/** 0010 - Can modify the resource */
|
||||
EDIT = 2,
|
||||
/** 0100 - Can delete the resource */
|
||||
DELETE = 4,
|
||||
/** 1000 - Can share the resource with others */
|
||||
SHARE = 8,
|
||||
}
|
||||
import { PermissionBits } from 'librechat-data-provider';
|
||||
|
||||
/**
|
||||
* Common role combinations
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import mongoose from 'mongoose';
|
||||
import { AccessRoleIds, ResourceType, PermissionBits } from 'librechat-data-provider';
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
||||
import { createAccessRoleMethods } from './accessRole';
|
||||
import { PermissionBits, RoleBits } from '~/common';
|
||||
import accessRoleSchema from '~/schema/accessRole';
|
||||
import type * as t from '~/types';
|
||||
import { createAccessRoleMethods } from './accessRole';
|
||||
import accessRoleSchema from '~/schema/accessRole';
|
||||
import { RoleBits } from '~/common';
|
||||
|
||||
let mongoServer: MongoMemoryServer;
|
||||
let AccessRole: mongoose.Model<t.IAccessRole>;
|
||||
|
@ -32,7 +33,7 @@ describe('AccessRole Model Tests', () => {
|
|||
accessRoleId: 'test_viewer',
|
||||
name: 'Test Viewer',
|
||||
description: 'Test role for viewer permissions',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER,
|
||||
};
|
||||
|
||||
|
@ -98,7 +99,7 @@ describe('AccessRole Model Tests', () => {
|
|||
accessRoleId: 'test_editor',
|
||||
name: 'Test Editor',
|
||||
description: 'Test role for editor permissions',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.EDITOR,
|
||||
},
|
||||
];
|
||||
|
@ -120,17 +121,17 @@ describe('AccessRole Model Tests', () => {
|
|||
// Create sample roles for testing
|
||||
await Promise.all([
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
name: 'Agent Viewer',
|
||||
description: 'Can view agents',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER,
|
||||
}),
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
name: 'Agent Editor',
|
||||
description: 'Can edit agents',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.EDITOR,
|
||||
}),
|
||||
methods.createRole({
|
||||
|
@ -154,7 +155,7 @@ describe('AccessRole Model Tests', () => {
|
|||
const agentRoles = await methods.findRolesByResourceType('agent');
|
||||
expect(agentRoles).toHaveLength(2);
|
||||
expect(agentRoles.map((r) => r.accessRoleId).sort()).toEqual(
|
||||
['agent_editor', 'agent_viewer'].sort(),
|
||||
[AccessRoleIds.AGENT_EDITOR, AccessRoleIds.AGENT_VIEWER].sort(),
|
||||
);
|
||||
|
||||
const projectRoles = await methods.findRolesByResourceType('project');
|
||||
|
@ -167,11 +168,11 @@ describe('AccessRole Model Tests', () => {
|
|||
test('should find role by permissions', async () => {
|
||||
const viewerRole = await methods.findRoleByPermissions('agent', RoleBits.VIEWER);
|
||||
expect(viewerRole).toBeDefined();
|
||||
expect(viewerRole?.accessRoleId).toBe('agent_viewer');
|
||||
expect(viewerRole?.accessRoleId).toBe(AccessRoleIds.AGENT_VIEWER);
|
||||
|
||||
const editorRole = await methods.findRoleByPermissions('agent', RoleBits.EDITOR);
|
||||
expect(editorRole).toBeDefined();
|
||||
expect(editorRole?.accessRoleId).toBe('agent_editor');
|
||||
expect(editorRole?.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR);
|
||||
});
|
||||
|
||||
test('should return null when no role matches the permissions', async () => {
|
||||
|
@ -192,19 +193,26 @@ describe('AccessRole Model Tests', () => {
|
|||
|
||||
// Verify the result contains the default roles
|
||||
expect(Object.keys(result).sort()).toEqual(
|
||||
['agent_editor', 'agent_owner', 'agent_viewer'].sort(),
|
||||
[
|
||||
AccessRoleIds.AGENT_EDITOR,
|
||||
AccessRoleIds.AGENT_OWNER,
|
||||
AccessRoleIds.AGENT_VIEWER,
|
||||
AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
].sort(),
|
||||
);
|
||||
|
||||
// Verify each role exists in the database
|
||||
const agentViewerRole = await methods.findRoleByIdentifier('agent_viewer');
|
||||
const agentViewerRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER);
|
||||
expect(agentViewerRole).toBeDefined();
|
||||
expect(agentViewerRole?.permBits).toBe(RoleBits.VIEWER);
|
||||
|
||||
const agentEditorRole = await methods.findRoleByIdentifier('agent_editor');
|
||||
const agentEditorRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR);
|
||||
expect(agentEditorRole).toBeDefined();
|
||||
expect(agentEditorRole?.permBits).toBe(RoleBits.EDITOR);
|
||||
|
||||
const agentOwnerRole = await methods.findRoleByIdentifier('agent_owner');
|
||||
const agentOwnerRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_OWNER);
|
||||
expect(agentOwnerRole).toBeDefined();
|
||||
expect(agentOwnerRole?.permBits).toBe(RoleBits.OWNER);
|
||||
});
|
||||
|
@ -212,10 +220,10 @@ describe('AccessRole Model Tests', () => {
|
|||
test('should not modify existing roles when seeding', async () => {
|
||||
// Create a modified version of a default role
|
||||
const customRole = {
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
name: 'Custom Viewer',
|
||||
description: 'Custom viewer description',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER,
|
||||
};
|
||||
|
||||
|
@ -225,7 +233,7 @@ describe('AccessRole Model Tests', () => {
|
|||
await methods.seedDefaultRoles();
|
||||
|
||||
// Verify the custom role was not modified
|
||||
const role = await methods.findRoleByIdentifier('agent_viewer');
|
||||
const role = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER);
|
||||
expect(role?.name).toBe(customRole.name);
|
||||
expect(role?.description).toBe(customRole.description);
|
||||
});
|
||||
|
@ -238,27 +246,27 @@ describe('AccessRole Model Tests', () => {
|
|||
// Create sample roles with ascending permission levels
|
||||
await Promise.all([
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
name: 'Agent Viewer',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER, // 1
|
||||
}),
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
name: 'Agent Editor',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.EDITOR, // 3
|
||||
}),
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_manager',
|
||||
name: 'Agent Manager',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.MANAGER, // 7
|
||||
}),
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
name: 'Agent Owner',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.OWNER, // 15
|
||||
}),
|
||||
]);
|
||||
|
@ -267,7 +275,7 @@ describe('AccessRole Model Tests', () => {
|
|||
test('should find exact matching role', async () => {
|
||||
const role = await methods.getRoleForPermissions('agent', RoleBits.EDITOR);
|
||||
expect(role).toBeDefined();
|
||||
expect(role?.accessRoleId).toBe('agent_editor');
|
||||
expect(role?.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR);
|
||||
expect(role?.permBits).toBe(RoleBits.EDITOR);
|
||||
});
|
||||
|
||||
|
@ -278,7 +286,7 @@ describe('AccessRole Model Tests', () => {
|
|||
// Should return VIEWER (1) as closest matching role without exceeding the permission bits
|
||||
const role = await methods.getRoleForPermissions('agent', customPerm);
|
||||
expect(role).toBeDefined();
|
||||
expect(role?.accessRoleId).toBe('agent_viewer');
|
||||
expect(role?.accessRoleId).toBe(AccessRoleIds.AGENT_VIEWER);
|
||||
});
|
||||
|
||||
test('should return null when no compatible role is found', async () => {
|
||||
|
@ -301,7 +309,7 @@ describe('AccessRole Model Tests', () => {
|
|||
// Query for agent roles
|
||||
const agentRole = await methods.getRoleForPermissions('agent', RoleBits.VIEWER);
|
||||
expect(agentRole).toBeDefined();
|
||||
expect(agentRole?.accessRoleId).toBe('agent_viewer');
|
||||
expect(agentRole?.accessRoleId).toBe(AccessRoleIds.AGENT_VIEWER);
|
||||
|
||||
// Query for project roles
|
||||
const projectRole = await methods.getRoleForPermissions('project', RoleBits.VIEWER);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { AccessRoleIds, ResourceType, PermissionBits } from 'librechat-data-provider';
|
||||
import type { Model, Types, DeleteResult } from 'mongoose';
|
||||
import { RoleBits, PermissionBits } from '~/common';
|
||||
import type { IAccessRole } from '~/types';
|
||||
import { RoleBits } from '~/common';
|
||||
|
||||
export function createAccessRoleMethods(mongoose: typeof import('mongoose')) {
|
||||
/**
|
||||
|
@ -104,68 +105,45 @@ export function createAccessRoleMethods(mongoose: typeof import('mongoose')) {
|
|||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
const defaultRoles = [
|
||||
{
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
name: 'com_ui_role_viewer',
|
||||
description: 'com_ui_role_viewer_desc',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
name: 'com_ui_role_editor',
|
||||
description: 'com_ui_role_editor_desc',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.EDITOR,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
name: 'com_ui_role_owner',
|
||||
description: 'com_ui_role_owner_desc',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.OWNER,
|
||||
},
|
||||
// Prompt access roles
|
||||
{
|
||||
accessRoleId: 'prompt_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
name: 'com_ui_role_viewer',
|
||||
description: 'com_ui_role_viewer_desc',
|
||||
resourceType: 'prompt',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: RoleBits.VIEWER,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'prompt_editor',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
name: 'com_ui_role_editor',
|
||||
description: 'com_ui_role_editor_desc',
|
||||
resourceType: 'prompt',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: RoleBits.EDITOR,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'prompt_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
name: 'com_ui_role_owner',
|
||||
description: 'com_ui_role_owner_desc',
|
||||
resourceType: 'prompt',
|
||||
permBits: RoleBits.OWNER,
|
||||
},
|
||||
// PromptGroup access roles
|
||||
{
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
name: 'com_ui_role_viewer',
|
||||
description: 'com_ui_role_viewer_desc',
|
||||
resourceType: 'promptGroup',
|
||||
permBits: RoleBits.VIEWER,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'promptGroup_editor',
|
||||
name: 'com_ui_role_editor',
|
||||
description: 'com_ui_role_editor_desc',
|
||||
resourceType: 'promptGroup',
|
||||
permBits: RoleBits.EDITOR,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
name: 'com_ui_role_owner',
|
||||
description: 'com_ui_role_owner_desc',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: RoleBits.OWNER,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import mongoose from 'mongoose';
|
||||
import { ResourceType, PermissionBits } from 'librechat-data-provider';
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
||||
import { createAclEntryMethods } from './aclEntry';
|
||||
import { PermissionBits } from '~/common';
|
||||
import aclEntrySchema from '~/schema/aclEntry';
|
||||
import type * as t from '~/types';
|
||||
import { createAclEntryMethods } from './aclEntry';
|
||||
import aclEntrySchema from '~/schema/aclEntry';
|
||||
|
||||
let mongoServer: MongoMemoryServer;
|
||||
let AclEntry: mongoose.Model<t.IAclEntry>;
|
||||
|
@ -38,7 +38,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const entry = await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -59,7 +59,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const entry = await methods.grantPermission(
|
||||
'group',
|
||||
groupId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW | PermissionBits.EDIT,
|
||||
grantedById,
|
||||
|
@ -76,7 +76,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const entry = await methods.grantPermission(
|
||||
'public',
|
||||
null,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -93,7 +93,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -122,7 +122,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -130,7 +130,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'group',
|
||||
groupId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
grantedById,
|
||||
|
@ -138,7 +138,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'public',
|
||||
null,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -155,7 +155,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -163,7 +163,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'group',
|
||||
groupId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
grantedById,
|
||||
|
@ -173,7 +173,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'public',
|
||||
null,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
otherResourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -188,7 +188,7 @@ describe('AclEntry Model Tests', () => {
|
|||
|
||||
const entries = await methods.findEntriesByPrincipalsAndResource(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
);
|
||||
expect(entries).toHaveLength(2);
|
||||
|
@ -200,7 +200,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** User has VIEW permission */
|
||||
const hasViewPermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
);
|
||||
|
@ -209,7 +209,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** User doesn't have EDIT permission */
|
||||
const hasEditPermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
);
|
||||
|
@ -222,7 +222,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** Group has EDIT permission */
|
||||
const hasEditPermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
);
|
||||
|
@ -238,7 +238,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** User has VIEW and group has EDIT, together they should have both */
|
||||
const hasViewPermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
);
|
||||
|
@ -246,7 +246,7 @@ describe('AclEntry Model Tests', () => {
|
|||
|
||||
const hasEditPermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
);
|
||||
|
@ -255,7 +255,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** Neither has DELETE permission */
|
||||
const hasDeletePermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.DELETE,
|
||||
);
|
||||
|
@ -281,7 +281,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -292,7 +292,7 @@ describe('AclEntry Model Tests', () => {
|
|||
expect(entriesBefore).toHaveLength(1);
|
||||
|
||||
/** Revoke it */
|
||||
const result = await methods.revokePermission('user', userId, 'agent', resourceId);
|
||||
const result = await methods.revokePermission('user', userId, ResourceType.AGENT, resourceId);
|
||||
expect(result.deletedCount).toBe(1);
|
||||
|
||||
/** Verify it's gone */
|
||||
|
@ -305,7 +305,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -315,7 +315,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const updated = await methods.modifyPermissionBits(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
null,
|
||||
|
@ -330,7 +330,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW | PermissionBits.EDIT,
|
||||
grantedById,
|
||||
|
@ -340,7 +340,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const updated = await methods.modifyPermissionBits(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
null,
|
||||
PermissionBits.EDIT,
|
||||
|
@ -355,7 +355,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -387,7 +387,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId1,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -397,7 +397,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId2,
|
||||
PermissionBits.VIEW | PermissionBits.EDIT,
|
||||
grantedById,
|
||||
|
@ -407,7 +407,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'group',
|
||||
groupId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId3,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
@ -416,7 +416,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** Find resources with VIEW permission for user */
|
||||
const userViewableResources = await methods.findAccessibleResources(
|
||||
[{ principalType: 'user', principalId: userId }],
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
PermissionBits.VIEW,
|
||||
);
|
||||
|
||||
|
@ -431,7 +431,7 @@ describe('AclEntry Model Tests', () => {
|
|||
{ principalType: 'user', principalId: userId },
|
||||
{ principalType: 'group', principalId: groupId },
|
||||
],
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
PermissionBits.VIEW,
|
||||
);
|
||||
|
||||
|
@ -440,7 +440,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** Find resources with EDIT permission for user */
|
||||
const editableResources = await methods.findAccessibleResources(
|
||||
[{ principalType: 'user', principalId: userId }],
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
PermissionBits.EDIT,
|
||||
);
|
||||
|
||||
|
@ -467,7 +467,7 @@ describe('AclEntry Model Tests', () => {
|
|||
principalType: 'user',
|
||||
principalId: userId,
|
||||
principalModel: 'User',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: childResourceId,
|
||||
permBits: PermissionBits.VIEW,
|
||||
grantedBy: grantedById,
|
||||
|
@ -477,7 +477,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** Get effective permissions */
|
||||
const effective = await methods.getEffectivePermissions(
|
||||
[{ principalType: 'user', principalId: userId }],
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
childResourceId,
|
||||
);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ const accessRoleSchema = new Schema<IAccessRole>(
|
|||
description: String,
|
||||
resourceType: {
|
||||
type: String,
|
||||
enum: ['agent', 'project', 'file', 'prompt', 'promptGroup'],
|
||||
enum: ['agent', 'project', 'file', 'promptGroup'],
|
||||
required: true,
|
||||
default: 'agent',
|
||||
},
|
||||
|
|
|
@ -7,8 +7,8 @@ export type AclEntry = {
|
|||
principalId?: Types.ObjectId;
|
||||
/** The model name for the principal ('User' or 'Group') */
|
||||
principalModel?: 'User' | 'Group';
|
||||
/** The type of resource ('agent', 'project', 'file', 'prompt', 'promptGroup') */
|
||||
resourceType: 'agent' | 'project' | 'file' | 'prompt' | 'promptGroup';
|
||||
/** The type of resource ('agent', 'project', 'file', 'promptGroup') */
|
||||
resourceType: 'agent' | 'project' | 'file' | 'promptGroup';
|
||||
/** The ID of the resource */
|
||||
resourceId: Types.ObjectId;
|
||||
/** Permission bits for this entry */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue