mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +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 mongoose = require('mongoose');
|
||||||
const crypto = require('node:crypto');
|
const crypto = require('node:crypto');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
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 } =
|
const { GLOBAL_PROJECT_NAME, EPHEMERAL_AGENT_ID, mcp_delimiter } =
|
||||||
require('librechat-data-provider').Constants;
|
require('librechat-data-provider').Constants;
|
||||||
const {
|
const {
|
||||||
getProjectByName,
|
|
||||||
addAgentIdsToProject,
|
|
||||||
removeAgentIdsFromProject,
|
|
||||||
removeAgentFromAllProjects,
|
removeAgentFromAllProjects,
|
||||||
|
removeAgentIdsFromProject,
|
||||||
|
addAgentIdsToProject,
|
||||||
|
getProjectByName,
|
||||||
} = require('./Project');
|
} = require('./Project');
|
||||||
const { getCachedTools } = require('~/server/services/Config');
|
|
||||||
const { removeAllPermissions } = require('~/server/services/PermissionService');
|
const { removeAllPermissions } = require('~/server/services/PermissionService');
|
||||||
const { Agent } = require('~/db/models');
|
const { getCachedTools } = require('~/server/services/Config');
|
||||||
|
|
||||||
/**
|
|
||||||
* Category values are now imported from shared constants
|
|
||||||
*/
|
|
||||||
const { getActions } = require('./Action');
|
const { getActions } = require('./Action');
|
||||||
|
const { Agent } = require('~/db/models');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an agent with the provided data.
|
* Create an agent with the provided data.
|
||||||
|
@ -511,7 +507,7 @@ const deleteAgent = async (searchParameter) => {
|
||||||
if (agent) {
|
if (agent) {
|
||||||
await removeAgentFromAllProjects(agent.id);
|
await removeAgentFromAllProjects(agent.id);
|
||||||
await removeAllPermissions({
|
await removeAllPermissions({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ const mongoose = require('mongoose');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const { agentSchema } = require('@librechat/data-schemas');
|
const { agentSchema } = require('@librechat/data-schemas');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
|
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
getAgent,
|
getAgent,
|
||||||
loadAgent,
|
loadAgent,
|
||||||
|
@ -21,14 +22,14 @@ const {
|
||||||
updateAgent,
|
updateAgent,
|
||||||
deleteAgent,
|
deleteAgent,
|
||||||
getListAgents,
|
getListAgents,
|
||||||
|
revertAgentVersion,
|
||||||
updateAgentProjects,
|
updateAgentProjects,
|
||||||
addAgentResourceFile,
|
addAgentResourceFile,
|
||||||
removeAgentResourceFiles,
|
removeAgentResourceFiles,
|
||||||
generateActionMetadataHash,
|
generateActionMetadataHash,
|
||||||
revertAgentVersion,
|
|
||||||
} = require('./Agent');
|
} = require('./Agent');
|
||||||
const { getCachedTools } = require('~/server/services/Config');
|
|
||||||
const permissionService = require('~/server/services/PermissionService');
|
const permissionService = require('~/server/services/PermissionService');
|
||||||
|
const { getCachedTools } = require('~/server/services/Config');
|
||||||
const { AclEntry } = require('~/db/models');
|
const { AclEntry } = require('~/db/models');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -423,10 +424,10 @@ describe('models/Agent', () => {
|
||||||
|
|
||||||
// Create necessary access roles for agents
|
// Create necessary access roles for agents
|
||||||
await AccessRole.create({
|
await AccessRole.create({
|
||||||
accessRoleId: 'agent_owner',
|
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||||
name: 'Owner',
|
name: 'Owner',
|
||||||
description: 'Full control over agents',
|
description: 'Full control over agents',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: 15, // VIEW | EDIT | DELETE | SHARE
|
permBits: 15, // VIEW | EDIT | DELETE | SHARE
|
||||||
});
|
});
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
@ -501,15 +502,15 @@ describe('models/Agent', () => {
|
||||||
await permissionService.grantPermission({
|
await permissionService.grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: authorId,
|
principalId: authorId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_owner',
|
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||||
grantedBy: authorId,
|
grantedBy: authorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify ACL entry exists
|
// Verify ACL entry exists
|
||||||
const aclEntriesBefore = await AclEntry.find({
|
const aclEntriesBefore = await AclEntry.find({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
});
|
});
|
||||||
expect(aclEntriesBefore).toHaveLength(1);
|
expect(aclEntriesBefore).toHaveLength(1);
|
||||||
|
@ -523,7 +524,7 @@ describe('models/Agent', () => {
|
||||||
|
|
||||||
// Verify ACL entries are removed
|
// Verify ACL entries are removed
|
||||||
const aclEntriesAfter = await AclEntry.find({
|
const aclEntriesAfter = await AclEntry.find({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
});
|
});
|
||||||
expect(aclEntriesAfter).toHaveLength(0);
|
expect(aclEntriesAfter).toHaveLength(0);
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
|
||||||
const { createModels } = require('@librechat/data-schemas');
|
const { createModels } = require('@librechat/data-schemas');
|
||||||
const { getFiles, createFile } = require('./File');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
const { createAgent } = require('./Agent');
|
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||||
const { grantPermission } = require('~/server/services/PermissionService');
|
const { grantPermission } = require('~/server/services/PermissionService');
|
||||||
|
const { getFiles, createFile } = require('./File');
|
||||||
const { seedDefaultRoles } = require('~/models');
|
const { seedDefaultRoles } = require('~/models');
|
||||||
|
const { createAgent } = require('./Agent');
|
||||||
|
|
||||||
let File;
|
let File;
|
||||||
let Agent;
|
let Agent;
|
||||||
|
@ -116,9 +117,9 @@ describe('File Access Control', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: authorId,
|
grantedBy: authorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -233,9 +234,9 @@ describe('File Access Control', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: authorId,
|
grantedBy: authorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -291,9 +292,9 @@ describe('File Access Control', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: authorId,
|
grantedBy: authorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
const { ObjectId } = require('mongodb');
|
const { ObjectId } = require('mongodb');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { SystemRoles, SystemCategories, Constants } = require('librechat-data-provider');
|
|
||||||
const {
|
const {
|
||||||
getProjectByName,
|
Constants,
|
||||||
addGroupIdsToProject,
|
SystemRoles,
|
||||||
removeGroupIdsFromProject,
|
ResourceType,
|
||||||
|
SystemCategories,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
|
const {
|
||||||
removeGroupFromAllProjects,
|
removeGroupFromAllProjects,
|
||||||
|
removeGroupIdsFromProject,
|
||||||
|
addGroupIdsToProject,
|
||||||
|
getProjectByName,
|
||||||
} = require('./Project');
|
} = require('./Project');
|
||||||
const { removeAllPermissions } = require('~/server/services/PermissionService');
|
const { removeAllPermissions } = require('~/server/services/PermissionService');
|
||||||
const { PromptGroup, Prompt } = require('~/db/models');
|
const { PromptGroup, Prompt } = require('~/db/models');
|
||||||
|
@ -234,7 +239,7 @@ const deletePromptGroup = async ({ _id, author, role }) => {
|
||||||
await removeGroupFromAllProjects(_id);
|
await removeGroupFromAllProjects(_id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await removeAllPermissions({ resourceType: 'promptGroup', resourceId: _id });
|
await removeAllPermissions({ resourceType: ResourceType.PROMPTGROUP, resourceId: _id });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error removing promptGroup permissions:', error);
|
logger.error('Error removing promptGroup permissions:', error);
|
||||||
}
|
}
|
||||||
|
@ -428,16 +433,6 @@ module.exports = {
|
||||||
throw new Error('Failed to delete the prompt');
|
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 })
|
const remainingPrompts = await Prompt.find({ groupId })
|
||||||
.select('_id')
|
.select('_id')
|
||||||
.sort({ createdAt: 1 })
|
.sort({ createdAt: 1 })
|
||||||
|
@ -447,7 +442,7 @@ module.exports = {
|
||||||
// Remove all ACL entries for the promptGroup when deleting the last prompt
|
// Remove all ACL entries for the promptGroup when deleting the last prompt
|
||||||
try {
|
try {
|
||||||
await removeAllPermissions({
|
await removeAllPermissions({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: groupId,
|
resourceId: groupId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
const { ObjectId } = require('mongodb');
|
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { SystemRoles } = require('librechat-data-provider');
|
const { ObjectId } = require('mongodb');
|
||||||
const { logger, PermissionBits } = require('@librechat/data-schemas');
|
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
|
// Mock the config/connect module to prevent connection attempts during tests
|
||||||
jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true));
|
jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true));
|
||||||
|
@ -49,24 +54,24 @@ async function setupTestData() {
|
||||||
// Create access roles for promptGroups
|
// Create access roles for promptGroups
|
||||||
testRoles = {
|
testRoles = {
|
||||||
viewer: await AccessRole.create({
|
viewer: await AccessRole.create({
|
||||||
accessRoleId: 'promptGroup_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
name: 'Viewer',
|
name: 'Viewer',
|
||||||
description: 'Can view promptGroups',
|
description: 'Can view promptGroups',
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits: PermissionBits.VIEW,
|
permBits: PermissionBits.VIEW,
|
||||||
}),
|
}),
|
||||||
editor: await AccessRole.create({
|
editor: await AccessRole.create({
|
||||||
accessRoleId: 'promptGroup_editor',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||||
name: 'Editor',
|
name: 'Editor',
|
||||||
description: 'Can view and edit promptGroups',
|
description: 'Can view and edit promptGroups',
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits: PermissionBits.VIEW | PermissionBits.EDIT,
|
permBits: PermissionBits.VIEW | PermissionBits.EDIT,
|
||||||
}),
|
}),
|
||||||
owner: await AccessRole.create({
|
owner: await AccessRole.create({
|
||||||
accessRoleId: 'promptGroup_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
name: 'Owner',
|
name: 'Owner',
|
||||||
description: 'Full control over promptGroups',
|
description: 'Full control over promptGroups',
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits:
|
permBits:
|
||||||
PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
|
PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
|
||||||
}),
|
}),
|
||||||
|
@ -148,15 +153,15 @@ describe('Prompt ACL Permissions', () => {
|
||||||
await permissionService.grantPermission({
|
await permissionService.grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.owner._id,
|
principalId: testUsers.owner._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testGroup._id,
|
resourceId: testGroup._id,
|
||||||
accessRoleId: 'promptGroup_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check ACL entry
|
// Check ACL entry
|
||||||
const aclEntry = await AclEntry.findOne({
|
const aclEntry = await AclEntry.findOne({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testGroup._id,
|
resourceId: testGroup._id,
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.owner._id,
|
principalId: testUsers.owner._id,
|
||||||
|
@ -192,9 +197,9 @@ describe('Prompt ACL Permissions', () => {
|
||||||
await permissionService.grantPermission({
|
await permissionService.grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.owner._id,
|
principalId: testUsers.owner._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
accessRoleId: 'promptGroup_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -208,7 +213,7 @@ describe('Prompt ACL Permissions', () => {
|
||||||
it('owner should have full access to their prompt', async () => {
|
it('owner should have full access to their prompt', async () => {
|
||||||
const hasAccess = await permissionService.checkPermission({
|
const hasAccess = await permissionService.checkPermission({
|
||||||
userId: testUsers.owner._id,
|
userId: testUsers.owner._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
requiredPermission: PermissionBits.VIEW,
|
requiredPermission: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
@ -217,7 +222,7 @@ describe('Prompt ACL Permissions', () => {
|
||||||
|
|
||||||
const canEdit = await permissionService.checkPermission({
|
const canEdit = await permissionService.checkPermission({
|
||||||
userId: testUsers.owner._id,
|
userId: testUsers.owner._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
requiredPermission: PermissionBits.EDIT,
|
requiredPermission: PermissionBits.EDIT,
|
||||||
});
|
});
|
||||||
|
@ -230,22 +235,22 @@ describe('Prompt ACL Permissions', () => {
|
||||||
await permissionService.grantPermission({
|
await permissionService.grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.viewer._id,
|
principalId: testUsers.viewer._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
accessRoleId: 'promptGroup_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const canView = await permissionService.checkPermission({
|
const canView = await permissionService.checkPermission({
|
||||||
userId: testUsers.viewer._id,
|
userId: testUsers.viewer._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
requiredPermission: PermissionBits.VIEW,
|
requiredPermission: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
|
||||||
const canEdit = await permissionService.checkPermission({
|
const canEdit = await permissionService.checkPermission({
|
||||||
userId: testUsers.viewer._id,
|
userId: testUsers.viewer._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
requiredPermission: PermissionBits.EDIT,
|
requiredPermission: PermissionBits.EDIT,
|
||||||
});
|
});
|
||||||
|
@ -257,7 +262,7 @@ describe('Prompt ACL Permissions', () => {
|
||||||
it('user without permissions should have no access', async () => {
|
it('user without permissions should have no access', async () => {
|
||||||
const hasAccess = await permissionService.checkPermission({
|
const hasAccess = await permissionService.checkPermission({
|
||||||
userId: testUsers.noAccess._id,
|
userId: testUsers.noAccess._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
requiredPermission: PermissionBits.VIEW,
|
requiredPermission: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
@ -270,7 +275,7 @@ describe('Prompt ACL Permissions', () => {
|
||||||
// The middleware layer handles admin bypass, not the permission service
|
// The middleware layer handles admin bypass, not the permission service
|
||||||
const hasAccess = await permissionService.checkPermission({
|
const hasAccess = await permissionService.checkPermission({
|
||||||
userId: testUsers.admin._id,
|
userId: testUsers.admin._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
requiredPermission: PermissionBits.VIEW,
|
requiredPermission: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
@ -278,7 +283,7 @@ describe('Prompt ACL Permissions', () => {
|
||||||
// Without explicit permissions, even admin won't have access at this layer
|
// Without explicit permissions, even admin won't have access at this layer
|
||||||
expect(hasAccess).toBe(false);
|
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
|
// which checks req.user.role === SystemRoles.ADMIN
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -352,16 +357,16 @@ describe('Prompt ACL Permissions', () => {
|
||||||
await permissionService.grantPermission({
|
await permissionService.grantPermission({
|
||||||
principalType: 'group',
|
principalType: 'group',
|
||||||
principalId: testGroups.editors._id,
|
principalId: testGroups.editors._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
accessRoleId: 'promptGroup_editor',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if group member has access
|
// Check if group member has access
|
||||||
const hasAccess = await permissionService.checkPermission({
|
const hasAccess = await permissionService.checkPermission({
|
||||||
userId: testUsers.editor._id,
|
userId: testUsers.editor._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
requiredPermission: PermissionBits.EDIT,
|
requiredPermission: PermissionBits.EDIT,
|
||||||
});
|
});
|
||||||
|
@ -371,7 +376,7 @@ describe('Prompt ACL Permissions', () => {
|
||||||
// Check that non-member doesn't have access
|
// Check that non-member doesn't have access
|
||||||
const nonMemberAccess = await permissionService.checkPermission({
|
const nonMemberAccess = await permissionService.checkPermission({
|
||||||
userId: testUsers.viewer._id,
|
userId: testUsers.viewer._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
requiredPermission: PermissionBits.EDIT,
|
requiredPermission: PermissionBits.EDIT,
|
||||||
});
|
});
|
||||||
|
@ -420,9 +425,9 @@ describe('Prompt ACL Permissions', () => {
|
||||||
await permissionService.grantPermission({
|
await permissionService.grantPermission({
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
principalId: null,
|
principalId: null,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: publicPromptGroup._id,
|
resourceId: publicPromptGroup._id,
|
||||||
accessRoleId: 'promptGroup_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -430,9 +435,9 @@ describe('Prompt ACL Permissions', () => {
|
||||||
await permissionService.grantPermission({
|
await permissionService.grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.owner._id,
|
principalId: testUsers.owner._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: privatePromptGroup._id,
|
resourceId: privatePromptGroup._id,
|
||||||
accessRoleId: 'promptGroup_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -446,7 +451,7 @@ describe('Prompt ACL Permissions', () => {
|
||||||
it('public prompt should be accessible to any user', async () => {
|
it('public prompt should be accessible to any user', async () => {
|
||||||
const hasAccess = await permissionService.checkPermission({
|
const hasAccess = await permissionService.checkPermission({
|
||||||
userId: testUsers.noAccess._id,
|
userId: testUsers.noAccess._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: publicPromptGroup._id,
|
resourceId: publicPromptGroup._id,
|
||||||
requiredPermission: PermissionBits.VIEW,
|
requiredPermission: PermissionBits.VIEW,
|
||||||
includePublic: true,
|
includePublic: true,
|
||||||
|
@ -458,7 +463,7 @@ describe('Prompt ACL Permissions', () => {
|
||||||
it('private prompt should not be accessible to unauthorized users', async () => {
|
it('private prompt should not be accessible to unauthorized users', async () => {
|
||||||
const hasAccess = await permissionService.checkPermission({
|
const hasAccess = await permissionService.checkPermission({
|
||||||
userId: testUsers.noAccess._id,
|
userId: testUsers.noAccess._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: privatePromptGroup._id,
|
resourceId: privatePromptGroup._id,
|
||||||
requiredPermission: PermissionBits.VIEW,
|
requiredPermission: PermissionBits.VIEW,
|
||||||
includePublic: true,
|
includePublic: true,
|
||||||
|
@ -501,15 +506,15 @@ describe('Prompt ACL Permissions', () => {
|
||||||
await permissionService.grantPermission({
|
await permissionService.grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.owner._id,
|
principalId: testUsers.owner._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
accessRoleId: 'promptGroup_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify ACL entry exists
|
// Verify ACL entry exists
|
||||||
const beforeDelete = await AclEntry.find({
|
const beforeDelete = await AclEntry.find({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
});
|
});
|
||||||
expect(beforeDelete).toHaveLength(1);
|
expect(beforeDelete).toHaveLength(1);
|
||||||
|
@ -524,7 +529,7 @@ describe('Prompt ACL Permissions', () => {
|
||||||
|
|
||||||
// Verify ACL entries are removed
|
// Verify ACL entries are removed
|
||||||
const aclEntries = await AclEntry.find({
|
const aclEntries = await AclEntry.find({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testPromptGroup._id,
|
resourceId: testPromptGroup._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
const { ObjectId } = require('mongodb');
|
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { logger, PermissionBits } = require('@librechat/data-schemas');
|
const { ObjectId } = require('mongodb');
|
||||||
const { Constants } = require('librechat-data-provider');
|
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
|
// Mock the config/connect module to prevent connection attempts during tests
|
||||||
jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true));
|
jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true));
|
||||||
|
@ -49,27 +54,27 @@ describe('PromptGroup Migration Script', () => {
|
||||||
|
|
||||||
// Create promptGroup access roles
|
// Create promptGroup access roles
|
||||||
ownerRole = await AccessRole.create({
|
ownerRole = await AccessRole.create({
|
||||||
accessRoleId: 'promptGroup_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
name: 'Owner',
|
name: 'Owner',
|
||||||
description: 'Full control over promptGroups',
|
description: 'Full control over promptGroups',
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits:
|
permBits:
|
||||||
PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
|
PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
|
||||||
});
|
});
|
||||||
|
|
||||||
viewerRole = await AccessRole.create({
|
viewerRole = await AccessRole.create({
|
||||||
accessRoleId: 'promptGroup_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
name: 'Viewer',
|
name: 'Viewer',
|
||||||
description: 'Can view promptGroups',
|
description: 'Can view promptGroups',
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits: PermissionBits.VIEW,
|
permBits: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
|
||||||
await AccessRole.create({
|
await AccessRole.create({
|
||||||
accessRoleId: 'promptGroup_editor',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||||
name: 'Editor',
|
name: 'Editor',
|
||||||
description: 'Can view and edit promptGroups',
|
description: 'Can view and edit promptGroups',
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits: PermissionBits.VIEW | PermissionBits.EDIT,
|
permBits: PermissionBits.VIEW | PermissionBits.EDIT,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -103,7 +108,7 @@ describe('PromptGroup Migration Script', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create private prompt group (not in any project)
|
// Create private prompt group (not in any project)
|
||||||
const privatePromptGroup = await PromptGroup.create({
|
await PromptGroup.create({
|
||||||
name: 'Private Group',
|
name: 'Private Group',
|
||||||
author: testOwner._id,
|
author: testOwner._id,
|
||||||
authorName: testOwner.name,
|
authorName: testOwner.name,
|
||||||
|
@ -151,7 +156,7 @@ describe('PromptGroup Migration Script', () => {
|
||||||
|
|
||||||
// Check global promptGroup permissions
|
// Check global promptGroup permissions
|
||||||
const globalOwnerEntry = await AclEntry.findOne({
|
const globalOwnerEntry = await AclEntry.findOne({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: globalPromptGroup._id,
|
resourceId: globalPromptGroup._id,
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testOwner._id,
|
principalId: testOwner._id,
|
||||||
|
@ -160,7 +165,7 @@ describe('PromptGroup Migration Script', () => {
|
||||||
expect(globalOwnerEntry.permBits).toBe(ownerRole.permBits);
|
expect(globalOwnerEntry.permBits).toBe(ownerRole.permBits);
|
||||||
|
|
||||||
const globalPublicEntry = await AclEntry.findOne({
|
const globalPublicEntry = await AclEntry.findOne({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: globalPromptGroup._id,
|
resourceId: globalPromptGroup._id,
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
});
|
});
|
||||||
|
@ -169,7 +174,7 @@ describe('PromptGroup Migration Script', () => {
|
||||||
|
|
||||||
// Check private promptGroup permissions
|
// Check private promptGroup permissions
|
||||||
const privateOwnerEntry = await AclEntry.findOne({
|
const privateOwnerEntry = await AclEntry.findOne({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: privatePromptGroup._id,
|
resourceId: privatePromptGroup._id,
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testOwner._id,
|
principalId: testOwner._id,
|
||||||
|
@ -178,7 +183,7 @@ describe('PromptGroup Migration Script', () => {
|
||||||
expect(privateOwnerEntry.permBits).toBe(ownerRole.permBits);
|
expect(privateOwnerEntry.permBits).toBe(ownerRole.permBits);
|
||||||
|
|
||||||
const privatePublicEntry = await AclEntry.findOne({
|
const privatePublicEntry = await AclEntry.findOne({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: privatePromptGroup._id,
|
resourceId: privatePromptGroup._id,
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
});
|
});
|
||||||
|
@ -206,7 +211,7 @@ describe('PromptGroup Migration Script', () => {
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testOwner._id,
|
principalId: testOwner._id,
|
||||||
principalModel: 'User',
|
principalModel: 'User',
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: promptGroup1._id,
|
resourceId: promptGroup1._id,
|
||||||
permBits: ownerRole.permBits,
|
permBits: ownerRole.permBits,
|
||||||
roleId: ownerRole._id,
|
roleId: ownerRole._id,
|
||||||
|
@ -222,7 +227,7 @@ describe('PromptGroup Migration Script', () => {
|
||||||
|
|
||||||
// Verify promptGroup2 now has permissions
|
// Verify promptGroup2 now has permissions
|
||||||
const group2Entry = await AclEntry.findOne({
|
const group2Entry = await AclEntry.findOne({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: promptGroup2._id,
|
resourceId: promptGroup2._id,
|
||||||
});
|
});
|
||||||
expect(group2Entry).toBeTruthy();
|
expect(group2Entry).toBeTruthy();
|
||||||
|
@ -259,7 +264,7 @@ describe('PromptGroup Migration Script', () => {
|
||||||
|
|
||||||
// Verify the promptGroup has permissions
|
// Verify the promptGroup has permissions
|
||||||
const groupEntry = await AclEntry.findOne({
|
const groupEntry = await AclEntry.findOne({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: promptGroup._id,
|
resourceId: promptGroup._id,
|
||||||
});
|
});
|
||||||
expect(groupEntry).toBeTruthy();
|
expect(groupEntry).toBeTruthy();
|
||||||
|
|
|
@ -4,12 +4,13 @@
|
||||||
|
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
|
const { ResourceType } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
getAvailableRoles,
|
|
||||||
ensurePrincipalExists,
|
|
||||||
getEffectivePermissions,
|
|
||||||
ensureGroupPrincipalExists,
|
|
||||||
bulkUpdateResourcePermissions,
|
bulkUpdateResourcePermissions,
|
||||||
|
ensureGroupPrincipalExists,
|
||||||
|
getEffectivePermissions,
|
||||||
|
ensurePrincipalExists,
|
||||||
|
getAvailableRoles,
|
||||||
} = require('~/server/services/PermissionService');
|
} = require('~/server/services/PermissionService');
|
||||||
const { AclEntry } = require('~/db/models');
|
const { AclEntry } = require('~/db/models');
|
||||||
const {
|
const {
|
||||||
|
@ -18,8 +19,8 @@ const {
|
||||||
calculateRelevanceScore,
|
calculateRelevanceScore,
|
||||||
} = require('~/models');
|
} = require('~/models');
|
||||||
const {
|
const {
|
||||||
searchEntraIdPrincipals,
|
|
||||||
entraIdPrincipalFeatureEnabled,
|
entraIdPrincipalFeatureEnabled,
|
||||||
|
searchEntraIdPrincipals,
|
||||||
} = require('~/server/services/GraphApiService');
|
} = require('~/server/services/GraphApiService');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,6 +28,18 @@ const {
|
||||||
* Delegates validation and logic to PermissionService
|
* 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)
|
* Bulk update permissions for a resource (grant, update, remove)
|
||||||
* @route PUT /api/{resourceType}/{resourceId}/permissions
|
* @route PUT /api/{resourceType}/{resourceId}/permissions
|
||||||
|
@ -41,6 +54,8 @@ const {
|
||||||
const updateResourcePermissions = async (req, res) => {
|
const updateResourcePermissions = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { resourceType, resourceId } = req.params;
|
const { resourceType, resourceId } = req.params;
|
||||||
|
validateResourceType(resourceType);
|
||||||
|
|
||||||
/** @type {TUpdateResourcePermissionsRequest} */
|
/** @type {TUpdateResourcePermissionsRequest} */
|
||||||
const { updated, removed, public: isPublic, publicAccessRoleId } = req.body;
|
const { updated, removed, public: isPublic, publicAccessRoleId } = req.body;
|
||||||
const { id: userId } = req.user;
|
const { id: userId } = req.user;
|
||||||
|
@ -163,6 +178,7 @@ const updateResourcePermissions = async (req, res) => {
|
||||||
const getResourcePermissions = async (req, res) => {
|
const getResourcePermissions = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { resourceType, resourceId } = req.params;
|
const { resourceType, resourceId } = req.params;
|
||||||
|
validateResourceType(resourceType);
|
||||||
|
|
||||||
// Use aggregation pipeline for efficient single-query data retrieval
|
// Use aggregation pipeline for efficient single-query data retrieval
|
||||||
const results = await AclEntry.aggregate([
|
const results = await AclEntry.aggregate([
|
||||||
|
@ -278,6 +294,7 @@ const getResourcePermissions = async (req, res) => {
|
||||||
const getResourceRoles = async (req, res) => {
|
const getResourceRoles = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { resourceType } = req.params;
|
const { resourceType } = req.params;
|
||||||
|
validateResourceType(resourceType);
|
||||||
|
|
||||||
const roles = await getAvailableRoles({ resourceType });
|
const roles = await getAvailableRoles({ resourceType });
|
||||||
|
|
||||||
|
@ -305,6 +322,8 @@ const getResourceRoles = async (req, res) => {
|
||||||
const getUserEffectivePermissions = async (req, res) => {
|
const getUserEffectivePermissions = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { resourceType, resourceId } = req.params;
|
const { resourceType, resourceId } = req.params;
|
||||||
|
validateResourceType(resourceType);
|
||||||
|
|
||||||
const { id: userId } = req.user;
|
const { id: userId } = req.user;
|
||||||
|
|
||||||
const permissionBits = await getEffectivePermissions({
|
const permissionBits = await getEffectivePermissions({
|
||||||
|
|
|
@ -1,30 +1,33 @@
|
||||||
const { z } = require('zod');
|
const { z } = require('zod');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
const { nanoid } = require('nanoid');
|
const { nanoid } = require('nanoid');
|
||||||
const { logger, PermissionBits } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { agentCreateSchema, agentUpdateSchema } = require('@librechat/api');
|
const { agentCreateSchema, agentUpdateSchema } = require('@librechat/api');
|
||||||
const {
|
const {
|
||||||
Tools,
|
Tools,
|
||||||
SystemRoles,
|
SystemRoles,
|
||||||
FileSources,
|
FileSources,
|
||||||
|
ResourceType,
|
||||||
|
AccessRoleIds,
|
||||||
EToolResources,
|
EToolResources,
|
||||||
actionDelimiter,
|
actionDelimiter,
|
||||||
|
PermissionBits,
|
||||||
removeNullishValues,
|
removeNullishValues,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
getAgent,
|
|
||||||
createAgent,
|
|
||||||
updateAgent,
|
|
||||||
deleteAgent,
|
|
||||||
getListAgentsByAccess,
|
getListAgentsByAccess,
|
||||||
countPromotedAgents,
|
countPromotedAgents,
|
||||||
revertAgentVersion,
|
revertAgentVersion,
|
||||||
|
createAgent,
|
||||||
|
updateAgent,
|
||||||
|
deleteAgent,
|
||||||
|
getAgent,
|
||||||
} = require('~/models/Agent');
|
} = require('~/models/Agent');
|
||||||
const {
|
const {
|
||||||
grantPermission,
|
|
||||||
findAccessibleResources,
|
|
||||||
findPubliclyAccessibleResources,
|
findPubliclyAccessibleResources,
|
||||||
|
findAccessibleResources,
|
||||||
hasPublicPermission,
|
hasPublicPermission,
|
||||||
|
grantPermission,
|
||||||
} = require('~/server/services/PermissionService');
|
} = require('~/server/services/PermissionService');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
||||||
|
@ -79,9 +82,9 @@ const createAgentHandler = async (req, res) => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_owner',
|
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||||
grantedBy: userId,
|
grantedBy: userId,
|
||||||
});
|
});
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -146,7 +149,7 @@ const getAgentHandler = async (req, res, expandProperties = false) => {
|
||||||
|
|
||||||
// Check if agent is public
|
// Check if agent is public
|
||||||
const isPublic = await hasPublicPermission({
|
const isPublic = await hasPublicPermission({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
requiredPermissions: PermissionBits.VIEW,
|
requiredPermissions: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
@ -345,9 +348,9 @@ const duplicateAgentHandler = async (req, res) => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: newAgent._id,
|
resourceId: newAgent._id,
|
||||||
accessRoleId: 'agent_owner',
|
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||||
grantedBy: userId,
|
grantedBy: userId,
|
||||||
});
|
});
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -440,11 +443,11 @@ const getListAgentsHandler = async (req, res) => {
|
||||||
// Get agent IDs the user has VIEW access to via ACL
|
// Get agent IDs the user has VIEW access to via ACL
|
||||||
const accessibleIds = await findAccessibleResources({
|
const accessibleIds = await findAccessibleResources({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermissions: requiredPermission,
|
requiredPermissions: requiredPermission,
|
||||||
});
|
});
|
||||||
const publiclyAccessibleIds = await findPubliclyAccessibleResources({
|
const publiclyAccessibleIds = await findPubliclyAccessibleResources({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermissions: PermissionBits.VIEW,
|
requiredPermissions: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
// Use the new ACL-aware function
|
// Use the new ACL-aware function
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { logger } = require('@librechat/data-schemas');
|
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 { canAccessResource } = require('./canAccessResource');
|
||||||
const { getAgent } = require('~/models/Agent');
|
const { getAgent } = require('~/models/Agent');
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ const canAccessAgentFromBody = (options) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const agentAccessMiddleware = canAccessResource({
|
const agentAccessMiddleware = canAccessResource({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermission,
|
requiredPermission,
|
||||||
resourceIdParam: 'agent_id', // This will be ignored since we use custom resolver
|
resourceIdParam: 'agent_id', // This will be ignored since we use custom resolver
|
||||||
idResolver: () => resolveAgentIdFromBody(agentId),
|
idResolver: () => resolveAgentIdFromBody(agentId),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const { getAgent } = require('~/models/Agent');
|
const { ResourceType } = require('librechat-data-provider');
|
||||||
const { canAccessResource } = require('./canAccessResource');
|
const { canAccessResource } = require('./canAccessResource');
|
||||||
|
const { getAgent } = require('~/models/Agent');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent ID resolver function
|
* Agent ID resolver function
|
||||||
|
@ -46,7 +47,7 @@ const canAccessAgentResource = (options) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return canAccessResource({
|
return canAccessResource({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermission,
|
requiredPermission,
|
||||||
resourceIdParam,
|
resourceIdParam,
|
||||||
idResolver: resolveAgentId,
|
idResolver: resolveAgentId,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
|
const { ResourceType } = require('librechat-data-provider');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
const { canAccessAgentResource } = require('./canAccessAgentResource');
|
const { canAccessAgentResource } = require('./canAccessAgentResource');
|
||||||
const { User, Role, AclEntry } = require('~/db/models');
|
const { User, Role, AclEntry } = require('~/db/models');
|
||||||
|
@ -99,7 +100,7 @@ describe('canAccessAgentResource middleware', () => {
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUser._id,
|
principalId: testUser._id,
|
||||||
principalModel: 'User',
|
principalModel: 'User',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
permBits: 15, // All permissions (1+2+4+8)
|
permBits: 15, // All permissions (1+2+4+8)
|
||||||
grantedBy: testUser._id,
|
grantedBy: testUser._id,
|
||||||
|
@ -136,7 +137,7 @@ describe('canAccessAgentResource middleware', () => {
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: otherUser._id,
|
principalId: otherUser._id,
|
||||||
principalModel: 'User',
|
principalModel: 'User',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
permBits: 15, // All permissions
|
permBits: 15, // All permissions
|
||||||
grantedBy: otherUser._id,
|
grantedBy: otherUser._id,
|
||||||
|
@ -177,7 +178,7 @@ describe('canAccessAgentResource middleware', () => {
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUser._id,
|
principalId: testUser._id,
|
||||||
principalModel: 'User',
|
principalModel: 'User',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
permBits: 1, // VIEW permission
|
permBits: 1, // VIEW permission
|
||||||
grantedBy: otherUser._id,
|
grantedBy: otherUser._id,
|
||||||
|
@ -214,7 +215,7 @@ describe('canAccessAgentResource middleware', () => {
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUser._id,
|
principalId: testUser._id,
|
||||||
principalModel: 'User',
|
principalModel: 'User',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
permBits: 1, // VIEW permission only
|
permBits: 1, // VIEW permission only
|
||||||
grantedBy: otherUser._id,
|
grantedBy: otherUser._id,
|
||||||
|
@ -261,7 +262,7 @@ describe('canAccessAgentResource middleware', () => {
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUser._id,
|
principalId: testUser._id,
|
||||||
principalModel: 'User',
|
principalModel: 'User',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
permBits: 15, // All permissions
|
permBits: 15, // All permissions
|
||||||
grantedBy: testUser._id,
|
grantedBy: testUser._id,
|
||||||
|
@ -297,7 +298,7 @@ describe('canAccessAgentResource middleware', () => {
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUser._id,
|
principalId: testUser._id,
|
||||||
principalModel: 'User',
|
principalModel: 'User',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
permBits: 15, // All permissions (1+2+4+8)
|
permBits: 15, // All permissions (1+2+4+8)
|
||||||
grantedBy: testUser._id,
|
grantedBy: testUser._id,
|
||||||
|
@ -357,7 +358,7 @@ describe('canAccessAgentResource middleware', () => {
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUser._id,
|
principalId: testUser._id,
|
||||||
principalModel: 'User',
|
principalModel: 'User',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
permBits: 15, // All permissions
|
permBits: 15, // All permissions
|
||||||
grantedBy: testUser._id,
|
grantedBy: testUser._id,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const { getPromptGroup } = require('~/models/Prompt');
|
const { ResourceType } = require('librechat-data-provider');
|
||||||
const { canAccessResource } = require('./canAccessResource');
|
const { canAccessResource } = require('./canAccessResource');
|
||||||
|
const { getPromptGroup } = require('~/models/Prompt');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PromptGroup ID resolver function
|
* PromptGroup ID resolver function
|
||||||
|
@ -48,7 +49,7 @@ const canAccessPromptGroupResource = (options) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return canAccessResource({
|
return canAccessResource({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
requiredPermission,
|
requiredPermission,
|
||||||
resourceIdParam,
|
resourceIdParam,
|
||||||
idResolver: resolvePromptGroupId,
|
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 { canAccessResource } = require('./canAccessResource');
|
||||||
|
const { getPrompt } = require('~/models/Prompt');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt to PromptGroup ID resolver function
|
* Prompt to PromptGroup ID resolver function
|
||||||
|
@ -42,7 +43,7 @@ const canAccessPromptViaGroup = (options) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return canAccessResource({
|
return canAccessResource({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
requiredPermission,
|
requiredPermission,
|
||||||
resourceIdParam,
|
resourceIdParam,
|
||||||
idResolver: resolvePromptToGroupId,
|
idResolver: resolvePromptToGroupId,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
const { logger } = require('@librechat/data-schemas');
|
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 { getEffectivePermissions } = require('~/server/services/PermissionService');
|
||||||
const { getFiles } = require('~/models/File');
|
|
||||||
const { getAgent } = require('~/models/Agent');
|
const { getAgent } = require('~/models/Agent');
|
||||||
|
const { getFiles } = require('~/models/File');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if user has access to a file through agent permissions
|
* Checks if user has access to a file through agent permissions
|
||||||
|
@ -35,11 +35,11 @@ const checkAgentBasedFileAccess = async (userId, fileId) => {
|
||||||
try {
|
try {
|
||||||
const permissions = await getEffectivePermissions({
|
const permissions = await getEffectivePermissions({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id || agent.id,
|
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}`);
|
logger.debug(`[fileAccess] User ${userId} has VIEW permissions on agent ${agent.id}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const { canAccessResource } = require('./canAccessResource');
|
const { canAccessResource } = require('./canAccessResource');
|
||||||
const { canAccessAgentResource } = require('./canAccessAgentResource');
|
const { canAccessAgentResource } = require('./canAccessAgentResource');
|
||||||
const { canAccessAgentFromBody } = require('./canAccessAgentFromBody');
|
const { canAccessAgentFromBody } = require('./canAccessAgentFromBody');
|
||||||
const { canAccessPromptResource } = require('./canAccessPromptResource');
|
|
||||||
const { canAccessPromptViaGroup } = require('./canAccessPromptViaGroup');
|
const { canAccessPromptViaGroup } = require('./canAccessPromptViaGroup');
|
||||||
const { canAccessPromptGroupResource } = require('./canAccessPromptGroupResource');
|
const { canAccessPromptGroupResource } = require('./canAccessPromptGroupResource');
|
||||||
|
|
||||||
|
@ -9,7 +8,6 @@ module.exports = {
|
||||||
canAccessResource,
|
canAccessResource,
|
||||||
canAccessAgentResource,
|
canAccessAgentResource,
|
||||||
canAccessAgentFromBody,
|
canAccessAgentFromBody,
|
||||||
canAccessPromptResource,
|
|
||||||
canAccessPromptViaGroup,
|
canAccessPromptViaGroup,
|
||||||
canAccessPromptGroupResource,
|
canAccessPromptGroupResource,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { PermissionBits } = require('@librechat/data-schemas');
|
const { ResourceType, PermissionBits } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
getUserEffectivePermissions,
|
getUserEffectivePermissions,
|
||||||
updateResourcePermissions,
|
updateResourcePermissions,
|
||||||
|
@ -49,19 +49,17 @@ router.put(
|
||||||
// Use middleware that dynamically handles resource type and permissions
|
// Use middleware that dynamically handles resource type and permissions
|
||||||
(req, res, next) => {
|
(req, res, next) => {
|
||||||
const { resourceType } = req.params;
|
const { resourceType } = req.params;
|
||||||
|
|
||||||
// Define resource-specific middleware based on resourceType
|
|
||||||
let middleware;
|
let middleware;
|
||||||
|
|
||||||
if (resourceType === 'agent') {
|
if (resourceType === ResourceType.AGENT) {
|
||||||
middleware = canAccessResource({
|
middleware = canAccessResource({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermission: PermissionBits.SHARE,
|
requiredPermission: PermissionBits.SHARE,
|
||||||
resourceIdParam: 'resourceId',
|
resourceIdParam: 'resourceId',
|
||||||
});
|
});
|
||||||
} else if (resourceType === 'promptGroup') {
|
} else if (resourceType === ResourceType.PROMPTGROUP) {
|
||||||
middleware = canAccessResource({
|
middleware = canAccessResource({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
requiredPermission: PermissionBits.SHARE,
|
requiredPermission: PermissionBits.SHARE,
|
||||||
resourceIdParam: 'resourceId',
|
resourceIdParam: 'resourceId',
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { nanoid } = require('nanoid');
|
const { nanoid } = require('nanoid');
|
||||||
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { generateCheckAccess } = require('@librechat/api');
|
const { generateCheckAccess } = require('@librechat/api');
|
||||||
const { logger, PermissionBits } = require('@librechat/data-schemas');
|
|
||||||
const {
|
const {
|
||||||
Permissions,
|
Permissions,
|
||||||
|
ResourceType,
|
||||||
PermissionTypes,
|
PermissionTypes,
|
||||||
actionDelimiter,
|
actionDelimiter,
|
||||||
|
PermissionBits,
|
||||||
removeNullishValues,
|
removeNullishValues,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const { encryptMetadata, domainParser } = require('~/server/services/ActionService');
|
const { encryptMetadata, domainParser } = require('~/server/services/ActionService');
|
||||||
const { findAccessibleResources } = require('~/server/services/PermissionService');
|
const { findAccessibleResources } = require('~/server/services/PermissionService');
|
||||||
|
const { getAgent, updateAgent, getListAgentsByAccess } = require('~/models/Agent');
|
||||||
const { updateAction, getActions, deleteAction } = require('~/models/Action');
|
const { updateAction, getActions, deleteAction } = require('~/models/Action');
|
||||||
const { isActionDomainAllowed } = require('~/server/services/domains');
|
const { isActionDomainAllowed } = require('~/server/services/domains');
|
||||||
const { canAccessAgentResource } = require('~/server/middleware');
|
const { canAccessAgentResource } = require('~/server/middleware');
|
||||||
const { getAgent, updateAgent } = require('~/models/Agent');
|
|
||||||
const { getRoleByName } = require('~/models/Role');
|
const { getRoleByName } = require('~/models/Role');
|
||||||
const { getListAgentsByAccess } = require('~/models/Agent');
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ router.get('/', async (req, res) => {
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
const editableAgentObjectIds = await findAccessibleResources({
|
const editableAgentObjectIds = await findAccessibleResources({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermissions: PermissionBits.EDIT,
|
requiredPermissions: PermissionBits.EDIT,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { PermissionBits } = require('@librechat/data-schemas');
|
|
||||||
const { generateCheckAccess, skipAgentCheck } = require('@librechat/api');
|
const { generateCheckAccess, skipAgentCheck } = require('@librechat/api');
|
||||||
const { PermissionTypes, Permissions } = require('librechat-data-provider');
|
const { PermissionTypes, Permissions, PermissionBits } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
setHeaders,
|
setHeaders,
|
||||||
moderateText,
|
moderateText,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { generateCheckAccess } = require('@librechat/api');
|
const { generateCheckAccess } = require('@librechat/api');
|
||||||
const { PermissionBits } = require('@librechat/data-schemas');
|
const { PermissionTypes, Permissions, PermissionBits } = require('librechat-data-provider');
|
||||||
const { PermissionTypes, Permissions } = require('librechat-data-provider');
|
|
||||||
const { requireJwtAuth, canAccessAgentResource } = require('~/server/middleware');
|
const { requireJwtAuth, canAccessAgentResource } = require('~/server/middleware');
|
||||||
const v1 = require('~/server/controllers/agents/v1');
|
const v1 = require('~/server/controllers/agents/v1');
|
||||||
const { getRoleByName } = require('~/models/Role');
|
const { getRoleByName } = require('~/models/Role');
|
||||||
|
|
|
@ -4,6 +4,7 @@ const mongoose = require('mongoose');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
const { createMethods } = require('@librechat/data-schemas');
|
const { createMethods } = require('@librechat/data-schemas');
|
||||||
|
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||||
const { createAgent } = require('~/models/Agent');
|
const { createAgent } = require('~/models/Agent');
|
||||||
const { createFile } = require('~/models/File');
|
const { createFile } = require('~/models/File');
|
||||||
|
|
||||||
|
@ -186,9 +187,9 @@ describe('File Routes - Agent Files Endpoint', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: otherUserId,
|
principalId: otherUserId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: authorId,
|
grantedBy: authorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -241,9 +242,9 @@ describe('File Routes - Agent Files Endpoint', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: otherUserId,
|
principalId: otherUserId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: authorId,
|
grantedBy: authorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,9 @@ const {
|
||||||
isUUID,
|
isUUID,
|
||||||
CacheKeys,
|
CacheKeys,
|
||||||
FileSources,
|
FileSources,
|
||||||
PERMISSION_BITS,
|
ResourceType,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
|
PermissionBits,
|
||||||
isAgentsEndpoint,
|
isAgentsEndpoint,
|
||||||
checkOpenAIStorage,
|
checkOpenAIStorage,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
|
@ -17,6 +18,7 @@ const {
|
||||||
processDeleteRequest,
|
processDeleteRequest,
|
||||||
processAgentFileUpload,
|
processAgentFileUpload,
|
||||||
} = require('~/server/services/Files/process');
|
} = require('~/server/services/Files/process');
|
||||||
|
const { fileAccess } = require('~/server/middleware/accessResources/fileAccess');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
||||||
const { checkPermission } = require('~/server/services/PermissionService');
|
const { 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 { refreshS3FileUrls } = require('~/server/services/Files/S3/crud');
|
||||||
const { hasAccessToFilesViaAgent } = require('~/server/services/Files');
|
const { hasAccessToFilesViaAgent } = require('~/server/services/Files');
|
||||||
const { getFiles, batchUpdateFiles } = require('~/models/File');
|
const { getFiles, batchUpdateFiles } = require('~/models/File');
|
||||||
|
const { cleanFileName } = require('~/server/utils/files');
|
||||||
const { getAssistant } = require('~/models/Assistant');
|
const { getAssistant } = require('~/models/Assistant');
|
||||||
const { getAgent } = require('~/models/Agent');
|
const { getAgent } = require('~/models/Agent');
|
||||||
const { cleanFileName } = require('~/server/utils/files');
|
|
||||||
const { getLogStores } = require('~/cache');
|
const { getLogStores } = require('~/cache');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
const { fileAccess } = require('~/server/middleware/accessResources/fileAccess');
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
@ -78,9 +79,9 @@ router.get('/agent/:agent_id', async (req, res) => {
|
||||||
if (agent.author.toString() !== userId) {
|
if (agent.author.toString() !== userId) {
|
||||||
const hasEditPermission = await checkPermission({
|
const hasEditPermission = await checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
requiredPermission: PERMISSION_BITS.EDIT,
|
requiredPermission: PermissionBits.EDIT,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasEditPermission) {
|
if (!hasEditPermission) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ const mongoose = require('mongoose');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const { createMethods } = require('@librechat/data-schemas');
|
const { createMethods } = require('@librechat/data-schemas');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
|
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||||
const { createAgent } = require('~/models/Agent');
|
const { createAgent } = require('~/models/Agent');
|
||||||
const { createFile } = require('~/models/File');
|
const { createFile } = require('~/models/File');
|
||||||
|
|
||||||
|
@ -228,9 +229,9 @@ describe('File Routes - Delete with Agent Access', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: otherUserId,
|
principalId: otherUserId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: authorId,
|
grantedBy: authorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -282,9 +283,9 @@ describe('File Routes - Delete with Agent Access', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: otherUserId,
|
principalId: otherUserId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: authorId,
|
grantedBy: authorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -348,9 +349,9 @@ describe('File Routes - Delete with Agent Access', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: otherUserId,
|
principalId: otherUserId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: authorId,
|
grantedBy: authorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -391,9 +392,9 @@ describe('File Routes - Delete with Agent Access', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: otherUserId,
|
principalId: otherUserId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: authorId,
|
grantedBy: authorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { logger, PermissionBits } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { generateCheckAccess } = require('@librechat/api');
|
const { generateCheckAccess } = require('@librechat/api');
|
||||||
const { Permissions, SystemRoles, PermissionTypes } = require('librechat-data-provider');
|
|
||||||
const {
|
const {
|
||||||
getPrompt,
|
Permissions,
|
||||||
getPrompts,
|
SystemRoles,
|
||||||
savePrompt,
|
ResourceType,
|
||||||
deletePrompt,
|
AccessRoleIds,
|
||||||
getPromptGroup,
|
PermissionTypes,
|
||||||
getPromptGroups,
|
PermissionBits,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
|
const {
|
||||||
|
makePromptProduction,
|
||||||
|
getAllPromptGroups,
|
||||||
updatePromptGroup,
|
updatePromptGroup,
|
||||||
deletePromptGroup,
|
deletePromptGroup,
|
||||||
createPromptGroup,
|
createPromptGroup,
|
||||||
getAllPromptGroups,
|
getPromptGroups,
|
||||||
// updatePromptLabels,
|
getPromptGroup,
|
||||||
makePromptProduction,
|
deletePrompt,
|
||||||
|
getPrompts,
|
||||||
|
savePrompt,
|
||||||
|
getPrompt,
|
||||||
} = require('~/models/Prompt');
|
} = require('~/models/Prompt');
|
||||||
const {
|
const {
|
||||||
canAccessPromptGroupResource,
|
canAccessPromptGroupResource,
|
||||||
|
@ -22,10 +28,10 @@ const {
|
||||||
requireJwtAuth,
|
requireJwtAuth,
|
||||||
} = require('~/server/middleware');
|
} = require('~/server/middleware');
|
||||||
const {
|
const {
|
||||||
grantPermission,
|
findPubliclyAccessibleResources,
|
||||||
getEffectivePermissions,
|
getEffectivePermissions,
|
||||||
findAccessibleResources,
|
findAccessibleResources,
|
||||||
findPubliclyAccessibleResources,
|
grantPermission,
|
||||||
} = require('~/server/services/PermissionService');
|
} = require('~/server/services/PermissionService');
|
||||||
const { getRoleByName } = require('~/models/Role');
|
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
|
// Get promptGroup IDs the user has VIEW access to via ACL
|
||||||
const accessibleIds = await findAccessibleResources({
|
const accessibleIds = await findAccessibleResources({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
requiredPermissions: PermissionBits.VIEW,
|
requiredPermissions: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -123,13 +129,13 @@ router.get('/groups', async (req, res) => {
|
||||||
// Get promptGroup IDs the user has VIEW access to via ACL
|
// Get promptGroup IDs the user has VIEW access to via ACL
|
||||||
const accessibleIds = await findAccessibleResources({
|
const accessibleIds = await findAccessibleResources({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
requiredPermissions: PermissionBits.VIEW,
|
requiredPermissions: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get publicly accessible promptGroups
|
// Get publicly accessible promptGroups
|
||||||
const publiclyAccessibleIds = await findPubliclyAccessibleResources({
|
const publiclyAccessibleIds = await findPubliclyAccessibleResources({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
requiredPermissions: PermissionBits.VIEW,
|
requiredPermissions: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -185,9 +191,9 @@ const createNewPromptGroup = async (req, res) => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: req.user.id,
|
principalId: req.user.id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: result.prompt.groupId,
|
resourceId: result.prompt.groupId,
|
||||||
accessRoleId: 'promptGroup_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
grantedBy: req.user.id,
|
grantedBy: req.user.id,
|
||||||
});
|
});
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -327,7 +333,7 @@ router.get('/', async (req, res) => {
|
||||||
if (groupId) {
|
if (groupId) {
|
||||||
const permissions = await getEffectivePermissions({
|
const permissions = await getEffectivePermissions({
|
||||||
userId: req.user.id,
|
userId: req.user.id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: groupId,
|
resourceId: groupId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
const request = require('supertest');
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const request = require('supertest');
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { ObjectId } = require('mongodb');
|
const { ObjectId } = require('mongodb');
|
||||||
const { SystemRoles } = require('librechat-data-provider');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
const { PermissionBits } = require('@librechat/data-schemas');
|
const {
|
||||||
|
SystemRoles,
|
||||||
|
ResourceType,
|
||||||
|
AccessRoleIds,
|
||||||
|
PermissionBits,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
|
|
||||||
// Mock modules before importing
|
// Mock modules before importing
|
||||||
jest.mock('~/server/services/Config', () => ({
|
jest.mock('~/server/services/Config', () => ({
|
||||||
|
@ -18,7 +22,6 @@ jest.mock('~/models/Role', () => ({
|
||||||
|
|
||||||
jest.mock('~/server/middleware', () => ({
|
jest.mock('~/server/middleware', () => ({
|
||||||
requireJwtAuth: (req, res, next) => next(),
|
requireJwtAuth: (req, res, next) => next(),
|
||||||
canAccessPromptResource: jest.requireActual('~/server/middleware').canAccessPromptResource,
|
|
||||||
canAccessPromptViaGroup: jest.requireActual('~/server/middleware').canAccessPromptViaGroup,
|
canAccessPromptViaGroup: jest.requireActual('~/server/middleware').canAccessPromptViaGroup,
|
||||||
canAccessPromptGroupResource:
|
canAccessPromptGroupResource:
|
||||||
jest.requireActual('~/server/middleware').canAccessPromptGroupResource,
|
jest.requireActual('~/server/middleware').canAccessPromptGroupResource,
|
||||||
|
@ -90,21 +93,21 @@ async function setupTestData() {
|
||||||
// Create access roles for promptGroups
|
// Create access roles for promptGroups
|
||||||
testRoles = {
|
testRoles = {
|
||||||
viewer: await AccessRole.create({
|
viewer: await AccessRole.create({
|
||||||
accessRoleId: 'promptGroup_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
name: 'Viewer',
|
name: 'Viewer',
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits: PermissionBits.VIEW,
|
permBits: PermissionBits.VIEW,
|
||||||
}),
|
}),
|
||||||
editor: await AccessRole.create({
|
editor: await AccessRole.create({
|
||||||
accessRoleId: 'promptGroup_editor',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||||
name: 'Editor',
|
name: 'Editor',
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits: PermissionBits.VIEW | PermissionBits.EDIT,
|
permBits: PermissionBits.VIEW | PermissionBits.EDIT,
|
||||||
}),
|
}),
|
||||||
owner: await AccessRole.create({
|
owner: await AccessRole.create({
|
||||||
accessRoleId: 'promptGroup_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
name: 'Owner',
|
name: 'Owner',
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits:
|
permBits:
|
||||||
PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
|
PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
|
||||||
}),
|
}),
|
||||||
|
@ -218,7 +221,7 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||||
|
|
||||||
// Check ACL entry was created
|
// Check ACL entry was created
|
||||||
const aclEntry = await AclEntry.findOne({
|
const aclEntry = await AclEntry.findOne({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: response.body.prompt.groupId,
|
resourceId: response.body.prompt.groupId,
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.owner._id,
|
principalId: testUsers.owner._id,
|
||||||
|
@ -248,7 +251,7 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||||
|
|
||||||
// Check ACL entry was created for the promptGroup
|
// Check ACL entry was created for the promptGroup
|
||||||
const aclEntry = await AclEntry.findOne({
|
const aclEntry = await AclEntry.findOne({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: response.body.group._id,
|
resourceId: response.body.group._id,
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.owner._id,
|
principalId: testUsers.owner._id,
|
||||||
|
@ -293,9 +296,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.owner._id,
|
principalId: testUsers.owner._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testGroup._id,
|
resourceId: testGroup._id,
|
||||||
accessRoleId: 'promptGroup_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -378,9 +381,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.owner._id,
|
principalId: testUsers.owner._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testGroup._id,
|
resourceId: testGroup._id,
|
||||||
accessRoleId: 'promptGroup_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -405,7 +408,7 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||||
|
|
||||||
// Verify ACL entries were removed
|
// Verify ACL entries were removed
|
||||||
const aclEntries = await AclEntry.find({
|
const aclEntries = await AclEntry.find({
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testGroup._id,
|
resourceId: testGroup._id,
|
||||||
});
|
});
|
||||||
expect(aclEntries).toHaveLength(0);
|
expect(aclEntries).toHaveLength(0);
|
||||||
|
@ -425,9 +428,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.viewer._id,
|
principalId: testUsers.viewer._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testGroup._id,
|
resourceId: testGroup._id,
|
||||||
accessRoleId: 'promptGroup_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
grantedBy: testUsers.editor._id,
|
grantedBy: testUsers.editor._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -492,9 +495,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.owner._id,
|
principalId: testUsers.owner._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testGroup._id,
|
resourceId: testGroup._id,
|
||||||
accessRoleId: 'promptGroup_editor',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -530,9 +533,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: testUsers.viewer._id,
|
principalId: testUsers.viewer._id,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: testGroup._id,
|
resourceId: testGroup._id,
|
||||||
accessRoleId: 'promptGroup_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -587,9 +590,9 @@ describe('Prompt Routes - ACL Permissions', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
principalId: null,
|
principalId: null,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: publicGroup._id,
|
resourceId: publicGroup._id,
|
||||||
accessRoleId: 'promptGroup_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
grantedBy: testUsers.owner._id,
|
grantedBy: testUsers.owner._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { logger } = require('@librechat/data-schemas');
|
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 { checkPermission } = require('~/server/services/PermissionService');
|
||||||
const { getAgent } = require('~/models/Agent');
|
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
|
// Check if user has at least VIEW permission on the agent
|
||||||
const hasViewPermission = await checkPermission({
|
const hasViewPermission = await checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
requiredPermission: PERMISSION_BITS.VIEW,
|
requiredPermission: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasViewPermission) {
|
if (!hasViewPermission) {
|
||||||
|
@ -44,9 +44,9 @@ const hasAccessToFilesViaAgent = async (userId, fileIds, agentId) => {
|
||||||
// Check if user has EDIT permission (which would indicate collaborative access)
|
// Check if user has EDIT permission (which would indicate collaborative access)
|
||||||
const hasEditPermission = await checkPermission({
|
const hasEditPermission = await checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
requiredPermission: PERMISSION_BITS.EDIT,
|
requiredPermission: PermissionBits.EDIT,
|
||||||
});
|
});
|
||||||
|
|
||||||
// If user only has VIEW permission, they can't access files
|
// If user only has VIEW permission, they can't access files
|
||||||
|
|
|
@ -1,32 +1,45 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
|
const { isEnabled } = require('@librechat/api');
|
||||||
|
const { ResourceType } = require('librechat-data-provider');
|
||||||
const { getTransactionSupport, logger } = require('@librechat/data-schemas');
|
const { getTransactionSupport, logger } = require('@librechat/data-schemas');
|
||||||
const { isEnabled } = require('~/server/utils');
|
|
||||||
const {
|
const {
|
||||||
entraIdPrincipalFeatureEnabled,
|
entraIdPrincipalFeatureEnabled,
|
||||||
getUserEntraGroups,
|
|
||||||
getUserOwnedEntraGroups,
|
getUserOwnedEntraGroups,
|
||||||
|
getUserEntraGroups,
|
||||||
getGroupMembers,
|
getGroupMembers,
|
||||||
getGroupOwners,
|
getGroupOwners,
|
||||||
} = require('~/server/services/GraphApiService');
|
} = require('~/server/services/GraphApiService');
|
||||||
const {
|
const {
|
||||||
|
findAccessibleResources: findAccessibleResourcesACL,
|
||||||
|
getEffectivePermissions: getEffectivePermissionsACL,
|
||||||
|
grantPermission: grantPermissionACL,
|
||||||
|
findEntriesByPrincipalsAndResource,
|
||||||
findGroupByExternalId,
|
findGroupByExternalId,
|
||||||
findRoleByIdentifier,
|
findRoleByIdentifier,
|
||||||
getUserPrincipals,
|
getUserPrincipals,
|
||||||
|
hasPermission,
|
||||||
createGroup,
|
createGroup,
|
||||||
createUser,
|
createUser,
|
||||||
updateUser,
|
updateUser,
|
||||||
findUser,
|
findUser,
|
||||||
grantPermission: grantPermissionACL,
|
|
||||||
findAccessibleResources: findAccessibleResourcesACL,
|
|
||||||
hasPermission,
|
|
||||||
getEffectivePermissions: getEffectivePermissionsACL,
|
|
||||||
findEntriesByPrincipalsAndResource,
|
|
||||||
} = require('~/models');
|
} = require('~/models');
|
||||||
const { AclEntry, AccessRole, Group } = require('~/db/models');
|
const { AclEntry, AccessRole, Group } = require('~/db/models');
|
||||||
|
|
||||||
/** @type {boolean|null} */
|
/** @type {boolean|null} */
|
||||||
let transactionSupportCache = 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'
|
* @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|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} params.resourceType - Type of resource (e.g., 'agent')
|
||||||
* @param {string|mongoose.Types.ObjectId} params.resourceId - The ID of the resource
|
* @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 {string|mongoose.Types.ObjectId} params.grantedBy - User ID granting the permission
|
||||||
* @param {mongoose.ClientSession} [params.session] - Optional MongoDB session for transactions
|
* @param {mongoose.ClientSession} [params.session] - Optional MongoDB session for transactions
|
||||||
* @returns {Promise<Object>} The created or updated ACL entry
|
* @returns {Promise<Object>} The created or updated ACL entry
|
||||||
|
@ -68,6 +81,8 @@ const grantPermission = async ({
|
||||||
throw new Error(`Invalid resource ID: ${resourceId}`);
|
throw new Error(`Invalid resource ID: ${resourceId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateResourceType(resourceType);
|
||||||
|
|
||||||
// Get the role to determine permission bits
|
// Get the role to determine permission bits
|
||||||
const role = await findRoleByIdentifier(accessRoleId);
|
const role = await findRoleByIdentifier(accessRoleId);
|
||||||
if (!role) {
|
if (!role) {
|
||||||
|
@ -111,6 +126,8 @@ const checkPermission = async ({ userId, resourceType, resourceId, requiredPermi
|
||||||
throw new Error('requiredPermission must be a positive number');
|
throw new Error('requiredPermission must be a positive number');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateResourceType(resourceType);
|
||||||
|
|
||||||
// Get all principals for the user (user + groups + public)
|
// Get all principals for the user (user + groups + public)
|
||||||
const principals = await getUserPrincipals(userId);
|
const principals = await getUserPrincipals(userId);
|
||||||
|
|
||||||
|
@ -139,6 +156,8 @@ const checkPermission = async ({ userId, resourceType, resourceId, requiredPermi
|
||||||
*/
|
*/
|
||||||
const getEffectivePermissions = async ({ userId, resourceType, resourceId }) => {
|
const getEffectivePermissions = async ({ userId, resourceType, resourceId }) => {
|
||||||
try {
|
try {
|
||||||
|
validateResourceType(resourceType);
|
||||||
|
|
||||||
// Get all principals for the user (user + groups + public)
|
// Get all principals for the user (user + groups + public)
|
||||||
const principals = await getUserPrincipals(userId);
|
const principals = await getUserPrincipals(userId);
|
||||||
|
|
||||||
|
@ -166,6 +185,8 @@ const findAccessibleResources = async ({ userId, resourceType, requiredPermissio
|
||||||
throw new Error('requiredPermissions must be a positive number');
|
throw new Error('requiredPermissions must be a positive number');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateResourceType(resourceType);
|
||||||
|
|
||||||
// Get all principals for the user (user + groups + public)
|
// Get all principals for the user (user + groups + public)
|
||||||
const principalsList = await getUserPrincipals(userId);
|
const principalsList = await getUserPrincipals(userId);
|
||||||
|
|
||||||
|
@ -196,6 +217,8 @@ const findPubliclyAccessibleResources = async ({ resourceType, requiredPermissio
|
||||||
throw new Error('requiredPermissions must be a positive number');
|
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
|
// Find all public ACL entries where the public principal has at least the required permission bits
|
||||||
const entries = await AclEntry.find({
|
const entries = await AclEntry.find({
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
|
@ -221,12 +244,9 @@ const findPubliclyAccessibleResources = async ({ resourceType, requiredPermissio
|
||||||
* @returns {Promise<Array>} Array of role definitions
|
* @returns {Promise<Array>} Array of role definitions
|
||||||
*/
|
*/
|
||||||
const getAvailableRoles = async ({ resourceType }) => {
|
const getAvailableRoles = async ({ resourceType }) => {
|
||||||
try {
|
validateResourceType(resourceType);
|
||||||
return await AccessRole.find({ resourceType }).lean();
|
|
||||||
} catch (error) {
|
return await AccessRole.find({ resourceType }).lean();
|
||||||
logger.error(`[PermissionService.getAvailableRoles] Error: ${error.message}`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -482,6 +502,8 @@ const hasPublicPermission = async ({ resourceType, resourceId, requiredPermissio
|
||||||
throw new Error('requiredPermissions must be a positive number');
|
throw new Error('requiredPermissions must be a positive number');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateResourceType(resourceType);
|
||||||
|
|
||||||
// Use public principal to check permissions
|
// Use public principal to check permissions
|
||||||
const publicPrincipal = [{ principalType: 'public' }];
|
const publicPrincipal = [{ principalType: 'public' }];
|
||||||
|
|
||||||
|
@ -707,14 +729,16 @@ const bulkUpdateResourcePermissions = async ({
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all permissions for a specific resource
|
* Remove all permissions for a resource (cleanup when resource is deleted)
|
||||||
* @param {Object} params - Parameters for removing permissions
|
* @param {Object} params - Parameters for removing all permissions
|
||||||
* @param {string} params.resourceType - Type of resource (e.g., 'agent', 'prompt')
|
* @param {string} params.resourceType - Type of resource (e.g., 'agent', 'prompt')
|
||||||
* @param {string|mongoose.Types.ObjectId} params.resourceId - The ID of the resource
|
* @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 }) => {
|
const removeAllPermissions = async ({ resourceType, resourceId }) => {
|
||||||
try {
|
try {
|
||||||
|
validateResourceType(resourceType);
|
||||||
|
|
||||||
if (!resourceId || !mongoose.Types.ObjectId.isValid(resourceId)) {
|
if (!resourceId || !mongoose.Types.ObjectId.isValid(resourceId)) {
|
||||||
throw new Error(`Invalid resource ID: ${resourceId}`);
|
throw new Error(`Invalid resource ID: ${resourceId}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { RoleBits } = require('@librechat/data-schemas');
|
const { RoleBits } = require('@librechat/data-schemas');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
|
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
bulkUpdateResourcePermissions,
|
bulkUpdateResourcePermissions,
|
||||||
getEffectivePermissions,
|
getEffectivePermissions,
|
||||||
|
@ -48,49 +49,49 @@ beforeEach(async () => {
|
||||||
// Seed some roles for testing
|
// Seed some roles for testing
|
||||||
await AccessRole.create([
|
await AccessRole.create([
|
||||||
{
|
{
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
name: 'Agent Viewer',
|
name: 'Agent Viewer',
|
||||||
description: 'Can view agents',
|
description: 'Can view agents',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.VIEWER, // VIEW permission
|
permBits: RoleBits.VIEWER, // VIEW permission
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
name: 'Agent Editor',
|
name: 'Agent Editor',
|
||||||
description: 'Can edit agents',
|
description: 'Can edit agents',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.EDITOR, // VIEW + EDIT permissions
|
permBits: RoleBits.EDITOR, // VIEW + EDIT permissions
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessRoleId: 'agent_owner',
|
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||||
name: 'Agent Owner',
|
name: 'Agent Owner',
|
||||||
description: 'Full control over agents',
|
description: 'Full control over agents',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.OWNER, // VIEW + EDIT + DELETE + SHARE permissions
|
permBits: RoleBits.OWNER, // VIEW + EDIT + DELETE + SHARE permissions
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessRoleId: 'project_viewer',
|
accessRoleId: AccessRoleIds.PROJECT_VIEWER,
|
||||||
name: 'Project Viewer',
|
name: 'Project Viewer',
|
||||||
description: 'Can view projects',
|
description: 'Can view projects',
|
||||||
resourceType: 'project',
|
resourceType: 'project',
|
||||||
permBits: RoleBits.VIEWER,
|
permBits: RoleBits.VIEWER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessRoleId: 'project_editor',
|
accessRoleId: AccessRoleIds.PROJECT_EDITOR,
|
||||||
name: 'Project Editor',
|
name: 'Project Editor',
|
||||||
description: 'Can edit projects',
|
description: 'Can edit projects',
|
||||||
resourceType: 'project',
|
resourceType: 'project',
|
||||||
permBits: RoleBits.EDITOR,
|
permBits: RoleBits.EDITOR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessRoleId: 'project_manager',
|
accessRoleId: AccessRoleIds.PROJECT_MANAGER,
|
||||||
name: 'Project Manager',
|
name: 'Project Manager',
|
||||||
description: 'Can manage projects',
|
description: 'Can manage projects',
|
||||||
resourceType: 'project',
|
resourceType: 'project',
|
||||||
permBits: RoleBits.MANAGER,
|
permBits: RoleBits.MANAGER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessRoleId: 'project_owner',
|
accessRoleId: AccessRoleIds.PROJECT_OWNER,
|
||||||
name: 'Project Owner',
|
name: 'Project Owner',
|
||||||
description: 'Full control over projects',
|
description: 'Full control over projects',
|
||||||
resourceType: 'project',
|
resourceType: 'project',
|
||||||
|
@ -117,9 +118,9 @@ describe('PermissionService', () => {
|
||||||
const entry = await grantPermission({
|
const entry = await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -131,7 +132,7 @@ describe('PermissionService', () => {
|
||||||
expect(entry.resourceId.toString()).toBe(resourceId.toString());
|
expect(entry.resourceId.toString()).toBe(resourceId.toString());
|
||||||
|
|
||||||
// Get the role to verify the permission bits are correctly set
|
// 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.permBits).toBe(role.permBits);
|
||||||
expect(entry.roleId.toString()).toBe(role._id.toString());
|
expect(entry.roleId.toString()).toBe(role._id.toString());
|
||||||
expect(entry.grantedBy.toString()).toBe(grantedById.toString());
|
expect(entry.grantedBy.toString()).toBe(grantedById.toString());
|
||||||
|
@ -142,9 +143,9 @@ describe('PermissionService', () => {
|
||||||
const entry = await grantPermission({
|
const entry = await grantPermission({
|
||||||
principalType: 'group',
|
principalType: 'group',
|
||||||
principalId: groupId,
|
principalId: groupId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -154,7 +155,7 @@ describe('PermissionService', () => {
|
||||||
expect(entry.principalModel).toBe('Group');
|
expect(entry.principalModel).toBe('Group');
|
||||||
|
|
||||||
// Get the role to verify the permission bits are correctly set
|
// 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.permBits).toBe(role.permBits);
|
||||||
expect(entry.roleId.toString()).toBe(role._id.toString());
|
expect(entry.roleId.toString()).toBe(role._id.toString());
|
||||||
});
|
});
|
||||||
|
@ -163,9 +164,9 @@ describe('PermissionService', () => {
|
||||||
const entry = await grantPermission({
|
const entry = await grantPermission({
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
principalId: null,
|
principalId: null,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -175,7 +176,7 @@ describe('PermissionService', () => {
|
||||||
expect(entry.principalModel).toBeUndefined();
|
expect(entry.principalModel).toBeUndefined();
|
||||||
|
|
||||||
// Get the role to verify the permission bits are correctly set
|
// 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.permBits).toBe(role.permBits);
|
||||||
expect(entry.roleId.toString()).toBe(role._id.toString());
|
expect(entry.roleId.toString()).toBe(role._id.toString());
|
||||||
});
|
});
|
||||||
|
@ -185,9 +186,9 @@ describe('PermissionService', () => {
|
||||||
grantPermission({
|
grantPermission({
|
||||||
principalType: 'invalid',
|
principalType: 'invalid',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow('Invalid principal type: invalid');
|
).rejects.toThrow('Invalid principal type: invalid');
|
||||||
|
@ -198,9 +199,9 @@ describe('PermissionService', () => {
|
||||||
grantPermission({
|
grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: null,
|
principalId: null,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow('Principal ID is required for user and group principals');
|
).rejects.toThrow('Principal ID is required for user and group principals');
|
||||||
|
@ -211,7 +212,7 @@ describe('PermissionService', () => {
|
||||||
grantPermission({
|
grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'non_existent_role',
|
accessRoleId: 'non_existent_role',
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
|
@ -224,9 +225,9 @@ describe('PermissionService', () => {
|
||||||
grantPermission({
|
grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'project_viewer', // Project role for agent resource
|
accessRoleId: AccessRoleIds.PROJECT_VIEWER, // Project role for agent resource
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow('Role project_viewer is for project resources, not agent');
|
).rejects.toThrow('Role project_viewer is for project resources, not agent');
|
||||||
|
@ -237,9 +238,9 @@ describe('PermissionService', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -247,13 +248,13 @@ describe('PermissionService', () => {
|
||||||
const updated = await grantPermission({
|
const updated = await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
const editorRole = await findRoleByIdentifier('agent_editor');
|
const editorRole = await findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR);
|
||||||
expect(updated.permBits).toBe(editorRole.permBits);
|
expect(updated.permBits).toBe(editorRole.permBits);
|
||||||
expect(updated.roleId.toString()).toBe(editorRole._id.toString());
|
expect(updated.roleId.toString()).toBe(editorRole._id.toString());
|
||||||
|
|
||||||
|
@ -261,7 +262,7 @@ describe('PermissionService', () => {
|
||||||
const entries = await AclEntry.find({
|
const entries = await AclEntry.find({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
});
|
});
|
||||||
expect(entries).toHaveLength(1);
|
expect(entries).toHaveLength(1);
|
||||||
|
@ -279,9 +280,9 @@ describe('PermissionService', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -289,9 +290,9 @@ describe('PermissionService', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'group',
|
principalType: 'group',
|
||||||
principalId: groupId,
|
principalId: groupId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: otherResourceId,
|
resourceId: otherResourceId,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -302,7 +303,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const hasViewPermission = await checkPermission({
|
const hasViewPermission = await checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||||
});
|
});
|
||||||
|
@ -312,7 +313,7 @@ describe('PermissionService', () => {
|
||||||
// Check higher permission level that user doesn't have
|
// Check higher permission level that user doesn't have
|
||||||
const hasEditPermission = await checkPermission({
|
const hasEditPermission = await checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
requiredPermission: 3, // RoleBits.EDITOR = VIEW + EDIT
|
requiredPermission: 3, // RoleBits.EDITOR = VIEW + EDIT
|
||||||
});
|
});
|
||||||
|
@ -330,7 +331,7 @@ describe('PermissionService', () => {
|
||||||
// Check original resource (user has access)
|
// Check original resource (user has access)
|
||||||
const hasViewOnOriginal = await checkPermission({
|
const hasViewOnOriginal = await checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||||
});
|
});
|
||||||
|
@ -340,7 +341,7 @@ describe('PermissionService', () => {
|
||||||
// Check other resource (group has access)
|
// Check other resource (group has access)
|
||||||
const hasViewOnOther = await checkPermission({
|
const hasViewOnOther = await checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: otherResourceId,
|
resourceId: otherResourceId,
|
||||||
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||||
});
|
});
|
||||||
|
@ -356,9 +357,9 @@ describe('PermissionService', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
principalId: null,
|
principalId: null,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: publicResourceId,
|
resourceId: publicResourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -371,7 +372,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const hasPublicAccess = await checkPermission({
|
const hasPublicAccess = await checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: publicResourceId,
|
resourceId: publicResourceId,
|
||||||
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
requiredPermission: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||||
});
|
});
|
||||||
|
@ -385,7 +386,7 @@ describe('PermissionService', () => {
|
||||||
await expect(
|
await expect(
|
||||||
checkPermission({
|
checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
requiredPermission: 'invalid',
|
requiredPermission: 'invalid',
|
||||||
}),
|
}),
|
||||||
|
@ -393,7 +394,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const nonExistentResource = await checkPermission({
|
const nonExistentResource = await checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: new mongoose.Types.ObjectId(),
|
resourceId: new mongoose.Types.ObjectId(),
|
||||||
requiredPermission: 1, // RoleBits.VIEWER
|
requiredPermission: 1, // RoleBits.VIEWER
|
||||||
});
|
});
|
||||||
|
@ -406,7 +407,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const hasPermission = await checkPermission({
|
const hasPermission = await checkPermission({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
requiredPermission: 1, // RoleBits.VIEWER
|
requiredPermission: 1, // RoleBits.VIEWER
|
||||||
});
|
});
|
||||||
|
@ -424,18 +425,18 @@ describe('PermissionService', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'group',
|
principalType: 'group',
|
||||||
principalId: groupId,
|
principalId: groupId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -444,9 +445,9 @@ describe('PermissionService', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
principalId: null,
|
principalId: null,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: publicResourceId,
|
resourceId: publicResourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -459,7 +460,7 @@ describe('PermissionService', () => {
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'project',
|
resourceType: 'project',
|
||||||
resourceId: projectId,
|
resourceId: projectId,
|
||||||
accessRoleId: 'project_viewer',
|
accessRoleId: AccessRoleIds.PROJECT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -467,10 +468,10 @@ describe('PermissionService', () => {
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
principalModel: 'User',
|
principalModel: 'User',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: childResourceId,
|
resourceId: childResourceId,
|
||||||
permBits: RoleBits.VIEWER,
|
permBits: RoleBits.VIEWER,
|
||||||
roleId: (await findRoleByIdentifier('agent_viewer'))._id,
|
roleId: (await findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER))._id,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
grantedAt: new Date(),
|
grantedAt: new Date(),
|
||||||
inheritedFrom: projectId,
|
inheritedFrom: projectId,
|
||||||
|
@ -486,7 +487,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const effective = await getEffectivePermissions({
|
const effective = await getEffectivePermissions({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -505,7 +506,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const effective = await getEffectivePermissions({
|
const effective = await getEffectivePermissions({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: childResourceId,
|
resourceId: childResourceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -519,7 +520,7 @@ describe('PermissionService', () => {
|
||||||
const nonExistentResource = new mongoose.Types.ObjectId();
|
const nonExistentResource = new mongoose.Types.ObjectId();
|
||||||
const effective = await getEffectivePermissions({
|
const effective = await getEffectivePermissions({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: nonExistentResource,
|
resourceId: nonExistentResource,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -532,7 +533,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const effective = await getEffectivePermissions({
|
const effective = await getEffectivePermissions({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -555,9 +556,9 @@ describe('PermissionService', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: resource1,
|
resourceId: resource1,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -565,9 +566,9 @@ describe('PermissionService', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: resource2,
|
resourceId: resource2,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -575,9 +576,9 @@ describe('PermissionService', () => {
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'group',
|
principalType: 'group',
|
||||||
principalId: groupId,
|
principalId: groupId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: resource3,
|
resourceId: resource3,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -588,7 +589,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const viewableResources = await findAccessibleResources({
|
const viewableResources = await findAccessibleResources({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermissions: 1, // RoleBits.VIEWER // 1 = VIEW
|
requiredPermissions: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -602,7 +603,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const editableResources = await findAccessibleResources({
|
const editableResources = await findAccessibleResources({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermissions: 3, // RoleBits.EDITOR = VIEW + EDIT
|
requiredPermissions: 3, // RoleBits.EDITOR = VIEW + EDIT
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -619,7 +620,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const viewableResources = await findAccessibleResources({
|
const viewableResources = await findAccessibleResources({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermissions: 1, // RoleBits.VIEWER // 1 = VIEW
|
requiredPermissions: 1, // RoleBits.VIEWER // 1 = VIEW
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -633,7 +634,7 @@ describe('PermissionService', () => {
|
||||||
await expect(
|
await expect(
|
||||||
findAccessibleResources({
|
findAccessibleResources({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermissions: 'invalid',
|
requiredPermissions: 'invalid',
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow('requiredPermissions must be a positive number');
|
).rejects.toThrow('requiredPermissions must be a positive number');
|
||||||
|
@ -652,7 +653,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
const resources = await findAccessibleResources({
|
const resources = await findAccessibleResources({
|
||||||
userId,
|
userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
requiredPermissions: 1, // RoleBits.VIEWER
|
requiredPermissions: 1, // RoleBits.VIEWER
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -663,12 +664,12 @@ describe('PermissionService', () => {
|
||||||
describe('getAvailableRoles', () => {
|
describe('getAvailableRoles', () => {
|
||||||
test('should get all roles for a resource type', async () => {
|
test('should get all roles for a resource type', async () => {
|
||||||
const roles = await getAvailableRoles({
|
const roles = await getAvailableRoles({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(roles).toHaveLength(3);
|
expect(roles).toHaveLength(3);
|
||||||
expect(roles.map((r) => r.accessRoleId).sort()).toEqual(
|
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({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'group',
|
principalType: 'group',
|
||||||
principalId: groupId,
|
principalId: groupId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
|
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
principalId: null,
|
principalId: null,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -720,22 +721,22 @@ describe('PermissionService', () => {
|
||||||
{
|
{
|
||||||
type: 'user',
|
type: 'user',
|
||||||
id: userId,
|
id: userId,
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'user',
|
type: 'user',
|
||||||
id: otherUserId,
|
id: otherUserId,
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'group',
|
type: 'group',
|
||||||
id: groupId,
|
id: groupId,
|
||||||
accessRoleId: 'agent_owner',
|
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const results = await bulkUpdateResourcePermissions({
|
const results = await bulkUpdateResourcePermissions({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: newResourceId,
|
resourceId: newResourceId,
|
||||||
updatedPrincipals,
|
updatedPrincipals,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
|
@ -748,7 +749,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
// Verify permissions were created
|
// Verify permissions were created
|
||||||
const aclEntries = await AclEntry.find({
|
const aclEntries = await AclEntry.find({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: newResourceId,
|
resourceId: newResourceId,
|
||||||
});
|
});
|
||||||
expect(aclEntries).toHaveLength(3);
|
expect(aclEntries).toHaveLength(3);
|
||||||
|
@ -759,21 +760,21 @@ describe('PermissionService', () => {
|
||||||
{
|
{
|
||||||
type: 'user',
|
type: 'user',
|
||||||
id: userId,
|
id: userId,
|
||||||
accessRoleId: 'agent_editor', // Upgrade from viewer to editor
|
accessRoleId: AccessRoleIds.AGENT_EDITOR, // Upgrade from viewer to editor
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'group',
|
type: 'group',
|
||||||
id: groupId,
|
id: groupId,
|
||||||
accessRoleId: 'agent_owner', // Upgrade from editor to owner
|
accessRoleId: AccessRoleIds.AGENT_OWNER, // Upgrade from editor to owner
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'public',
|
type: 'public',
|
||||||
accessRoleId: 'agent_viewer', // Keep same role
|
accessRoleId: AccessRoleIds.AGENT_VIEWER, // Keep same role
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const results = await bulkUpdateResourcePermissions({
|
const results = await bulkUpdateResourcePermissions({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
updatedPrincipals,
|
updatedPrincipals,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
|
@ -789,18 +790,18 @@ describe('PermissionService', () => {
|
||||||
const userEntry = await AclEntry.findOne({
|
const userEntry = await AclEntry.findOne({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
}).populate('roleId', 'accessRoleId');
|
}).populate('roleId', 'accessRoleId');
|
||||||
expect(userEntry.roleId.accessRoleId).toBe('agent_editor');
|
expect(userEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR);
|
||||||
|
|
||||||
const groupEntry = await AclEntry.findOne({
|
const groupEntry = await AclEntry.findOne({
|
||||||
principalType: 'group',
|
principalType: 'group',
|
||||||
principalId: groupId,
|
principalId: groupId,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
}).populate('roleId', 'accessRoleId');
|
}).populate('roleId', 'accessRoleId');
|
||||||
expect(groupEntry.roleId.accessRoleId).toBe('agent_owner');
|
expect(groupEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_OWNER);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should revoke specified permissions', async () => {
|
test('should revoke specified permissions', async () => {
|
||||||
|
@ -815,7 +816,7 @@ describe('PermissionService', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
const results = await bulkUpdateResourcePermissions({
|
const results = await bulkUpdateResourcePermissions({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
revokedPrincipals,
|
revokedPrincipals,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
|
@ -828,7 +829,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
// Verify only user permission remains
|
// Verify only user permission remains
|
||||||
const remainingEntries = await AclEntry.find({
|
const remainingEntries = await AclEntry.find({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
});
|
});
|
||||||
expect(remainingEntries).toHaveLength(1);
|
expect(remainingEntries).toHaveLength(1);
|
||||||
|
@ -841,12 +842,12 @@ describe('PermissionService', () => {
|
||||||
{
|
{
|
||||||
type: 'user',
|
type: 'user',
|
||||||
id: userId,
|
id: userId,
|
||||||
accessRoleId: 'agent_owner', // Update existing
|
accessRoleId: AccessRoleIds.AGENT_OWNER, // Update existing
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'user',
|
type: 'user',
|
||||||
id: otherUserId,
|
id: otherUserId,
|
||||||
accessRoleId: 'agent_viewer', // New permission
|
accessRoleId: AccessRoleIds.AGENT_VIEWER, // New permission
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -861,7 +862,7 @@ describe('PermissionService', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
const results = await bulkUpdateResourcePermissions({
|
const results = await bulkUpdateResourcePermissions({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
updatedPrincipals,
|
updatedPrincipals,
|
||||||
revokedPrincipals,
|
revokedPrincipals,
|
||||||
|
@ -875,19 +876,19 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
// Verify final state
|
// Verify final state
|
||||||
const finalEntries = await AclEntry.find({
|
const finalEntries = await AclEntry.find({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
}).populate('roleId', 'accessRoleId');
|
}).populate('roleId', 'accessRoleId');
|
||||||
|
|
||||||
expect(finalEntries).toHaveLength(2);
|
expect(finalEntries).toHaveLength(2);
|
||||||
|
|
||||||
const userEntry = finalEntries.find((e) => e.principalId.toString() === userId.toString());
|
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(
|
const otherUserEntry = finalEntries.find(
|
||||||
(e) => e.principalId.toString() === otherUserId.toString(),
|
(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 () => {
|
test('should handle errors for invalid roles gracefully', async () => {
|
||||||
|
@ -895,7 +896,7 @@ describe('PermissionService', () => {
|
||||||
{
|
{
|
||||||
type: 'user',
|
type: 'user',
|
||||||
id: userId,
|
id: userId,
|
||||||
accessRoleId: 'agent_viewer', // Valid
|
accessRoleId: AccessRoleIds.AGENT_VIEWER, // Valid
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'user',
|
type: 'user',
|
||||||
|
@ -905,12 +906,12 @@ describe('PermissionService', () => {
|
||||||
{
|
{
|
||||||
type: 'group',
|
type: 'group',
|
||||||
id: groupId,
|
id: groupId,
|
||||||
accessRoleId: 'project_viewer', // Wrong resource type
|
accessRoleId: AccessRoleIds.PROJECT_VIEWER, // Wrong resource type
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const results = await bulkUpdateResourcePermissions({
|
const results = await bulkUpdateResourcePermissions({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
updatedPrincipals,
|
updatedPrincipals,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
|
@ -928,7 +929,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
test('should handle empty arrays (no operations)', async () => {
|
test('should handle empty arrays (no operations)', async () => {
|
||||||
const results = await bulkUpdateResourcePermissions({
|
const results = await bulkUpdateResourcePermissions({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
updatedPrincipals: [],
|
updatedPrincipals: [],
|
||||||
revokedPrincipals: [],
|
revokedPrincipals: [],
|
||||||
|
@ -942,7 +943,7 @@ describe('PermissionService', () => {
|
||||||
|
|
||||||
// Verify no changes to existing permissions (since no operations were performed)
|
// Verify no changes to existing permissions (since no operations were performed)
|
||||||
const remainingEntries = await AclEntry.find({
|
const remainingEntries = await AclEntry.find({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
});
|
});
|
||||||
expect(remainingEntries).toHaveLength(3); // Original permissions still exist
|
expect(remainingEntries).toHaveLength(3); // Original permissions still exist
|
||||||
|
@ -951,7 +952,7 @@ describe('PermissionService', () => {
|
||||||
test('should throw error for invalid updatedPrincipals array', async () => {
|
test('should throw error for invalid updatedPrincipals array', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
bulkUpdateResourcePermissions({
|
bulkUpdateResourcePermissions({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
updatedPrincipals: 'not an array',
|
updatedPrincipals: 'not an array',
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
|
@ -962,7 +963,7 @@ describe('PermissionService', () => {
|
||||||
test('should throw error for invalid resource ID', async () => {
|
test('should throw error for invalid resource ID', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
bulkUpdateResourcePermissions({
|
bulkUpdateResourcePermissions({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: 'invalid-id',
|
resourceId: 'invalid-id',
|
||||||
permissions: [],
|
permissions: [],
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
|
@ -974,12 +975,12 @@ describe('PermissionService', () => {
|
||||||
const updatedPrincipals = [
|
const updatedPrincipals = [
|
||||||
{
|
{
|
||||||
type: 'public',
|
type: 'public',
|
||||||
accessRoleId: 'agent_editor', // Update public permission
|
accessRoleId: AccessRoleIds.AGENT_EDITOR, // Update public permission
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'user',
|
type: 'user',
|
||||||
id: otherUserId,
|
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({
|
const results = await bulkUpdateResourcePermissions({
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
updatedPrincipals,
|
updatedPrincipals,
|
||||||
revokedPrincipals,
|
revokedPrincipals,
|
||||||
|
@ -1010,12 +1011,12 @@ describe('PermissionService', () => {
|
||||||
// Verify public permission was updated
|
// Verify public permission was updated
|
||||||
const publicEntry = await AclEntry.findOne({
|
const publicEntry = await AclEntry.findOne({
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
}).populate('roleId', 'accessRoleId');
|
}).populate('roleId', 'accessRoleId');
|
||||||
|
|
||||||
expect(publicEntry).toBeDefined();
|
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 () => {
|
test('should work with different resource types', async () => {
|
||||||
|
@ -1025,12 +1026,12 @@ describe('PermissionService', () => {
|
||||||
{
|
{
|
||||||
type: 'user',
|
type: 'user',
|
||||||
id: userId,
|
id: userId,
|
||||||
accessRoleId: 'project_viewer',
|
accessRoleId: AccessRoleIds.PROJECT_VIEWER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'group',
|
type: 'group',
|
||||||
id: groupId,
|
id: groupId,
|
||||||
accessRoleId: 'project_editor',
|
accessRoleId: AccessRoleIds.PROJECT_EDITOR,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1072,6 +1072,19 @@
|
||||||
* @memberof typedefs
|
* @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
|
* @exports JsonSchemaType
|
||||||
* @typedef {import('@librechat/api').JsonSchemaType} JsonSchemaType
|
* @typedef {import('@librechat/api').JsonSchemaType} JsonSchemaType
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type t from 'librechat-data-provider';
|
import type t from 'librechat-data-provider';
|
||||||
|
|
||||||
import useLocalize from '~/hooks/useLocalize';
|
import useLocalize from '~/hooks/useLocalize';
|
||||||
import { renderAgentAvatar, getContactDisplayName } from '~/utils/agents';
|
import { cn, renderAgentAvatar, getContactDisplayName } from '~/utils';
|
||||||
import { cn } from '~/utils';
|
|
||||||
|
|
||||||
interface AgentCardProps {
|
interface AgentCardProps {
|
||||||
agent: t.Agent; // The agent data to display
|
agent: t.Agent; // The agent data to display
|
|
@ -6,7 +6,7 @@ import {
|
||||||
QueryKeys,
|
QueryKeys,
|
||||||
Constants,
|
Constants,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
PERMISSION_BITS,
|
PermissionBits,
|
||||||
LocalStorageKeys,
|
LocalStorageKeys,
|
||||||
AgentListResponse,
|
AgentListResponse,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
|
@ -45,7 +45,7 @@ const AgentDetail: React.FC<AgentDetailProps> = ({ agent, isOpen, onClose }) =>
|
||||||
*/
|
*/
|
||||||
const handleStartChat = () => {
|
const handleStartChat = () => {
|
||||||
if (agent) {
|
if (agent) {
|
||||||
const keys = [QueryKeys.agents, { requiredPermission: PERMISSION_BITS.EDIT }];
|
const keys = [QueryKeys.agents, { requiredPermission: PermissionBits.EDIT }];
|
||||||
const listResp = queryClient.getQueryData<AgentListResponse>(keys);
|
const listResp = queryClient.getQueryData<AgentListResponse>(keys);
|
||||||
if (listResp != null) {
|
if (listResp != null) {
|
||||||
if (!listResp.data.some((a) => a.id === agent.id)) {
|
if (!listResp.data.some((a) => a.id === agent.id)) {
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Button, Spinner } from '@librechat/client';
|
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 type t from 'librechat-data-provider';
|
||||||
import { useMarketplaceAgentsInfiniteQuery } from '~/data-provider/Agents';
|
import { useMarketplaceAgentsInfiniteQuery } from '~/data-provider/Agents';
|
||||||
import { useAgentCategories, useLocalize } from '~/hooks';
|
import { useAgentCategories, useLocalize } from '~/hooks';
|
||||||
|
@ -33,7 +33,7 @@ const AgentGrid: React.FC<AgentGridProps> = ({ category, searchQuery, onSelectAg
|
||||||
limit: number;
|
limit: number;
|
||||||
promoted?: 0 | 1;
|
promoted?: 0 | 1;
|
||||||
} = {
|
} = {
|
||||||
requiredPermission: PERMISSION_BITS.VIEW, // View permission for marketplace viewing
|
requiredPermission: PermissionBits.VIEW, // View permission for marketplace viewing
|
||||||
limit: 6,
|
limit: 6,
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@librechat/client';
|
} from '@librechat/client';
|
||||||
import { PERMISSION_BITS } from 'librechat-data-provider';
|
import { PermissionBits } from 'librechat-data-provider';
|
||||||
import type { TPromptGroup } from 'librechat-data-provider';
|
import type { TPromptGroup } from 'librechat-data-provider';
|
||||||
import { useLocalize, useSubmitMessage, useCustomLink, useResourcePermissions } from '~/hooks';
|
import { useLocalize, useSubmitMessage, useCustomLink, useResourcePermissions } from '~/hooks';
|
||||||
import VariableDialog from '~/components/Prompts/Groups/VariableDialog';
|
import VariableDialog from '~/components/Prompts/Groups/VariableDialog';
|
||||||
|
@ -35,7 +35,7 @@ function ChatGroupItem({
|
||||||
|
|
||||||
// Check permissions for the promptGroup
|
// Check permissions for the promptGroup
|
||||||
const { hasPermission } = useResourcePermissions('promptGroup', group._id || '');
|
const { hasPermission } = useResourcePermissions('promptGroup', group._id || '');
|
||||||
const canEdit = hasPermission(PERMISSION_BITS.EDIT);
|
const canEdit = hasPermission(PermissionBits.EDIT);
|
||||||
|
|
||||||
const onCardClick: React.MouseEventHandler<HTMLButtonElement> = () => {
|
const onCardClick: React.MouseEventHandler<HTMLButtonElement> = () => {
|
||||||
const text = group.productionPrompt?.prompt;
|
const text = group.productionPrompt?.prompt;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { memo, useState, useRef, useMemo, useCallback, KeyboardEvent } from 'react';
|
import { memo, useState, useRef, useMemo, useCallback, KeyboardEvent } from 'react';
|
||||||
import { EarthIcon, Pen } from 'lucide-react';
|
import { EarthIcon, Pen } from 'lucide-react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
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 {
|
import {
|
||||||
Input,
|
Input,
|
||||||
Label,
|
Label,
|
||||||
|
@ -30,8 +30,8 @@ function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps
|
||||||
const [nameInputValue, setNameInputValue] = useState(group.name);
|
const [nameInputValue, setNameInputValue] = useState(group.name);
|
||||||
|
|
||||||
const { hasPermission } = useResourcePermissions('promptGroup', group._id || '');
|
const { hasPermission } = useResourcePermissions('promptGroup', group._id || '');
|
||||||
const canEdit = hasPermission(PERMISSION_BITS.EDIT);
|
const canEdit = hasPermission(PermissionBits.EDIT);
|
||||||
const canDelete = hasPermission(PERMISSION_BITS.DELETE);
|
const canDelete = hasPermission(PermissionBits.DELETE);
|
||||||
|
|
||||||
const isGlobalGroup = useMemo(
|
const isGlobalGroup = useMemo(
|
||||||
() => instanceProjectId && group.projectIds?.includes(instanceProjectId),
|
() => instanceProjectId && group.projectIds?.includes(instanceProjectId),
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Menu, Rocket } from 'lucide-react';
|
||||||
import { useForm, FormProvider } from 'react-hook-form';
|
import { useForm, FormProvider } from 'react-hook-form';
|
||||||
import { useParams, useOutletContext } from 'react-router-dom';
|
import { useParams, useOutletContext } from 'react-router-dom';
|
||||||
import { Button, Skeleton, useToastContext } from '@librechat/client';
|
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 type { TCreatePrompt, TPrompt, TPromptGroup } from 'librechat-data-provider';
|
||||||
import {
|
import {
|
||||||
useGetPrompts,
|
useGetPrompts,
|
||||||
|
@ -186,8 +186,8 @@ const PromptForm = () => {
|
||||||
group?._id || '',
|
group?._id || '',
|
||||||
);
|
);
|
||||||
|
|
||||||
const canEdit = hasPermission(PERMISSION_BITS.EDIT);
|
const canEdit = hasPermission(PermissionBits.EDIT);
|
||||||
const canView = hasPermission(PERMISSION_BITS.VIEW);
|
const canView = hasPermission(PermissionBits.VIEW);
|
||||||
|
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
|
|
@ -3,8 +3,9 @@ import { Share2Icon } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
SystemRoles,
|
SystemRoles,
|
||||||
Permissions,
|
Permissions,
|
||||||
|
ResourceType,
|
||||||
PermissionTypes,
|
PermissionTypes,
|
||||||
PERMISSION_BITS,
|
PermissionBits,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import { Button } from '@librechat/client';
|
import { Button } from '@librechat/client';
|
||||||
import type { TPromptGroup } from 'librechat-data-provider';
|
import type { TPromptGroup } from 'librechat-data-provider';
|
||||||
|
@ -25,7 +26,7 @@ const SharePrompt = React.memo(
|
||||||
// The query will be disabled if groupId is empty
|
// The query will be disabled if groupId is empty
|
||||||
const groupId = group?._id || '';
|
const groupId = group?._id || '';
|
||||||
const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions(
|
const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions(
|
||||||
'promptGroup',
|
ResourceType.PROMPTGROUP,
|
||||||
groupId,
|
groupId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ const SharePrompt = React.memo(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canShareThisPrompt = hasPermission(PERMISSION_BITS.SHARE);
|
const canShareThisPrompt = hasPermission(PermissionBits.SHARE);
|
||||||
|
|
||||||
const shouldShowShareButton =
|
const shouldShowShareButton =
|
||||||
(group.author === user?.id || user?.role === SystemRoles.ADMIN || canShareThisPrompt) &&
|
(group.author === user?.id || user?.role === SystemRoles.ADMIN || canShareThisPrompt) &&
|
||||||
|
@ -49,7 +50,7 @@ const SharePrompt = React.memo(
|
||||||
<GenericGrantAccessDialog
|
<GenericGrantAccessDialog
|
||||||
resourceDbId={groupId}
|
resourceDbId={groupId}
|
||||||
resourceName={group.name}
|
resourceName={group.name}
|
||||||
resourceType="promptGroup"
|
resourceType={ResourceType.PROMPTGROUP}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import * as Ariakit from '@ariakit/react';
|
import * as Ariakit from '@ariakit/react';
|
||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
import { DropdownPopup } from '@librechat/client';
|
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 { useGetAccessRolesQuery } from 'librechat-data-provider/react-query';
|
||||||
import type { AccessRole } from 'librechat-data-provider';
|
import type { AccessRole } from 'librechat-data-provider';
|
||||||
import type * as t from '~/common';
|
import type * as t from '~/common';
|
||||||
|
@ -10,15 +10,15 @@ import { cn, getRoleLocalizationKeys } from '~/utils';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
interface AccessRolesPickerProps {
|
interface AccessRolesPickerProps {
|
||||||
resourceType?: string;
|
resourceType?: ResourceType;
|
||||||
selectedRoleId?: ACCESS_ROLE_IDS;
|
selectedRoleId?: AccessRoleIds;
|
||||||
onRoleChange: (roleId: ACCESS_ROLE_IDS) => void;
|
onRoleChange: (roleId: AccessRoleIds) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AccessRolesPicker({
|
export default function AccessRolesPicker({
|
||||||
resourceType = 'agent',
|
resourceType = ResourceType.AGENT,
|
||||||
selectedRoleId = ACCESS_ROLE_IDS.AGENT_VIEWER,
|
selectedRoleId = AccessRoleIds.AGENT_VIEWER,
|
||||||
onRoleChange,
|
onRoleChange,
|
||||||
className = '',
|
className = '',
|
||||||
}: AccessRolesPickerProps) {
|
}: AccessRolesPickerProps) {
|
||||||
|
@ -27,7 +27,7 @@ export default function AccessRolesPicker({
|
||||||
const { data: accessRoles, isLoading: rolesLoading } = useGetAccessRolesQuery(resourceType);
|
const { data: accessRoles, isLoading: rolesLoading } = useGetAccessRolesQuery(resourceType);
|
||||||
|
|
||||||
/** Helper function to get localized role name and description */
|
/** Helper function to get localized role name and description */
|
||||||
const getLocalizedRoleInfo = (roleId: ACCESS_ROLE_IDS) => {
|
const getLocalizedRoleInfo = (roleId: AccessRoleIds) => {
|
||||||
const keys = getRoleLocalizationKeys(roleId);
|
const keys = getRoleLocalizationKeys(roleId);
|
||||||
return {
|
return {
|
||||||
name: localize(keys.name),
|
name: localize(keys.name),
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { AccessRoleIds, ResourceType } from 'librechat-data-provider';
|
||||||
import { Share2Icon, Users, Loader, Shield, Link, CopyCheck } from 'lucide-react';
|
import { Share2Icon, Users, Loader, Shield, Link, CopyCheck } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -10,13 +11,17 @@ import {
|
||||||
useToastContext,
|
useToastContext,
|
||||||
} from '@librechat/client';
|
} from '@librechat/client';
|
||||||
import type { TPrincipal } from 'librechat-data-provider';
|
import type { TPrincipal } from 'librechat-data-provider';
|
||||||
import { useLocalize, useCopyToClipboard } from '~/hooks';
|
import {
|
||||||
import { usePeoplePickerPermissions, useResourcePermissionState } from '~/hooks/Sharing';
|
usePeoplePickerPermissions,
|
||||||
|
useResourcePermissionState,
|
||||||
|
useCopyToClipboard,
|
||||||
|
useLocalize,
|
||||||
|
} from '~/hooks';
|
||||||
import GenericManagePermissionsDialog from './GenericManagePermissionsDialog';
|
import GenericManagePermissionsDialog from './GenericManagePermissionsDialog';
|
||||||
import PeoplePicker from '../SidePanel/Agents/Sharing/PeoplePicker/PeoplePicker';
|
|
||||||
import AccessRolesPicker from '../SidePanel/Agents/Sharing/AccessRolesPicker';
|
|
||||||
import PublicSharingToggle from './PublicSharingToggle';
|
import PublicSharingToggle from './PublicSharingToggle';
|
||||||
|
import AccessRolesPicker from './AccessRolesPicker';
|
||||||
import { cn, removeFocusOutlines } from '~/utils';
|
import { cn, removeFocusOutlines } from '~/utils';
|
||||||
|
import { PeoplePicker } from './PeoplePicker';
|
||||||
|
|
||||||
export default function GenericGrantAccessDialog({
|
export default function GenericGrantAccessDialog({
|
||||||
resourceName,
|
resourceName,
|
||||||
|
@ -30,8 +35,8 @@ export default function GenericGrantAccessDialog({
|
||||||
resourceDbId?: string | null;
|
resourceDbId?: string | null;
|
||||||
resourceId?: string | null;
|
resourceId?: string | null;
|
||||||
resourceName?: string;
|
resourceName?: string;
|
||||||
resourceType: string;
|
resourceType: ResourceType;
|
||||||
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole: string) => void;
|
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole: AccessRoleIds) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
@ -55,8 +60,8 @@ export default function GenericGrantAccessDialog({
|
||||||
} = useResourcePermissionState(resourceType, resourceDbId, isModalOpen);
|
} = useResourcePermissionState(resourceType, resourceDbId, isModalOpen);
|
||||||
|
|
||||||
const [newShares, setNewShares] = useState<TPrincipal[]>([]);
|
const [newShares, setNewShares] = useState<TPrincipal[]>([]);
|
||||||
const [defaultPermissionId, setDefaultPermissionId] = useState<string>(
|
const [defaultPermissionId, setDefaultPermissionId] = useState<AccessRoleIds | undefined>(
|
||||||
config?.defaultViewerRoleId ?? '',
|
config?.defaultViewerRoleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const resourceUrl = config?.getResourceUrl ? config?.getResourceUrl(resourceId || '') : '';
|
const resourceUrl = config?.getResourceUrl ? config?.getResourceUrl(resourceId || '') : '';
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
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 { useGetAccessRolesQuery } from 'librechat-data-provider/react-query';
|
||||||
|
import { Settings, Users, Loader, UserCheck, Trash2, Shield } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
OGDialog,
|
OGDialog,
|
||||||
|
@ -11,9 +10,10 @@ import {
|
||||||
OGDialogTrigger,
|
OGDialogTrigger,
|
||||||
useToastContext,
|
useToastContext,
|
||||||
} from '@librechat/client';
|
} 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 { useResourcePermissionState } from '~/hooks/Sharing';
|
||||||
import PublicSharingToggle from './PublicSharingToggle';
|
import PublicSharingToggle from './PublicSharingToggle';
|
||||||
|
import { SelectedPrincipalsList } from './PeoplePicker';
|
||||||
import { cn, removeFocusOutlines } from '~/utils';
|
import { cn, removeFocusOutlines } from '~/utils';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
|
@ -26,8 +26,12 @@ export default function GenericManagePermissionsDialog({
|
||||||
}: {
|
}: {
|
||||||
resourceDbId: string;
|
resourceDbId: string;
|
||||||
resourceName?: string;
|
resourceName?: string;
|
||||||
resourceType: string;
|
resourceType: ResourceType;
|
||||||
onUpdatePermissions?: (shares: TPrincipal[], isPublic: boolean, publicRole: string) => void;
|
onUpdatePermissions?: (
|
||||||
|
shares: TPrincipal[],
|
||||||
|
isPublic: boolean,
|
||||||
|
publicRole: AccessRoleIds,
|
||||||
|
) => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState, useEffect, useMemo } from 'react';
|
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 { Share2Icon, Users, Loader, Shield, Link, CopyCheck } from 'lucide-react';
|
||||||
|
import { Permissions, ResourceType, PermissionTypes, AccessRoleIds } from 'librechat-data-provider';
|
||||||
import {
|
import {
|
||||||
useGetResourcePermissionsQuery,
|
useGetResourcePermissionsQuery,
|
||||||
useUpdateResourcePermissionsMutation,
|
useUpdateResourcePermissionsMutation,
|
||||||
|
@ -18,22 +18,22 @@ import type { TPrincipal } from 'librechat-data-provider';
|
||||||
import { useLocalize, useCopyToClipboard, useHasAccess } from '~/hooks';
|
import { useLocalize, useCopyToClipboard, useHasAccess } from '~/hooks';
|
||||||
import ManagePermissionsDialog from './ManagePermissionsDialog';
|
import ManagePermissionsDialog from './ManagePermissionsDialog';
|
||||||
import PublicSharingToggle from './PublicSharingToggle';
|
import PublicSharingToggle from './PublicSharingToggle';
|
||||||
import PeoplePicker from './PeoplePicker/PeoplePicker';
|
|
||||||
import AccessRolesPicker from './AccessRolesPicker';
|
import AccessRolesPicker from './AccessRolesPicker';
|
||||||
import { cn, removeFocusOutlines } from '~/utils';
|
import { cn, removeFocusOutlines } from '~/utils';
|
||||||
|
import { PeoplePicker } from './PeoplePicker';
|
||||||
|
|
||||||
export default function GrantAccessDialog({
|
export default function GrantAccessDialog({
|
||||||
agentName,
|
agentName,
|
||||||
onGrantAccess,
|
onGrantAccess,
|
||||||
resourceType = 'agent',
|
resourceType = ResourceType.AGENT,
|
||||||
agentDbId,
|
agentDbId,
|
||||||
agentId,
|
agentId,
|
||||||
}: {
|
}: {
|
||||||
agentDbId?: string | null;
|
agentDbId?: string | null;
|
||||||
agentId?: string | null;
|
agentId?: string | null;
|
||||||
agentName?: string;
|
agentName?: string;
|
||||||
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole: string) => void;
|
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole: AccessRoleIds) => void;
|
||||||
resourceType?: string;
|
resourceType?: ResourceType;
|
||||||
}) {
|
}) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { showToast } = useToastContext();
|
const { showToast } = useToastContext();
|
||||||
|
@ -73,7 +73,7 @@ export default function GrantAccessDialog({
|
||||||
|
|
||||||
const [newShares, setNewShares] = useState<TPrincipal[]>([]);
|
const [newShares, setNewShares] = useState<TPrincipal[]>([]);
|
||||||
const [defaultPermissionId, setDefaultPermissionId] = useState<string>(
|
const [defaultPermissionId, setDefaultPermissionId] = useState<string>(
|
||||||
ACCESS_ROLE_IDS.AGENT_VIEWER,
|
AccessRoleIds.AGENT_VIEWER,
|
||||||
);
|
);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [isCopying, setIsCopying] = useState(false);
|
const [isCopying, setIsCopying] = useState(false);
|
||||||
|
@ -94,10 +94,10 @@ export default function GrantAccessDialog({
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
const currentIsPublic = permissionsData?.public ?? false;
|
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 [isPublic, setIsPublic] = useState(false);
|
||||||
const [publicRole, setPublicRole] = useState<string>(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
const [publicRole, setPublicRole] = useState<AccessRoleIds>(AccessRoleIds.AGENT_VIEWER);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (permissionsData && isModalOpen) {
|
if (permissionsData && isModalOpen) {
|
||||||
|
@ -140,9 +140,9 @@ export default function GrantAccessDialog({
|
||||||
});
|
});
|
||||||
|
|
||||||
setNewShares([]);
|
setNewShares([]);
|
||||||
setDefaultPermissionId(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
setDefaultPermissionId(AccessRoleIds.AGENT_VIEWER);
|
||||||
setIsPublic(false);
|
setIsPublic(false);
|
||||||
setPublicRole(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
setPublicRole(AccessRoleIds.AGENT_VIEWER);
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error granting access:', error);
|
console.error('Error granting access:', error);
|
||||||
|
@ -155,9 +155,9 @@ export default function GrantAccessDialog({
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setNewShares([]);
|
setNewShares([]);
|
||||||
setDefaultPermissionId(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
setDefaultPermissionId(AccessRoleIds.AGENT_VIEWER);
|
||||||
setIsPublic(false);
|
setIsPublic(false);
|
||||||
setPublicRole(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
setPublicRole(AccessRoleIds.AGENT_VIEWER);
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
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 { Settings, Users, Loader, UserCheck, Trash2, Shield } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
useGetAccessRolesQuery,
|
useGetAccessRolesQuery,
|
||||||
useGetResourcePermissionsQuery,
|
useGetResourcePermissionsQuery,
|
||||||
useUpdateResourcePermissionsMutation,
|
useUpdateResourcePermissionsMutation,
|
||||||
} from 'librechat-data-provider/react-query';
|
} from 'librechat-data-provider/react-query';
|
||||||
|
import type { TPrincipal } from 'librechat-data-provider';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
OGDialog,
|
OGDialog,
|
||||||
|
@ -15,21 +16,25 @@ import {
|
||||||
OGDialogTrigger,
|
OGDialogTrigger,
|
||||||
useToastContext,
|
useToastContext,
|
||||||
} from '@librechat/client';
|
} from '@librechat/client';
|
||||||
import SelectedPrincipalsList from './PeoplePicker/SelectedPrincipalsList';
|
import { SelectedPrincipalsList } from './PeoplePicker';
|
||||||
import PublicSharingToggle from './PublicSharingToggle';
|
import PublicSharingToggle from './PublicSharingToggle';
|
||||||
import { cn, removeFocusOutlines } from '~/utils';
|
import { cn, removeFocusOutlines } from '~/utils';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
export default function ManagePermissionsDialog({
|
export default function ManagePermissionsDialog({
|
||||||
agentDbId,
|
|
||||||
agentName,
|
agentName,
|
||||||
resourceType = 'agent',
|
resourceType = ResourceType.AGENT,
|
||||||
|
agentDbId,
|
||||||
onUpdatePermissions,
|
onUpdatePermissions,
|
||||||
}: {
|
}: {
|
||||||
agentDbId: string;
|
agentDbId: string;
|
||||||
agentName?: string;
|
agentName?: string;
|
||||||
resourceType?: string;
|
resourceType?: ResourceType;
|
||||||
onUpdatePermissions?: (shares: TPrincipal[], isPublic: boolean, publicRole: string) => void;
|
onUpdatePermissions?: (
|
||||||
|
shares: TPrincipal[],
|
||||||
|
isPublic: boolean,
|
||||||
|
publicRole: AccessRoleIds,
|
||||||
|
) => void;
|
||||||
}) {
|
}) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { showToast } = useToastContext();
|
const { showToast } = useToastContext();
|
||||||
|
@ -50,20 +55,22 @@ export default function ManagePermissionsDialog({
|
||||||
|
|
||||||
const [managedShares, setManagedShares] = useState<TPrincipal[]>([]);
|
const [managedShares, setManagedShares] = useState<TPrincipal[]>([]);
|
||||||
const [managedIsPublic, setManagedIsPublic] = useState(false);
|
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 [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [hasChanges, setHasChanges] = useState(false);
|
const [hasChanges, setHasChanges] = useState(false);
|
||||||
|
|
||||||
const currentShares: TPrincipal[] = permissionsData?.principals || [];
|
const currentShares: TPrincipal[] = permissionsData?.principals || [];
|
||||||
|
|
||||||
const isPublic = permissionsData?.public || false;
|
const isPublic = permissionsData?.public || false;
|
||||||
const publicRole = permissionsData?.publicAccessRoleId || ACCESS_ROLE_IDS.AGENT_VIEWER;
|
const publicRole = permissionsData?.publicAccessRoleId || AccessRoleIds.AGENT_VIEWER;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (permissionsData) {
|
if (permissionsData) {
|
||||||
const shares = permissionsData.principals || [];
|
const shares = permissionsData.principals || [];
|
||||||
const isPublicValue = permissionsData.public || false;
|
const isPublicValue = permissionsData.public || false;
|
||||||
const publicRoleValue = permissionsData.publicAccessRoleId || ACCESS_ROLE_IDS.AGENT_VIEWER;
|
const publicRoleValue = permissionsData.publicAccessRoleId || AccessRoleIds.AGENT_VIEWER;
|
||||||
|
|
||||||
setManagedShares(shares);
|
setManagedShares(shares);
|
||||||
setManagedIsPublic(isPublicValue);
|
setManagedIsPublic(isPublicValue);
|
||||||
|
@ -85,7 +92,7 @@ export default function ManagePermissionsDialog({
|
||||||
setHasChanges(true);
|
setHasChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRoleChange = (idOnTheSource: string, newRole: string) => {
|
const handleRoleChange = (idOnTheSource: string, newRole: AccessRoleIds) => {
|
||||||
setManagedShares(
|
setManagedShares(
|
||||||
managedShares.map((s) =>
|
managedShares.map((s) =>
|
||||||
s.idOnTheSource === idOnTheSource ? { ...s, accessRoleId: newRole } : s,
|
s.idOnTheSource === idOnTheSource ? { ...s, accessRoleId: newRole } : s,
|
||||||
|
@ -160,10 +167,10 @@ export default function ManagePermissionsDialog({
|
||||||
setManagedIsPublic(isPublic);
|
setManagedIsPublic(isPublic);
|
||||||
setHasChanges(true);
|
setHasChanges(true);
|
||||||
if (!isPublic) {
|
if (!isPublic) {
|
||||||
setManagedPublicRole(ACCESS_ROLE_IDS.AGENT_VIEWER);
|
setManagedPublicRole(AccessRoleIds.AGENT_VIEWER);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handlePublicRoleChange = (role: string) => {
|
const handlePublicRoleChange = (role: AccessRoleIds) => {
|
||||||
setManagedPublicRole(role);
|
setManagedPublicRole(role);
|
||||||
setHasChanges(true);
|
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) */
|
/** Check if there's at least one owner (user, group, or public with owner role) */
|
||||||
const hasAtLeastOneOwner =
|
const hasAtLeastOneOwner =
|
||||||
managedShares.some((share) => share.accessRoleId === ACCESS_ROLE_IDS.AGENT_OWNER) ||
|
managedShares.some((share) => share.accessRoleId === AccessRoleIds.AGENT_OWNER) ||
|
||||||
(managedIsPublic && managedPublicRole === ACCESS_ROLE_IDS.AGENT_OWNER);
|
(managedIsPublic && managedPublicRole === AccessRoleIds.AGENT_OWNER);
|
||||||
|
|
||||||
let peopleLabel = localize('com_ui_people');
|
let peopleLabel = localize('com_ui_people');
|
||||||
if (managedShares.length === 1) {
|
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 { useSearchPrincipalsQuery } from 'librechat-data-provider/react-query';
|
||||||
import PeoplePickerSearchItem from './PeoplePickerSearchItem';
|
import PeoplePickerSearchItem from './PeoplePickerSearchItem';
|
||||||
import SelectedPrincipalsList from './SelectedPrincipalsList';
|
import SelectedPrincipalsList from './SelectedPrincipalsList';
|
||||||
import { SearchPicker } from '~/components/ui/SearchPicker';
|
import { SearchPicker } from './SearchPicker';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
interface PeoplePickerProps {
|
interface PeoplePickerProps {
|
|
@ -1,4 +1,5 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
import { Search, X } from 'lucide-react';
|
import { Search, X } from 'lucide-react';
|
||||||
import * as Ariakit from '@ariakit/react';
|
import * as Ariakit from '@ariakit/react';
|
||||||
import { Spinner, Skeleton } from '@librechat/client';
|
import { Spinner, Skeleton } from '@librechat/client';
|
||||||
|
@ -36,10 +37,31 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const [_open, setOpen] = React.useState(false);
|
const [_open, setOpen] = React.useState(false);
|
||||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
const [localQuery, setLocalQuery] = React.useState(query);
|
||||||
const combobox = Ariakit.useComboboxStore({
|
const combobox = Ariakit.useComboboxStore({
|
||||||
resetValueOnHide,
|
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) => {
|
const onPickHandler = (option: TOption) => {
|
||||||
|
setLocalQuery('');
|
||||||
onQueryChange('');
|
onQueryChange('');
|
||||||
onPick(option);
|
onPick(option);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
@ -47,9 +69,11 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const showClearIcon = query.trim().length > 0;
|
const showClearIcon = localQuery.trim().length > 0;
|
||||||
const clearText = () => {
|
const clearText = () => {
|
||||||
|
setLocalQuery('');
|
||||||
onQueryChange('');
|
onQueryChange('');
|
||||||
|
debouncedOnQueryChange.cancel();
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
}
|
}
|
||||||
|
@ -77,7 +101,9 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
|
||||||
if (e.key === 'Escape' && combobox.getState().open) {
|
if (e.key === 'Escape' && combobox.getState().open) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
setLocalQuery('');
|
||||||
onQueryChange('');
|
onQueryChange('');
|
||||||
|
debouncedOnQueryChange.cancel();
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -85,9 +111,11 @@ export function SearchPicker<TOption extends { key: string; value: string }>({
|
||||||
setValueOnClick={false}
|
setValueOnClick={false}
|
||||||
setValueOnChange={false}
|
setValueOnChange={false}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onQueryChange(e.target.value);
|
const value = e.target.value;
|
||||||
|
setLocalQuery(value);
|
||||||
|
debouncedOnQueryChange(value);
|
||||||
}}
|
}}
|
||||||
value={query}
|
value={localQuery}
|
||||||
// autoSelect
|
// autoSelect
|
||||||
placeholder={placeholder || localize('com_ui_select_options')}
|
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"
|
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={
|
open={
|
||||||
isLoading ||
|
isLoading ||
|
||||||
options.length > 0 ||
|
options.length > 0 ||
|
||||||
(query.trim().length >= minQueryLengthForNoResults && !isLoading)
|
(localQuery.trim().length >= minQueryLengthForNoResults && !isLoading)
|
||||||
}
|
}
|
||||||
store={combobox}
|
store={combobox}
|
||||||
unmountOnHide
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
|
@ -2,7 +2,7 @@ import React, { useState, useId } from 'react';
|
||||||
import * as Menu from '@ariakit/react/menu';
|
import * as Menu from '@ariakit/react/menu';
|
||||||
import { Button, DropdownPopup } from '@librechat/client';
|
import { Button, DropdownPopup } from '@librechat/client';
|
||||||
import { Users, X, ExternalLink, ChevronDown } from 'lucide-react';
|
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 { getRoleLocalizationKeys } from '~/utils';
|
||||||
import PrincipalAvatar from '../PrincipalAvatar';
|
import PrincipalAvatar from '../PrincipalAvatar';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
@ -10,7 +10,7 @@ import { useLocalize } from '~/hooks';
|
||||||
interface SelectedPrincipalsListProps {
|
interface SelectedPrincipalsListProps {
|
||||||
principles: TPrincipal[];
|
principles: TPrincipal[];
|
||||||
onRemoveHandler: (idOnTheSource: string) => void;
|
onRemoveHandler: (idOnTheSource: string) => void;
|
||||||
onRoleChange?: (idOnTheSource: string, newRoleId: string) => void;
|
onRoleChange?: (idOnTheSource: string, newRoleId: AccessRoleIds) => void;
|
||||||
availableRoles?: Omit<TAccessRole, 'resourceType'>[];
|
availableRoles?: Omit<TAccessRole, 'resourceType'>[];
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
@ -98,8 +98,8 @@ export default function SelectedPrincipalsList({
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RoleSelectorProps {
|
interface RoleSelectorProps {
|
||||||
currentRole: ACCESS_ROLE_IDS;
|
currentRole: AccessRoleIds;
|
||||||
onRoleChange: (newRole: ACCESS_ROLE_IDS) => void;
|
onRoleChange: (newRole: AccessRoleIds) => void;
|
||||||
availableRoles: Omit<TAccessRole, 'resourceType'>[];
|
availableRoles: Omit<TAccessRole, 'resourceType'>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ function RoleSelector({ currentRole, onRoleChange, availableRoles }: RoleSelecto
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
||||||
const getLocalizedRoleName = (roleId: ACCESS_ROLE_IDS) => {
|
const getLocalizedRoleName = (roleId: AccessRoleIds) => {
|
||||||
const keys = getRoleLocalizationKeys(roleId);
|
const keys = getRoleLocalizationKeys(roleId);
|
||||||
return localize(keys.name);
|
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 React from 'react';
|
||||||
import { Globe, Shield } from 'lucide-react';
|
|
||||||
import { Switch } from '@librechat/client';
|
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';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
export default function PublicSharingToggle({
|
export default function PublicSharingToggle({
|
||||||
|
@ -9,13 +11,13 @@ export default function PublicSharingToggle({
|
||||||
publicRole,
|
publicRole,
|
||||||
onPublicToggle,
|
onPublicToggle,
|
||||||
onPublicRoleChange,
|
onPublicRoleChange,
|
||||||
resourceType = 'agent',
|
resourceType = ResourceType.AGENT,
|
||||||
}: {
|
}: {
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
publicRole: string;
|
publicRole: AccessRoleIds;
|
||||||
onPublicToggle: (isPublic: boolean) => void;
|
onPublicToggle: (isPublic: boolean) => void;
|
||||||
onPublicRoleChange: (role: string) => void;
|
onPublicRoleChange: (role: AccessRoleIds) => void;
|
||||||
resourceType?: string;
|
resourceType?: ResourceType;
|
||||||
}) {
|
}) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
export { default as AccessRolesPicker } from './AccessRolesPicker';
|
||||||
export { default as GenericGrantAccessDialog } from './GenericGrantAccessDialog';
|
export { default as GenericGrantAccessDialog } from './GenericGrantAccessDialog';
|
||||||
export { default as GenericManagePermissionsDialog } from './GenericManagePermissionsDialog';
|
export { default as GenericManagePermissionsDialog } from './GenericManagePermissionsDialog';
|
||||||
|
export { default as ManagePermissionsDialog } from './ManagePermissionsDialog';
|
||||||
|
export { default as PrincipalAvatar } from './PrincipalAvatar';
|
||||||
export { default as PublicSharingToggle } from './PublicSharingToggle';
|
export { default as PublicSharingToggle } from './PublicSharingToggle';
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import * as Ariakit from '@ariakit/react';
|
|
||||||
import { useMemo, useEffect, useState } from 'react';
|
import { useMemo, useEffect, useState } from 'react';
|
||||||
|
import * as Ariakit from '@ariakit/react';
|
||||||
import { ShieldEllipsis } from 'lucide-react';
|
import { ShieldEllipsis } from 'lucide-react';
|
||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import { Permissions, SystemRoles, roleDefaults, PermissionTypes } from 'librechat-data-provider';
|
import { Permissions, SystemRoles, roleDefaults, PermissionTypes } from 'librechat-data-provider';
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
|
Switch,
|
||||||
OGDialog,
|
OGDialog,
|
||||||
|
DropdownPopup,
|
||||||
OGDialogTitle,
|
OGDialogTitle,
|
||||||
OGDialogContent,
|
OGDialogContent,
|
||||||
OGDialogTrigger,
|
OGDialogTrigger,
|
||||||
Button,
|
|
||||||
Switch,
|
|
||||||
DropdownPopup,
|
|
||||||
useToastContext,
|
useToastContext,
|
||||||
} from '@librechat/client';
|
} from '@librechat/client';
|
||||||
import type { Control, UseFormSetValue, UseFormGetValues } from 'react-hook-form';
|
import type { Control, UseFormSetValue, UseFormGetValues } from 'react-hook-form';
|
||||||
|
@ -64,8 +64,8 @@ const LabelController: React.FC<LabelControllerProps> = ({
|
||||||
|
|
||||||
const AdminSettings = () => {
|
const AdminSettings = () => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { user, roles } = useAuthContext();
|
|
||||||
const { showToast } = useToastContext();
|
const { showToast } = useToastContext();
|
||||||
|
const { user, roles } = useAuthContext();
|
||||||
const { mutate, isLoading } = useUpdateAgentPermissionsMutation({
|
const { mutate, isLoading } = useUpdateAgentPermissionsMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
showToast({ status: 'success', message: localize('com_ui_saved') });
|
showToast({ status: 'success', message: localize('com_ui_saved') });
|
||||||
|
@ -79,8 +79,9 @@ const AdminSettings = () => {
|
||||||
const [selectedRole, setSelectedRole] = useState<SystemRoles>(SystemRoles.USER);
|
const [selectedRole, setSelectedRole] = useState<SystemRoles>(SystemRoles.USER);
|
||||||
|
|
||||||
const defaultValues = useMemo(() => {
|
const defaultValues = useMemo(() => {
|
||||||
if (roles?.[selectedRole]?.permissions) {
|
const rolePerms = roles?.[selectedRole]?.permissions;
|
||||||
return roles[selectedRole].permissions[PermissionTypes.AGENTS];
|
if (rolePerms) {
|
||||||
|
return rolePerms[PermissionTypes.AGENTS];
|
||||||
}
|
}
|
||||||
return roleDefaults[selectedRole].permissions[PermissionTypes.AGENTS];
|
return roleDefaults[selectedRole].permissions[PermissionTypes.AGENTS];
|
||||||
}, [roles, selectedRole]);
|
}, [roles, selectedRole]);
|
||||||
|
@ -98,8 +99,9 @@ const AdminSettings = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (roles?.[selectedRole]?.permissions?.[PermissionTypes.AGENTS]) {
|
const value = roles?.[selectedRole]?.permissions?.[PermissionTypes.AGENTS];
|
||||||
reset(roles[selectedRole].permissions[PermissionTypes.AGENTS]);
|
if (value) {
|
||||||
|
reset(value);
|
||||||
} else {
|
} else {
|
||||||
reset(roleDefaults[selectedRole].permissions[PermissionTypes.AGENTS]);
|
reset(roleDefaults[selectedRole].permissions[PermissionTypes.AGENTS]);
|
||||||
}
|
}
|
||||||
|
@ -211,7 +213,8 @@ const AdminSettings = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="button"
|
||||||
|
onClick={handleSubmit(onSubmit)}
|
||||||
disabled={isSubmitting || isLoading}
|
disabled={isSubmitting || isLoading}
|
||||||
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
|
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 {
|
import {
|
||||||
SystemRoles,
|
SystemRoles,
|
||||||
Permissions,
|
Permissions,
|
||||||
|
ResourceType,
|
||||||
PermissionTypes,
|
PermissionTypes,
|
||||||
PERMISSION_BITS,
|
PermissionBits,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { AgentForm, AgentPanelProps } from '~/common';
|
import type { AgentForm, AgentPanelProps } from '~/common';
|
||||||
import { useLocalize, useAuthContext, useHasAccess, useResourcePermissions } from '~/hooks';
|
import { useLocalize, useAuthContext, useHasAccess, useResourcePermissions } from '~/hooks';
|
||||||
|
@ -46,8 +47,8 @@ export default function AgentFooter({
|
||||||
agent?._id || '',
|
agent?._id || '',
|
||||||
);
|
);
|
||||||
|
|
||||||
const canShareThisAgent = hasPermission(PERMISSION_BITS.SHARE);
|
const canShareThisAgent = hasPermission(PermissionBits.SHARE);
|
||||||
const canDeleteThisAgent = hasPermission(PERMISSION_BITS.DELETE);
|
const canDeleteThisAgent = hasPermission(PermissionBits.DELETE);
|
||||||
const renderSaveButton = () => {
|
const renderSaveButton = () => {
|
||||||
if (createMutation.isLoading || updateMutation.isLoading) {
|
if (createMutation.isLoading || updateMutation.isLoading) {
|
||||||
return <Spinner className="icon-md" aria-hidden="true" />;
|
return <Spinner className="icon-md" aria-hidden="true" />;
|
||||||
|
@ -84,7 +85,7 @@ export default function AgentFooter({
|
||||||
resourceDbId={agent?._id}
|
resourceDbId={agent?._id}
|
||||||
resourceId={agent_id}
|
resourceId={agent_id}
|
||||||
resourceName={agent?.name ?? ''}
|
resourceName={agent?.name ?? ''}
|
||||||
resourceType="agent"
|
resourceType={ResourceType.AGENT}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{agent && agent.author === user?.id && <DuplicateAgent agent_id={agent_id} />}
|
{agent && agent.author === user?.id && <DuplicateAgent agent_id={agent_id} />}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
Constants,
|
Constants,
|
||||||
SystemRoles,
|
SystemRoles,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
PERMISSION_BITS,
|
PermissionBits,
|
||||||
isAssistantsEndpoint,
|
isAssistantsEndpoint,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { AgentForm, StringOption } from '~/common';
|
import type { AgentForm, StringOption } from '~/common';
|
||||||
|
@ -57,7 +57,7 @@ export default function AgentPanel() {
|
||||||
basicAgentQuery.data?._id || '',
|
basicAgentQuery.data?._id || '',
|
||||||
);
|
);
|
||||||
|
|
||||||
const canEdit = hasPermission(PERMISSION_BITS.EDIT);
|
const canEdit = hasPermission(PermissionBits.EDIT);
|
||||||
|
|
||||||
const expandedAgentQuery = useGetExpandedAgentByIdQuery(current_agent_id ?? '', {
|
const expandedAgentQuery = useGetExpandedAgentByIdQuery(current_agent_id ?? '', {
|
||||||
enabled:
|
enabled:
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { Controller, useFormContext } from 'react-hook-form';
|
||||||
import type { TSpecialVarLabel } from 'librechat-data-provider';
|
import type { TSpecialVarLabel } from 'librechat-data-provider';
|
||||||
import type { AgentForm } from '~/common';
|
import type { AgentForm } from '~/common';
|
||||||
import { cn, defaultTextProps, removeFocusOutlines } from '~/utils';
|
import { cn, defaultTextProps, removeFocusOutlines } from '~/utils';
|
||||||
// import { ControlCombobox } from '@librechat/client';
|
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
const inputClass = cn(
|
const inputClass = cn(
|
||||||
|
@ -49,26 +48,6 @@ export default function Instructions() {
|
||||||
{localize('com_ui_instructions')}
|
{localize('com_ui_instructions')}
|
||||||
</label>
|
</label>
|
||||||
<div className="ml-auto" title="Add variables to instructions">
|
<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
|
<DropdownPopup
|
||||||
portal={true}
|
portal={true}
|
||||||
mountByState={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 { render, screen } from '@testing-library/react';
|
||||||
import type { UseMutationResult } from '@tanstack/react-query';
|
import type { UseMutationResult } from '@tanstack/react-query';
|
||||||
import '@testing-library/jest-dom/extend-expect';
|
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 AgentFooter from '../AgentFooter';
|
||||||
import { Panel } from '~/common';
|
import { Panel } from '~/common';
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ jest.mock('~/components/Sharing', () => ({
|
||||||
resourceDbId: string;
|
resourceDbId: string;
|
||||||
resourceId: string;
|
resourceId: string;
|
||||||
resourceName: string;
|
resourceName: string;
|
||||||
resourceType: string;
|
resourceType: ResourceType;
|
||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
data-testid="grant-access-dialog"
|
data-testid="grant-access-dialog"
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export * from './ui';
|
export * from './ui';
|
||||||
export * from './Plugins';
|
export * from './Plugins';
|
||||||
export * from './svg';
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
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 * as t from 'librechat-data-provider';
|
||||||
import type { QueryClient, UseMutationResult } from '@tanstack/react-query';
|
import type { QueryClient, UseMutationResult } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ import type { QueryClient, UseMutationResult } from '@tanstack/react-query';
|
||||||
* AGENTS
|
* AGENTS
|
||||||
*/
|
*/
|
||||||
export const allAgentViewAndEditQueryKeys: t.AgentListParams[] = [
|
export const allAgentViewAndEditQueryKeys: t.AgentListParams[] = [
|
||||||
{ requiredPermission: PERMISSION_BITS.VIEW },
|
{ requiredPermission: PermissionBits.VIEW },
|
||||||
{ requiredPermission: PERMISSION_BITS.EDIT },
|
{ requiredPermission: PermissionBits.EDIT },
|
||||||
];
|
];
|
||||||
/**
|
/**
|
||||||
* Create a new agent
|
* Create a new agent
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {
|
||||||
dataService,
|
dataService,
|
||||||
Permissions,
|
Permissions,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
PERMISSION_BITS,
|
PermissionBits,
|
||||||
PermissionTypes,
|
PermissionTypes,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
|
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 active: EDIT permissions (builder mode)
|
||||||
// When marketplace is not active: VIEW permissions (browse 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 = {
|
export const defaultAgentParams: t.AgentListParams = {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
requiredPermission: PERMISSION_BITS.EDIT,
|
requiredPermission: PermissionBits.EDIT,
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Hook for getting all available tools for A
|
* Hook for getting all available tools for A
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
isAgentsEndpoint,
|
isAgentsEndpoint,
|
||||||
getConfigDefaults,
|
getConfigDefaults,
|
||||||
isAssistantsEndpoint,
|
isAssistantsEndpoint,
|
||||||
PERMISSION_BITS,
|
PermissionBits,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider';
|
import type { TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider';
|
||||||
import type { MentionOption } from '~/common';
|
import type { MentionOption } from '~/common';
|
||||||
|
@ -81,7 +81,7 @@ export default function useMentions({
|
||||||
[startupConfig?.interface],
|
[startupConfig?.interface],
|
||||||
);
|
);
|
||||||
const { data: agentsList = null } = useListAgentsQuery(
|
const { data: agentsList = null } = useListAgentsQuery(
|
||||||
{ requiredPermission: PERMISSION_BITS.VIEW },
|
{ requiredPermission: PermissionBits.VIEW },
|
||||||
{
|
{
|
||||||
enabled: hasAgentAccess && interfaceConfig.modelSelect === true,
|
enabled: hasAgentAccess && interfaceConfig.modelSelect === true,
|
||||||
select: (res) => {
|
select: (res) => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
isAgentsEndpoint,
|
isAgentsEndpoint,
|
||||||
tQueryParamsSchema,
|
tQueryParamsSchema,
|
||||||
isAssistantsEndpoint,
|
isAssistantsEndpoint,
|
||||||
PERMISSION_BITS,
|
PermissionBits,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type {
|
import type {
|
||||||
TPreset,
|
TPreset,
|
||||||
|
@ -80,7 +80,7 @@ const processValidSettings = (queryParams: Record<string, string>) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const injectAgentIntoAgentsMap = (queryClient: QueryClient, agent: any) => {
|
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);
|
const editCache = queryClient.getQueryData<AgentListResponse>(editCacheKey);
|
||||||
|
|
||||||
if (editCache?.data && !editCache.data.some((cachedAgent) => cachedAgent.id === agent.id)) {
|
if (editCache?.data && !editCache.data.some((cachedAgent) => cachedAgent.id === agent.id)) {
|
||||||
|
|
|
@ -3,18 +3,18 @@ import {
|
||||||
useGetResourcePermissionsQuery,
|
useGetResourcePermissionsQuery,
|
||||||
useUpdateResourcePermissionsMutation,
|
useUpdateResourcePermissionsMutation,
|
||||||
} from 'librechat-data-provider/react-query';
|
} 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';
|
import { getResourceConfig } from '~/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to manage resource permission state including current shares, public access, and mutations
|
* 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 resourceDbId - Database ID of the resource
|
||||||
* @param isModalOpen - Whether the modal is open (for effect dependencies)
|
* @param isModalOpen - Whether the modal is open (for effect dependencies)
|
||||||
* @returns Object with permission state and update mutation
|
* @returns Object with permission state and update mutation
|
||||||
*/
|
*/
|
||||||
export const useResourcePermissionState = (
|
export const useResourcePermissionState = (
|
||||||
resourceType: string,
|
resourceType: ResourceType,
|
||||||
resourceDbId: string | null | undefined,
|
resourceDbId: string | null | undefined,
|
||||||
isModalOpen: boolean = false,
|
isModalOpen: boolean = false,
|
||||||
) => {
|
) => {
|
||||||
|
@ -52,13 +52,15 @@ export const useResourcePermissionState = (
|
||||||
|
|
||||||
// State for managing public access
|
// State for managing public access
|
||||||
const [isPublic, setIsPublic] = useState(false);
|
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
|
// Sync state with permissions data when modal opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (permissionsData && isModalOpen) {
|
if (permissionsData && isModalOpen) {
|
||||||
setIsPublic(currentIsPublic ?? false);
|
setIsPublic(currentIsPublic ?? false);
|
||||||
setPublicRole(currentPublicRole ?? '');
|
setPublicRole(currentPublicRole);
|
||||||
}
|
}
|
||||||
}, [permissionsData, isModalOpen, currentIsPublic, currentPublicRole]);
|
}, [permissionsData, isModalOpen, currentIsPublic, currentPublicRole]);
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ export * from './Messages';
|
||||||
export * from './Plugins';
|
export * from './Plugins';
|
||||||
export * from './Prompts';
|
export * from './Prompts';
|
||||||
export * from './Roles';
|
export * from './Roles';
|
||||||
|
export * from './Sharing';
|
||||||
export * from './SSE';
|
export * from './SSE';
|
||||||
export * from './AuthContext';
|
export * from './AuthContext';
|
||||||
export * from './ScreenshotContext';
|
export * from './ScreenshotContext';
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import {
|
import {
|
||||||
useGetEffectivePermissionsQuery,
|
|
||||||
hasPermissions,
|
hasPermissions,
|
||||||
|
useGetEffectivePermissionsQuery,
|
||||||
} from 'librechat-data-provider/react-query';
|
} 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
|
* fetches resource permissions once and returns a function to check any permission
|
||||||
* More efficient when checking multiple permissions for the same resource
|
* 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
|
* @param resourceId - ID of the resource
|
||||||
* @returns Object with hasPermission function and loading state
|
* @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 { data, isLoading } = useGetEffectivePermissionsQuery(resourceType, resourceId);
|
||||||
|
|
||||||
const hasPermission = (requiredPermission: number): boolean => {
|
const hasPermission = (requiredPermission: number): boolean => {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
TwoFactorScreen,
|
TwoFactorScreen,
|
||||||
RequestPasswordReset,
|
RequestPasswordReset,
|
||||||
} from '~/components/Auth';
|
} from '~/components/Auth';
|
||||||
|
import AgentMarketplace from '~/components/Agents/Marketplace';
|
||||||
import { OAuthSuccess, OAuthError } from '~/components/OAuth';
|
import { OAuthSuccess, OAuthError } from '~/components/OAuth';
|
||||||
import { AuthContextProvider } from '~/hooks/AuthContext';
|
import { AuthContextProvider } from '~/hooks/AuthContext';
|
||||||
import RouteErrorBoundary from './RouteErrorBoundary';
|
import RouteErrorBoundary from './RouteErrorBoundary';
|
||||||
|
@ -18,7 +19,6 @@ import ShareRoute from './ShareRoute';
|
||||||
import ChatRoute from './ChatRoute';
|
import ChatRoute from './ChatRoute';
|
||||||
import Search from './Search';
|
import Search from './Search';
|
||||||
import Root from './Root';
|
import Root from './Root';
|
||||||
import AgentMarketplace from '~/components/SidePanel/Agents/AgentMarketplace';
|
|
||||||
|
|
||||||
const AuthLayout = () => (
|
const AuthLayout = () => (
|
||||||
<AuthContextProvider>
|
<AuthContextProvider>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { ACCESS_ROLE_IDS } from 'librechat-data-provider';
|
import { AccessRoleIds, ResourceType } from 'librechat-data-provider';
|
||||||
|
|
||||||
export interface ResourceConfig {
|
export interface ResourceConfig {
|
||||||
resourceType: string;
|
resourceType: ResourceType;
|
||||||
defaultViewerRoleId: string;
|
defaultViewerRoleId: AccessRoleIds;
|
||||||
defaultEditorRoleId: string;
|
defaultEditorRoleId: AccessRoleIds;
|
||||||
defaultOwnerRoleId: string;
|
defaultOwnerRoleId: AccessRoleIds;
|
||||||
getResourceUrl?: (resourceId: string) => string;
|
getResourceUrl?: (resourceId: string) => string;
|
||||||
getResourceName: (resourceName?: string) => string;
|
getResourceName: (resourceName?: string) => string;
|
||||||
getShareMessage: (resourceName?: string) => string;
|
getShareMessage: (resourceName?: string) => string;
|
||||||
|
@ -12,12 +12,12 @@ export interface ResourceConfig {
|
||||||
getCopyUrlMessage: () => string;
|
getCopyUrlMessage: () => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RESOURCE_CONFIGS: Record<string, ResourceConfig> = {
|
export const RESOURCE_CONFIGS: Record<ResourceType, ResourceConfig> = {
|
||||||
agent: {
|
[ResourceType.AGENT]: {
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
defaultViewerRoleId: ACCESS_ROLE_IDS.AGENT_VIEWER,
|
defaultViewerRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
defaultEditorRoleId: ACCESS_ROLE_IDS.AGENT_EDITOR,
|
defaultEditorRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
defaultOwnerRoleId: ACCESS_ROLE_IDS.AGENT_OWNER,
|
defaultOwnerRoleId: AccessRoleIds.AGENT_OWNER,
|
||||||
getResourceUrl: (agentId: string) => `${window.location.origin}/c/new?agent_id=${agentId}`,
|
getResourceUrl: (agentId: string) => `${window.location.origin}/c/new?agent_id=${agentId}`,
|
||||||
getResourceName: (name?: string) => (name && name !== '' ? `"${name}"` : 'agent'),
|
getResourceName: (name?: string) => (name && name !== '' ? `"${name}"` : 'agent'),
|
||||||
getShareMessage: (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'}`,
|
`Manage permissions for ${name && name !== '' ? `"${name}"` : 'agent'}`,
|
||||||
getCopyUrlMessage: () => 'Agent URL copied',
|
getCopyUrlMessage: () => 'Agent URL copied',
|
||||||
},
|
},
|
||||||
promptGroup: {
|
[ResourceType.PROMPTGROUP]: {
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
defaultViewerRoleId: ACCESS_ROLE_IDS.PROMPTGROUP_VIEWER,
|
defaultViewerRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
defaultEditorRoleId: ACCESS_ROLE_IDS.PROMPTGROUP_EDITOR,
|
defaultEditorRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||||
defaultOwnerRoleId: ACCESS_ROLE_IDS.PROMPTGROUP_OWNER,
|
defaultOwnerRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
getResourceName: (name?: string) => (name && name !== '' ? `"${name}"` : 'prompt'),
|
getResourceName: (name?: string) => (name && name !== '' ? `"${name}"` : 'prompt'),
|
||||||
getShareMessage: (name?: string) => (name && name !== '' ? `"${name}"` : 'prompt'),
|
getShareMessage: (name?: string) => (name && name !== '' ? `"${name}"` : 'prompt'),
|
||||||
getManageMessage: (name?: string) =>
|
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];
|
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';
|
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
|
* @returns Object with name and description localization keys, or unknown keys if not found
|
||||||
*/
|
*/
|
||||||
export const getRoleLocalizationKeys = (
|
export const getRoleLocalizationKeys = (
|
||||||
roleId: ACCESS_ROLE_IDS,
|
roleId: AccessRoleIds,
|
||||||
): {
|
): {
|
||||||
name: TranslationKeys;
|
name: TranslationKeys;
|
||||||
description: TranslationKeys;
|
description: TranslationKeys;
|
||||||
|
|
|
@ -4,6 +4,7 @@ const path = require('path');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
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 { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
|
||||||
|
@ -164,9 +165,9 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: agent.author,
|
principalId: agent.author,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: 'agent_owner',
|
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||||
grantedBy: agent.author,
|
grantedBy: agent.author,
|
||||||
});
|
});
|
||||||
results.ownerGrants++;
|
results.ownerGrants++;
|
||||||
|
@ -178,12 +179,12 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100
|
||||||
if (isGlobal) {
|
if (isGlobal) {
|
||||||
if (isCollab) {
|
if (isCollab) {
|
||||||
// Global project + collaborative = Public EDIT access
|
// Global project + collaborative = Public EDIT access
|
||||||
publicRoleId = 'agent_editor';
|
publicRoleId = AccessRoleIds.AGENT_EDITOR;
|
||||||
description = 'Global Edit';
|
description = 'Global Edit';
|
||||||
results.publicEditGrants++;
|
results.publicEditGrants++;
|
||||||
} else {
|
} else {
|
||||||
// Global project + not collaborative = Public VIEW access
|
// Global project + not collaborative = Public VIEW access
|
||||||
publicRoleId = 'agent_viewer';
|
publicRoleId = AccessRoleIds.AGENT_VIEWER;
|
||||||
description = 'Global View';
|
description = 'Global View';
|
||||||
results.publicViewGrants++;
|
results.publicViewGrants++;
|
||||||
}
|
}
|
||||||
|
@ -192,7 +193,7 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
principalId: null,
|
principalId: null,
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: agent._id,
|
resourceId: agent._id,
|
||||||
accessRoleId: publicRoleId,
|
accessRoleId: publicRoleId,
|
||||||
grantedBy: agent.author,
|
grantedBy: agent.author,
|
||||||
|
|
|
@ -2,6 +2,7 @@ const path = require('path');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
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 { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
|
||||||
|
@ -146,9 +147,9 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: group.author,
|
principalId: group.author,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: group._id,
|
resourceId: group._id,
|
||||||
accessRoleId: 'promptGroup_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
grantedBy: group.author,
|
grantedBy: group.author,
|
||||||
});
|
});
|
||||||
results.ownerGrants++;
|
results.ownerGrants++;
|
||||||
|
@ -158,9 +159,9 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100
|
||||||
await grantPermission({
|
await grantPermission({
|
||||||
principalType: 'public',
|
principalType: 'public',
|
||||||
principalId: null,
|
principalId: null,
|
||||||
resourceType: 'promptGroup',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
resourceId: group._id,
|
resourceId: group._id,
|
||||||
accessRoleId: 'promptGroup_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
grantedBy: group.author,
|
grantedBy: group.author,
|
||||||
});
|
});
|
||||||
results.publicViewGrants++;
|
results.publicViewGrants++;
|
||||||
|
|
|
@ -25,26 +25,35 @@ export type TPrincipalSource = 'local' | 'entra';
|
||||||
*/
|
*/
|
||||||
export type TAccessLevel = 'none' | 'viewer' | 'editor' | 'owner';
|
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
|
* Permission bit constants for bitwise operations
|
||||||
*/
|
*/
|
||||||
export const PERMISSION_BITS = {
|
export enum PermissionBits {
|
||||||
VIEW: 1, // 001 - Can view and use agent
|
/** 001 - Can view and use agent */
|
||||||
EDIT: 2, // 010 - Can modify agent settings
|
VIEW = 1,
|
||||||
DELETE: 4, // 100 - Can delete agent
|
/** 010 - Can modify agent settings */
|
||||||
SHARE: 8, // 1000 - Can share agent with others (future)
|
EDIT = 2,
|
||||||
} as const;
|
/** 100 - Can delete agent */
|
||||||
|
DELETE = 4,
|
||||||
|
/** 1000 - Can share agent with others (future) */
|
||||||
|
SHARE = 8,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard access role IDs
|
* Standard access role IDs
|
||||||
*/
|
*/
|
||||||
export enum ACCESS_ROLE_IDS {
|
export enum AccessRoleIds {
|
||||||
AGENT_VIEWER = 'agent_viewer',
|
AGENT_VIEWER = 'agent_viewer',
|
||||||
AGENT_EDITOR = 'agent_editor',
|
AGENT_EDITOR = 'agent_editor',
|
||||||
AGENT_OWNER = 'agent_owner', // Future use
|
AGENT_OWNER = 'agent_owner',
|
||||||
PROMPT_VIEWER = 'prompt_viewer',
|
|
||||||
PROMPT_EDITOR = 'prompt_editor',
|
|
||||||
PROMPT_OWNER = 'prompt_owner',
|
|
||||||
PROMPTGROUP_VIEWER = 'promptGroup_viewer',
|
PROMPTGROUP_VIEWER = 'promptGroup_viewer',
|
||||||
PROMPTGROUP_EDITOR = 'promptGroup_editor',
|
PROMPTGROUP_EDITOR = 'promptGroup_editor',
|
||||||
PROMPTGROUP_OWNER = 'promptGroup_owner',
|
PROMPTGROUP_OWNER = 'promptGroup_owner',
|
||||||
|
@ -64,7 +73,7 @@ export const principalSchema = z.object({
|
||||||
avatar: z.string().optional(), // for user and group types
|
avatar: z.string().optional(), // for user and group types
|
||||||
description: z.string().optional(), // for group type
|
description: z.string().optional(), // for group type
|
||||||
idOnTheSource: z.string().optional(), // Entra ID for users/groups
|
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
|
memberCount: z.number().optional(), // for group type
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,10 +81,10 @@ export const principalSchema = z.object({
|
||||||
* Access role schema - defines named permission sets
|
* Access role schema - defines named permission sets
|
||||||
*/
|
*/
|
||||||
export const accessRoleSchema = z.object({
|
export const accessRoleSchema = z.object({
|
||||||
accessRoleId: z.nativeEnum(ACCESS_ROLE_IDS),
|
accessRoleId: z.nativeEnum(AccessRoleIds),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
resourceType: z.string().default('agent'),
|
resourceType: z.nativeEnum(ResourceType).default(ResourceType.AGENT),
|
||||||
permBits: z.number(),
|
permBits: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -98,7 +107,7 @@ export const permissionEntrySchema = z.object({
|
||||||
* Resource permissions response schema
|
* Resource permissions response schema
|
||||||
*/
|
*/
|
||||||
export const resourcePermissionsResponseSchema = z.object({
|
export const resourcePermissionsResponseSchema = z.object({
|
||||||
resourceType: z.string(),
|
resourceType: z.nativeEnum(ResourceType),
|
||||||
resourceId: z.string(),
|
resourceId: z.string(),
|
||||||
permissions: z.array(permissionEntrySchema),
|
permissions: z.array(permissionEntrySchema),
|
||||||
});
|
});
|
||||||
|
@ -210,7 +219,7 @@ export type TPrincipalSearchResponse = {
|
||||||
* Available roles response
|
* Available roles response
|
||||||
*/
|
*/
|
||||||
export type TAvailableRolesResponse = {
|
export type TAvailableRolesResponse = {
|
||||||
resourceType: string;
|
resourceType: ResourceType;
|
||||||
roles: TAccessRole[];
|
roles: TAccessRole[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -219,11 +228,11 @@ export type TAvailableRolesResponse = {
|
||||||
* This matches the enhanced aggregation-based endpoint response format
|
* This matches the enhanced aggregation-based endpoint response format
|
||||||
*/
|
*/
|
||||||
export const getResourcePermissionsResponseSchema = z.object({
|
export const getResourcePermissionsResponseSchema = z.object({
|
||||||
resourceType: z.string(),
|
resourceType: z.nativeEnum(ResourceType),
|
||||||
resourceId: z.string(),
|
resourceId: z.nativeEnum(AccessRoleIds),
|
||||||
principals: z.array(principalSchema),
|
principals: z.array(principalSchema),
|
||||||
public: z.boolean(),
|
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
|
* Convert permission bits to access level
|
||||||
*/
|
*/
|
||||||
export function permBitsToAccessLevel(permBits: number): TAccessLevel {
|
export function permBitsToAccessLevel(permBits: number): TAccessLevel {
|
||||||
if ((permBits & PERMISSION_BITS.DELETE) > 0) return 'owner';
|
if ((permBits & PermissionBits.DELETE) > 0) return 'owner';
|
||||||
if ((permBits & PERMISSION_BITS.EDIT) > 0) return 'editor';
|
if ((permBits & PermissionBits.EDIT) > 0) return 'editor';
|
||||||
if ((permBits & PERMISSION_BITS.VIEW) > 0) return 'viewer';
|
if ((permBits & PermissionBits.VIEW) > 0) return 'viewer';
|
||||||
return 'none';
|
return 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,14 +285,14 @@ export function permBitsToAccessLevel(permBits: number): TAccessLevel {
|
||||||
*/
|
*/
|
||||||
export function accessRoleToPermBits(accessRoleId: string): number {
|
export function accessRoleToPermBits(accessRoleId: string): number {
|
||||||
switch (accessRoleId) {
|
switch (accessRoleId) {
|
||||||
case ACCESS_ROLE_IDS.AGENT_VIEWER:
|
case AccessRoleIds.AGENT_VIEWER:
|
||||||
return PERMISSION_BITS.VIEW;
|
return PermissionBits.VIEW;
|
||||||
case ACCESS_ROLE_IDS.AGENT_EDITOR:
|
case AccessRoleIds.AGENT_EDITOR:
|
||||||
return PERMISSION_BITS.VIEW | PERMISSION_BITS.EDIT;
|
return PermissionBits.VIEW | PermissionBits.EDIT;
|
||||||
case ACCESS_ROLE_IDS.AGENT_OWNER:
|
case AccessRoleIds.AGENT_OWNER:
|
||||||
return PERMISSION_BITS.VIEW | PERMISSION_BITS.EDIT | PERMISSION_BITS.DELETE;
|
return PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE;
|
||||||
default:
|
default:
|
||||||
return PERMISSION_BITS.VIEW;
|
return PermissionBits.VIEW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import type { AssistantsEndpoint } from './schemas';
|
import type { AssistantsEndpoint } from './schemas';
|
||||||
import * as q from './types/queries';
|
import * as q from './types/queries';
|
||||||
|
import { ResourceType } from './accessPermissions';
|
||||||
|
|
||||||
// Testing this buildQuery function
|
// Testing this buildQuery function
|
||||||
const buildQuery = (params: Record<string, unknown>): string => {
|
const buildQuery = (params: Record<string, unknown>): string => {
|
||||||
|
@ -323,15 +324,16 @@ export const searchPrincipals = (params: q.PrincipalSearchParams) => {
|
||||||
return url;
|
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}`;
|
`/api/permissions/${resourceType}/${resourceId}`;
|
||||||
|
|
||||||
export const updateResourcePermissions = (resourceType: string, resourceId: string) =>
|
export const updateResourcePermissions = (resourceType: ResourceType, resourceId: string) =>
|
||||||
`/api/permissions/${resourceType}/${resourceId}`;
|
`/api/permissions/${resourceType}/${resourceId}`;
|
||||||
|
|
||||||
export const getEffectivePermissions = (resourceType: string, resourceId: string) =>
|
export const getEffectivePermissions = (resourceType: ResourceType, resourceId: string) =>
|
||||||
`/api/permissions/${resourceType}/${resourceId}/effective`;
|
`/api/permissions/${resourceType}/${resourceId}/effective`;
|
||||||
|
|
||||||
// SharePoint Graph API Token
|
// SharePoint Graph API Token
|
||||||
|
|
|
@ -909,19 +909,21 @@ export function searchPrincipals(
|
||||||
return request.get(endpoints.searchPrincipals(params));
|
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));
|
return request.get(endpoints.getAccessRoles(resourceType));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getResourcePermissions(
|
export function getResourcePermissions(
|
||||||
resourceType: string,
|
resourceType: permissions.ResourceType,
|
||||||
resourceId: string,
|
resourceId: string,
|
||||||
): Promise<permissions.TGetResourcePermissionsResponse> {
|
): Promise<permissions.TGetResourcePermissionsResponse> {
|
||||||
return request.get(endpoints.getResourcePermissions(resourceType, resourceId));
|
return request.get(endpoints.getResourcePermissions(resourceType, resourceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateResourcePermissions(
|
export function updateResourcePermissions(
|
||||||
resourceType: string,
|
resourceType: permissions.ResourceType,
|
||||||
resourceId: string,
|
resourceId: string,
|
||||||
data: permissions.TUpdateResourcePermissionsRequest,
|
data: permissions.TUpdateResourcePermissionsRequest,
|
||||||
): Promise<permissions.TUpdateResourcePermissionsResponse> {
|
): Promise<permissions.TUpdateResourcePermissionsResponse> {
|
||||||
|
@ -929,7 +931,7 @@ export function updateResourcePermissions(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEffectivePermissions(
|
export function getEffectivePermissions(
|
||||||
resourceType: string,
|
resourceType: permissions.ResourceType,
|
||||||
resourceId: string,
|
resourceId: string,
|
||||||
): Promise<permissions.TEffectivePermissionsResponse> {
|
): Promise<permissions.TEffectivePermissionsResponse> {
|
||||||
return request.get(endpoints.getEffectivePermissions(resourceType, resourceId));
|
return request.get(endpoints.getEffectivePermissions(resourceType, resourceId));
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { QueryKeys } from '../keys';
|
||||||
import * as s from '../schemas';
|
import * as s from '../schemas';
|
||||||
import * as t from '../types';
|
import * as t from '../types';
|
||||||
import * as permissions from '../accessPermissions';
|
import * as permissions from '../accessPermissions';
|
||||||
|
import { ResourceType } from '../accessPermissions';
|
||||||
|
|
||||||
export { hasPermissions } from '../accessPermissions';
|
export { hasPermissions } from '../accessPermissions';
|
||||||
|
|
||||||
|
@ -405,7 +406,7 @@ export const useSearchPrincipalsQuery = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetAccessRolesQuery = (
|
export const useGetAccessRolesQuery = (
|
||||||
resourceType: string,
|
resourceType: ResourceType,
|
||||||
config?: UseQueryOptions<q.AccessRolesResponse>,
|
config?: UseQueryOptions<q.AccessRolesResponse>,
|
||||||
): QueryObserverResult<q.AccessRolesResponse> => {
|
): QueryObserverResult<q.AccessRolesResponse> => {
|
||||||
return useQuery<q.AccessRolesResponse>(
|
return useQuery<q.AccessRolesResponse>(
|
||||||
|
@ -423,7 +424,7 @@ export const useGetAccessRolesQuery = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetResourcePermissionsQuery = (
|
export const useGetResourcePermissionsQuery = (
|
||||||
resourceType: string,
|
resourceType: ResourceType,
|
||||||
resourceId: string,
|
resourceId: string,
|
||||||
config?: UseQueryOptions<permissions.TGetResourcePermissionsResponse>,
|
config?: UseQueryOptions<permissions.TGetResourcePermissionsResponse>,
|
||||||
): QueryObserverResult<permissions.TGetResourcePermissionsResponse> => {
|
): QueryObserverResult<permissions.TGetResourcePermissionsResponse> => {
|
||||||
|
@ -445,7 +446,7 @@ export const useUpdateResourcePermissionsMutation = (): UseMutationResult<
|
||||||
permissions.TUpdateResourcePermissionsResponse,
|
permissions.TUpdateResourcePermissionsResponse,
|
||||||
Error,
|
Error,
|
||||||
{
|
{
|
||||||
resourceType: string;
|
resourceType: ResourceType;
|
||||||
resourceId: string;
|
resourceId: string;
|
||||||
data: permissions.TUpdateResourcePermissionsRequest;
|
data: permissions.TUpdateResourcePermissionsRequest;
|
||||||
}
|
}
|
||||||
|
@ -472,7 +473,7 @@ export const useUpdateResourcePermissionsMutation = (): UseMutationResult<
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetEffectivePermissionsQuery = (
|
export const useGetEffectivePermissionsQuery = (
|
||||||
resourceType: string,
|
resourceType: ResourceType,
|
||||||
resourceId: string,
|
resourceId: string,
|
||||||
config?: UseQueryOptions<permissions.TEffectivePermissionsResponse>,
|
config?: UseQueryOptions<permissions.TEffectivePermissionsResponse>,
|
||||||
): QueryObserverResult<permissions.TEffectivePermissionsResponse> => {
|
): QueryObserverResult<permissions.TEffectivePermissionsResponse> => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { InfiniteData } from '@tanstack/react-query';
|
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 a from '../types/agents';
|
||||||
import type * as s from '../schemas';
|
import type * as s from '../schemas';
|
||||||
import type * as t from '../types';
|
import type * as t from '../types';
|
||||||
|
@ -159,7 +159,7 @@ export type PrincipalSearchResponse = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AccessRole = {
|
export type AccessRole = {
|
||||||
accessRoleId: ACCESS_ROLE_IDS;
|
accessRoleId: AccessRoleIds;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
permBits: number;
|
permBits: number;
|
||||||
|
|
|
@ -1,16 +1,4 @@
|
||||||
/**
|
import { PermissionBits } from 'librechat-data-provider';
|
||||||
* 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common role combinations
|
* Common role combinations
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { AccessRoleIds, ResourceType, PermissionBits } from 'librechat-data-provider';
|
||||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
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 type * as t from '~/types';
|
||||||
|
import { createAccessRoleMethods } from './accessRole';
|
||||||
|
import accessRoleSchema from '~/schema/accessRole';
|
||||||
|
import { RoleBits } from '~/common';
|
||||||
|
|
||||||
let mongoServer: MongoMemoryServer;
|
let mongoServer: MongoMemoryServer;
|
||||||
let AccessRole: mongoose.Model<t.IAccessRole>;
|
let AccessRole: mongoose.Model<t.IAccessRole>;
|
||||||
|
@ -32,7 +33,7 @@ describe('AccessRole Model Tests', () => {
|
||||||
accessRoleId: 'test_viewer',
|
accessRoleId: 'test_viewer',
|
||||||
name: 'Test Viewer',
|
name: 'Test Viewer',
|
||||||
description: 'Test role for viewer permissions',
|
description: 'Test role for viewer permissions',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.VIEWER,
|
permBits: RoleBits.VIEWER,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,7 +99,7 @@ describe('AccessRole Model Tests', () => {
|
||||||
accessRoleId: 'test_editor',
|
accessRoleId: 'test_editor',
|
||||||
name: 'Test Editor',
|
name: 'Test Editor',
|
||||||
description: 'Test role for editor permissions',
|
description: 'Test role for editor permissions',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.EDITOR,
|
permBits: RoleBits.EDITOR,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -120,17 +121,17 @@ describe('AccessRole Model Tests', () => {
|
||||||
// Create sample roles for testing
|
// Create sample roles for testing
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
methods.createRole({
|
methods.createRole({
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
name: 'Agent Viewer',
|
name: 'Agent Viewer',
|
||||||
description: 'Can view agents',
|
description: 'Can view agents',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.VIEWER,
|
permBits: RoleBits.VIEWER,
|
||||||
}),
|
}),
|
||||||
methods.createRole({
|
methods.createRole({
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
name: 'Agent Editor',
|
name: 'Agent Editor',
|
||||||
description: 'Can edit agents',
|
description: 'Can edit agents',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.EDITOR,
|
permBits: RoleBits.EDITOR,
|
||||||
}),
|
}),
|
||||||
methods.createRole({
|
methods.createRole({
|
||||||
|
@ -154,7 +155,7 @@ describe('AccessRole Model Tests', () => {
|
||||||
const agentRoles = await methods.findRolesByResourceType('agent');
|
const agentRoles = await methods.findRolesByResourceType('agent');
|
||||||
expect(agentRoles).toHaveLength(2);
|
expect(agentRoles).toHaveLength(2);
|
||||||
expect(agentRoles.map((r) => r.accessRoleId).sort()).toEqual(
|
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');
|
const projectRoles = await methods.findRolesByResourceType('project');
|
||||||
|
@ -167,11 +168,11 @@ describe('AccessRole Model Tests', () => {
|
||||||
test('should find role by permissions', async () => {
|
test('should find role by permissions', async () => {
|
||||||
const viewerRole = await methods.findRoleByPermissions('agent', RoleBits.VIEWER);
|
const viewerRole = await methods.findRoleByPermissions('agent', RoleBits.VIEWER);
|
||||||
expect(viewerRole).toBeDefined();
|
expect(viewerRole).toBeDefined();
|
||||||
expect(viewerRole?.accessRoleId).toBe('agent_viewer');
|
expect(viewerRole?.accessRoleId).toBe(AccessRoleIds.AGENT_VIEWER);
|
||||||
|
|
||||||
const editorRole = await methods.findRoleByPermissions('agent', RoleBits.EDITOR);
|
const editorRole = await methods.findRoleByPermissions('agent', RoleBits.EDITOR);
|
||||||
expect(editorRole).toBeDefined();
|
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 () => {
|
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
|
// Verify the result contains the default roles
|
||||||
expect(Object.keys(result).sort()).toEqual(
|
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
|
// 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).toBeDefined();
|
||||||
expect(agentViewerRole?.permBits).toBe(RoleBits.VIEWER);
|
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).toBeDefined();
|
||||||
expect(agentEditorRole?.permBits).toBe(RoleBits.EDITOR);
|
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).toBeDefined();
|
||||||
expect(agentOwnerRole?.permBits).toBe(RoleBits.OWNER);
|
expect(agentOwnerRole?.permBits).toBe(RoleBits.OWNER);
|
||||||
});
|
});
|
||||||
|
@ -212,10 +220,10 @@ describe('AccessRole Model Tests', () => {
|
||||||
test('should not modify existing roles when seeding', async () => {
|
test('should not modify existing roles when seeding', async () => {
|
||||||
// Create a modified version of a default role
|
// Create a modified version of a default role
|
||||||
const customRole = {
|
const customRole = {
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
name: 'Custom Viewer',
|
name: 'Custom Viewer',
|
||||||
description: 'Custom viewer description',
|
description: 'Custom viewer description',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.VIEWER,
|
permBits: RoleBits.VIEWER,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -225,7 +233,7 @@ describe('AccessRole Model Tests', () => {
|
||||||
await methods.seedDefaultRoles();
|
await methods.seedDefaultRoles();
|
||||||
|
|
||||||
// Verify the custom role was not modified
|
// 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?.name).toBe(customRole.name);
|
||||||
expect(role?.description).toBe(customRole.description);
|
expect(role?.description).toBe(customRole.description);
|
||||||
});
|
});
|
||||||
|
@ -238,27 +246,27 @@ describe('AccessRole Model Tests', () => {
|
||||||
// Create sample roles with ascending permission levels
|
// Create sample roles with ascending permission levels
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
methods.createRole({
|
methods.createRole({
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
name: 'Agent Viewer',
|
name: 'Agent Viewer',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.VIEWER, // 1
|
permBits: RoleBits.VIEWER, // 1
|
||||||
}),
|
}),
|
||||||
methods.createRole({
|
methods.createRole({
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
name: 'Agent Editor',
|
name: 'Agent Editor',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.EDITOR, // 3
|
permBits: RoleBits.EDITOR, // 3
|
||||||
}),
|
}),
|
||||||
methods.createRole({
|
methods.createRole({
|
||||||
accessRoleId: 'agent_manager',
|
accessRoleId: 'agent_manager',
|
||||||
name: 'Agent Manager',
|
name: 'Agent Manager',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.MANAGER, // 7
|
permBits: RoleBits.MANAGER, // 7
|
||||||
}),
|
}),
|
||||||
methods.createRole({
|
methods.createRole({
|
||||||
accessRoleId: 'agent_owner',
|
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||||
name: 'Agent Owner',
|
name: 'Agent Owner',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.OWNER, // 15
|
permBits: RoleBits.OWNER, // 15
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
@ -267,7 +275,7 @@ describe('AccessRole Model Tests', () => {
|
||||||
test('should find exact matching role', async () => {
|
test('should find exact matching role', async () => {
|
||||||
const role = await methods.getRoleForPermissions('agent', RoleBits.EDITOR);
|
const role = await methods.getRoleForPermissions('agent', RoleBits.EDITOR);
|
||||||
expect(role).toBeDefined();
|
expect(role).toBeDefined();
|
||||||
expect(role?.accessRoleId).toBe('agent_editor');
|
expect(role?.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR);
|
||||||
expect(role?.permBits).toBe(RoleBits.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
|
// Should return VIEWER (1) as closest matching role without exceeding the permission bits
|
||||||
const role = await methods.getRoleForPermissions('agent', customPerm);
|
const role = await methods.getRoleForPermissions('agent', customPerm);
|
||||||
expect(role).toBeDefined();
|
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 () => {
|
test('should return null when no compatible role is found', async () => {
|
||||||
|
@ -301,7 +309,7 @@ describe('AccessRole Model Tests', () => {
|
||||||
// Query for agent roles
|
// Query for agent roles
|
||||||
const agentRole = await methods.getRoleForPermissions('agent', RoleBits.VIEWER);
|
const agentRole = await methods.getRoleForPermissions('agent', RoleBits.VIEWER);
|
||||||
expect(agentRole).toBeDefined();
|
expect(agentRole).toBeDefined();
|
||||||
expect(agentRole?.accessRoleId).toBe('agent_viewer');
|
expect(agentRole?.accessRoleId).toBe(AccessRoleIds.AGENT_VIEWER);
|
||||||
|
|
||||||
// Query for project roles
|
// Query for project roles
|
||||||
const projectRole = await methods.getRoleForPermissions('project', RoleBits.VIEWER);
|
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 type { Model, Types, DeleteResult } from 'mongoose';
|
||||||
import { RoleBits, PermissionBits } from '~/common';
|
|
||||||
import type { IAccessRole } from '~/types';
|
import type { IAccessRole } from '~/types';
|
||||||
|
import { RoleBits } from '~/common';
|
||||||
|
|
||||||
export function createAccessRoleMethods(mongoose: typeof import('mongoose')) {
|
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 AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||||
const defaultRoles = [
|
const defaultRoles = [
|
||||||
{
|
{
|
||||||
accessRoleId: 'agent_viewer',
|
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||||
name: 'com_ui_role_viewer',
|
name: 'com_ui_role_viewer',
|
||||||
description: 'com_ui_role_viewer_desc',
|
description: 'com_ui_role_viewer_desc',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.VIEWER,
|
permBits: RoleBits.VIEWER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessRoleId: 'agent_editor',
|
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||||
name: 'com_ui_role_editor',
|
name: 'com_ui_role_editor',
|
||||||
description: 'com_ui_role_editor_desc',
|
description: 'com_ui_role_editor_desc',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.EDITOR,
|
permBits: RoleBits.EDITOR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessRoleId: 'agent_owner',
|
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||||
name: 'com_ui_role_owner',
|
name: 'com_ui_role_owner',
|
||||||
description: 'com_ui_role_owner_desc',
|
description: 'com_ui_role_owner_desc',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
permBits: RoleBits.OWNER,
|
permBits: RoleBits.OWNER,
|
||||||
},
|
},
|
||||||
// Prompt access roles
|
|
||||||
{
|
{
|
||||||
accessRoleId: 'prompt_viewer',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||||
name: 'com_ui_role_viewer',
|
name: 'com_ui_role_viewer',
|
||||||
description: 'com_ui_role_viewer_desc',
|
description: 'com_ui_role_viewer_desc',
|
||||||
resourceType: 'prompt',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits: RoleBits.VIEWER,
|
permBits: RoleBits.VIEWER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessRoleId: 'prompt_editor',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||||
name: 'com_ui_role_editor',
|
name: 'com_ui_role_editor',
|
||||||
description: 'com_ui_role_editor_desc',
|
description: 'com_ui_role_editor_desc',
|
||||||
resourceType: 'prompt',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
permBits: RoleBits.EDITOR,
|
permBits: RoleBits.EDITOR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessRoleId: 'prompt_owner',
|
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||||
name: 'com_ui_role_owner',
|
name: 'com_ui_role_owner',
|
||||||
description: 'com_ui_role_owner_desc',
|
description: 'com_ui_role_owner_desc',
|
||||||
resourceType: 'prompt',
|
resourceType: ResourceType.PROMPTGROUP,
|
||||||
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',
|
|
||||||
permBits: RoleBits.OWNER,
|
permBits: RoleBits.OWNER,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { ResourceType, PermissionBits } from 'librechat-data-provider';
|
||||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
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 type * as t from '~/types';
|
||||||
|
import { createAclEntryMethods } from './aclEntry';
|
||||||
|
import aclEntrySchema from '~/schema/aclEntry';
|
||||||
|
|
||||||
let mongoServer: MongoMemoryServer;
|
let mongoServer: MongoMemoryServer;
|
||||||
let AclEntry: mongoose.Model<t.IAclEntry>;
|
let AclEntry: mongoose.Model<t.IAclEntry>;
|
||||||
|
@ -38,7 +38,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
const entry = await methods.grantPermission(
|
const entry = await methods.grantPermission(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -59,7 +59,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
const entry = await methods.grantPermission(
|
const entry = await methods.grantPermission(
|
||||||
'group',
|
'group',
|
||||||
groupId,
|
groupId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW | PermissionBits.EDIT,
|
PermissionBits.VIEW | PermissionBits.EDIT,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -76,7 +76,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
const entry = await methods.grantPermission(
|
const entry = await methods.grantPermission(
|
||||||
'public',
|
'public',
|
||||||
null,
|
null,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -93,7 +93,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -122,7 +122,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -130,7 +130,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'group',
|
'group',
|
||||||
groupId,
|
groupId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.EDIT,
|
PermissionBits.EDIT,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -138,7 +138,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'public',
|
'public',
|
||||||
null,
|
null,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -155,7 +155,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -163,7 +163,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'group',
|
'group',
|
||||||
groupId,
|
groupId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.EDIT,
|
PermissionBits.EDIT,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -173,7 +173,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'public',
|
'public',
|
||||||
null,
|
null,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
otherResourceId,
|
otherResourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -188,7 +188,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
|
|
||||||
const entries = await methods.findEntriesByPrincipalsAndResource(
|
const entries = await methods.findEntriesByPrincipalsAndResource(
|
||||||
principalsList,
|
principalsList,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
);
|
);
|
||||||
expect(entries).toHaveLength(2);
|
expect(entries).toHaveLength(2);
|
||||||
|
@ -200,7 +200,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
/** User has VIEW permission */
|
/** User has VIEW permission */
|
||||||
const hasViewPermission = await methods.hasPermission(
|
const hasViewPermission = await methods.hasPermission(
|
||||||
principalsList,
|
principalsList,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
);
|
);
|
||||||
|
@ -209,7 +209,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
/** User doesn't have EDIT permission */
|
/** User doesn't have EDIT permission */
|
||||||
const hasEditPermission = await methods.hasPermission(
|
const hasEditPermission = await methods.hasPermission(
|
||||||
principalsList,
|
principalsList,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.EDIT,
|
PermissionBits.EDIT,
|
||||||
);
|
);
|
||||||
|
@ -222,7 +222,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
/** Group has EDIT permission */
|
/** Group has EDIT permission */
|
||||||
const hasEditPermission = await methods.hasPermission(
|
const hasEditPermission = await methods.hasPermission(
|
||||||
principalsList,
|
principalsList,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.EDIT,
|
PermissionBits.EDIT,
|
||||||
);
|
);
|
||||||
|
@ -238,7 +238,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
/** User has VIEW and group has EDIT, together they should have both */
|
/** User has VIEW and group has EDIT, together they should have both */
|
||||||
const hasViewPermission = await methods.hasPermission(
|
const hasViewPermission = await methods.hasPermission(
|
||||||
principalsList,
|
principalsList,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
);
|
);
|
||||||
|
@ -246,7 +246,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
|
|
||||||
const hasEditPermission = await methods.hasPermission(
|
const hasEditPermission = await methods.hasPermission(
|
||||||
principalsList,
|
principalsList,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.EDIT,
|
PermissionBits.EDIT,
|
||||||
);
|
);
|
||||||
|
@ -255,7 +255,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
/** Neither has DELETE permission */
|
/** Neither has DELETE permission */
|
||||||
const hasDeletePermission = await methods.hasPermission(
|
const hasDeletePermission = await methods.hasPermission(
|
||||||
principalsList,
|
principalsList,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.DELETE,
|
PermissionBits.DELETE,
|
||||||
);
|
);
|
||||||
|
@ -281,7 +281,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -292,7 +292,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
expect(entriesBefore).toHaveLength(1);
|
expect(entriesBefore).toHaveLength(1);
|
||||||
|
|
||||||
/** Revoke it */
|
/** 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);
|
expect(result.deletedCount).toBe(1);
|
||||||
|
|
||||||
/** Verify it's gone */
|
/** Verify it's gone */
|
||||||
|
@ -305,7 +305,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -315,7 +315,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
const updated = await methods.modifyPermissionBits(
|
const updated = await methods.modifyPermissionBits(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.EDIT,
|
PermissionBits.EDIT,
|
||||||
null,
|
null,
|
||||||
|
@ -330,7 +330,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW | PermissionBits.EDIT,
|
PermissionBits.VIEW | PermissionBits.EDIT,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -340,7 +340,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
const updated = await methods.modifyPermissionBits(
|
const updated = await methods.modifyPermissionBits(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
null,
|
null,
|
||||||
PermissionBits.EDIT,
|
PermissionBits.EDIT,
|
||||||
|
@ -355,7 +355,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId,
|
resourceId,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -387,7 +387,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId1,
|
resourceId1,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -397,7 +397,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'user',
|
'user',
|
||||||
userId,
|
userId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId2,
|
resourceId2,
|
||||||
PermissionBits.VIEW | PermissionBits.EDIT,
|
PermissionBits.VIEW | PermissionBits.EDIT,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -407,7 +407,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
await methods.grantPermission(
|
await methods.grantPermission(
|
||||||
'group',
|
'group',
|
||||||
groupId,
|
groupId,
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
resourceId3,
|
resourceId3,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
grantedById,
|
grantedById,
|
||||||
|
@ -416,7 +416,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
/** Find resources with VIEW permission for user */
|
/** Find resources with VIEW permission for user */
|
||||||
const userViewableResources = await methods.findAccessibleResources(
|
const userViewableResources = await methods.findAccessibleResources(
|
||||||
[{ principalType: 'user', principalId: userId }],
|
[{ principalType: 'user', principalId: userId }],
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -431,7 +431,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
{ principalType: 'user', principalId: userId },
|
{ principalType: 'user', principalId: userId },
|
||||||
{ principalType: 'group', principalId: groupId },
|
{ principalType: 'group', principalId: groupId },
|
||||||
],
|
],
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
PermissionBits.VIEW,
|
PermissionBits.VIEW,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -440,7 +440,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
/** Find resources with EDIT permission for user */
|
/** Find resources with EDIT permission for user */
|
||||||
const editableResources = await methods.findAccessibleResources(
|
const editableResources = await methods.findAccessibleResources(
|
||||||
[{ principalType: 'user', principalId: userId }],
|
[{ principalType: 'user', principalId: userId }],
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
PermissionBits.EDIT,
|
PermissionBits.EDIT,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -467,7 +467,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
principalType: 'user',
|
principalType: 'user',
|
||||||
principalId: userId,
|
principalId: userId,
|
||||||
principalModel: 'User',
|
principalModel: 'User',
|
||||||
resourceType: 'agent',
|
resourceType: ResourceType.AGENT,
|
||||||
resourceId: childResourceId,
|
resourceId: childResourceId,
|
||||||
permBits: PermissionBits.VIEW,
|
permBits: PermissionBits.VIEW,
|
||||||
grantedBy: grantedById,
|
grantedBy: grantedById,
|
||||||
|
@ -477,7 +477,7 @@ describe('AclEntry Model Tests', () => {
|
||||||
/** Get effective permissions */
|
/** Get effective permissions */
|
||||||
const effective = await methods.getEffectivePermissions(
|
const effective = await methods.getEffectivePermissions(
|
||||||
[{ principalType: 'user', principalId: userId }],
|
[{ principalType: 'user', principalId: userId }],
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
childResourceId,
|
childResourceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ const accessRoleSchema = new Schema<IAccessRole>(
|
||||||
description: String,
|
description: String,
|
||||||
resourceType: {
|
resourceType: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: ['agent', 'project', 'file', 'prompt', 'promptGroup'],
|
enum: ['agent', 'project', 'file', 'promptGroup'],
|
||||||
required: true,
|
required: true,
|
||||||
default: 'agent',
|
default: 'agent',
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,8 +7,8 @@ export type AclEntry = {
|
||||||
principalId?: Types.ObjectId;
|
principalId?: Types.ObjectId;
|
||||||
/** The model name for the principal ('User' or 'Group') */
|
/** The model name for the principal ('User' or 'Group') */
|
||||||
principalModel?: 'User' | 'Group';
|
principalModel?: 'User' | 'Group';
|
||||||
/** The type of resource ('agent', 'project', 'file', 'prompt', 'promptGroup') */
|
/** The type of resource ('agent', 'project', 'file', 'promptGroup') */
|
||||||
resourceType: 'agent' | 'project' | 'file' | 'prompt' | 'promptGroup';
|
resourceType: 'agent' | 'project' | 'file' | 'promptGroup';
|
||||||
/** The ID of the resource */
|
/** The ID of the resource */
|
||||||
resourceId: Types.ObjectId;
|
resourceId: Types.ObjectId;
|
||||||
/** Permission bits for this entry */
|
/** Permission bits for this entry */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue