🔧 refactor: Add and use PrincipalType Enum

- Replaced string literals for principal types ('user', 'group', 'public') with the new PrincipalType enum across various models, services, and tests for improved type safety and consistency.
- Updated permission handling in multiple files to utilize the PrincipalType enum, enhancing maintainability and reducing potential errors.
- Ensured all relevant tests reflect these changes to maintain coverage and functionality.
This commit is contained in:
Danny Avila 2025-08-02 16:02:56 -04:00
parent 0262c25989
commit 49d1cefe71
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
23 changed files with 253 additions and 219 deletions

View file

@ -14,7 +14,7 @@ const mongoose = require('mongoose');
const { v4: uuidv4 } = require('uuid');
const { agentSchema } = require('@librechat/data-schemas');
const { MongoMemoryServer } = require('mongodb-memory-server');
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
const { AccessRoleIds, ResourceType, PrincipalType } = require('librechat-data-provider');
const {
getAgent,
loadAgent,
@ -500,7 +500,7 @@ describe('models/Agent', () => {
// Grant permissions (simulating sharing)
await permissionService.grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: authorId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,

View file

@ -2,7 +2,7 @@ const mongoose = require('mongoose');
const { v4: uuidv4 } = require('uuid');
const { createModels } = require('@librechat/data-schemas');
const { MongoMemoryServer } = require('mongodb-memory-server');
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
const { AccessRoleIds, ResourceType, PrincipalType } = require('librechat-data-provider');
const { grantPermission } = require('~/server/services/PermissionService');
const { getFiles, createFile } = require('./File');
const { seedDefaultRoles } = require('~/models');
@ -115,7 +115,7 @@ describe('File Access Control', () => {
// Grant EDIT permission to user on the agent
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,
@ -232,7 +232,7 @@ describe('File Access Control', () => {
// Grant only VIEW permission to user on the agent
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,
@ -290,7 +290,7 @@ describe('File Access Control', () => {
// Grant EDIT permission to user on the agent
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,

View file

@ -6,6 +6,7 @@ const {
SystemRoles,
ResourceType,
AccessRoleIds,
PrincipalType,
PermissionBits,
} = require('librechat-data-provider');
@ -151,7 +152,7 @@ describe('Prompt ACL Permissions', () => {
// Manually grant permissions as would happen in the route
await permissionService.grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.owner._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: testGroup._id,
@ -163,7 +164,7 @@ describe('Prompt ACL Permissions', () => {
const aclEntry = await AclEntry.findOne({
resourceType: ResourceType.PROMPTGROUP,
resourceId: testGroup._id,
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.owner._id,
});
@ -195,7 +196,7 @@ describe('Prompt ACL Permissions', () => {
// Grant owner permissions
await permissionService.grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.owner._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: testPromptGroup._id,
@ -233,7 +234,7 @@ describe('Prompt ACL Permissions', () => {
it('user with viewer role should only have view access', async () => {
// Grant viewer permissions
await permissionService.grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.viewer._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: testPromptGroup._id,
@ -355,7 +356,7 @@ describe('Prompt ACL Permissions', () => {
// Grant edit permissions to the group
await permissionService.grantPermission({
principalType: 'group',
principalType: PrincipalType.GROUP,
principalId: testGroups.editors._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: testPromptGroup._id,
@ -423,7 +424,7 @@ describe('Prompt ACL Permissions', () => {
// Grant public view access to publicPromptGroup
await permissionService.grantPermission({
principalType: 'public',
principalType: PrincipalType.PUBLIC,
principalId: null,
resourceType: ResourceType.PROMPTGROUP,
resourceId: publicPromptGroup._id,
@ -433,7 +434,7 @@ describe('Prompt ACL Permissions', () => {
// Grant only owner access to privatePromptGroup
await permissionService.grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.owner._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: privatePromptGroup._id,
@ -504,7 +505,7 @@ describe('Prompt ACL Permissions', () => {
// Grant permission
await permissionService.grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.owner._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: testPromptGroup._id,

View file

@ -6,6 +6,7 @@ const {
Constants,
ResourceType,
AccessRoleIds,
PrincipalType,
PermissionBits,
} = require('librechat-data-provider');
@ -158,7 +159,7 @@ describe('PromptGroup Migration Script', () => {
const globalOwnerEntry = await AclEntry.findOne({
resourceType: ResourceType.PROMPTGROUP,
resourceId: globalPromptGroup._id,
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testOwner._id,
});
expect(globalOwnerEntry).toBeTruthy();
@ -167,7 +168,7 @@ describe('PromptGroup Migration Script', () => {
const globalPublicEntry = await AclEntry.findOne({
resourceType: ResourceType.PROMPTGROUP,
resourceId: globalPromptGroup._id,
principalType: 'public',
principalType: PrincipalType.PUBLIC,
});
expect(globalPublicEntry).toBeTruthy();
expect(globalPublicEntry.permBits).toBe(viewerRole.permBits);
@ -176,7 +177,7 @@ describe('PromptGroup Migration Script', () => {
const privateOwnerEntry = await AclEntry.findOne({
resourceType: ResourceType.PROMPTGROUP,
resourceId: privatePromptGroup._id,
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testOwner._id,
});
expect(privateOwnerEntry).toBeTruthy();
@ -185,7 +186,7 @@ describe('PromptGroup Migration Script', () => {
const privatePublicEntry = await AclEntry.findOne({
resourceType: ResourceType.PROMPTGROUP,
resourceId: privatePromptGroup._id,
principalType: 'public',
principalType: PrincipalType.PUBLIC,
});
expect(privatePublicEntry).toBeNull();
});
@ -208,7 +209,7 @@ describe('PromptGroup Migration Script', () => {
// Grant permission to one promptGroup manually (simulating it already has ACL)
await AclEntry.create({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testOwner._id,
principalModel: 'User',
resourceType: ResourceType.PROMPTGROUP,

View file

@ -4,7 +4,7 @@
const mongoose = require('mongoose');
const { logger } = require('@librechat/data-schemas');
const { ResourceType } = require('librechat-data-provider');
const { ResourceType, PrincipalType } = require('librechat-data-provider');
const {
bulkUpdateResourcePermissions,
ensureGroupPrincipalExists,
@ -235,14 +235,14 @@ const getResourcePermissions = async (req, res) => {
// Process aggregation results
for (const result of results) {
if (result.principalType === 'public') {
if (result.principalType === PrincipalType.PUBLIC) {
publicPermission = {
public: true,
publicAccessRoleId: result.accessRoleId,
};
} else if (result.principalType === 'user' && result.userInfo) {
} else if (result.principalType === PrincipalType.USER && result.userInfo) {
principals.push({
type: 'user',
type: PrincipalType.USER,
id: result.userInfo._id.toString(),
name: result.userInfo.name || result.userInfo.username,
email: result.userInfo.email,
@ -251,9 +251,9 @@ const getResourcePermissions = async (req, res) => {
idOnTheSource: result.userInfo.idOnTheSource || result.userInfo._id.toString(),
accessRoleId: result.accessRoleId,
});
} else if (result.principalType === 'group' && result.groupInfo) {
} else if (result.principalType === PrincipalType.GROUP && result.groupInfo) {
principals.push({
type: 'group',
type: PrincipalType.GROUP,
id: result.groupInfo._id.toString(),
name: result.groupInfo.name,
email: result.groupInfo.email,

View file

@ -9,9 +9,10 @@ const {
FileSources,
ResourceType,
AccessRoleIds,
PrincipalType,
EToolResources,
actionDelimiter,
PermissionBits,
actionDelimiter,
removeNullishValues,
} = require('librechat-data-provider');
const {
@ -80,7 +81,7 @@ const createAgentHandler = async (req, res) => {
// Automatically grant owner permissions to the creator
try {
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,
@ -346,7 +347,7 @@ const duplicateAgentHandler = async (req, res) => {
// Automatically grant owner permissions to the duplicator
try {
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId: newAgent._id,

View file

@ -1,5 +1,5 @@
const mongoose = require('mongoose');
const { ResourceType } = require('librechat-data-provider');
const { ResourceType, PrincipalType } = require('librechat-data-provider');
const { MongoMemoryServer } = require('mongodb-memory-server');
const { canAccessAgentResource } = require('./canAccessAgentResource');
const { User, Role, AclEntry } = require('~/db/models');
@ -97,7 +97,7 @@ describe('canAccessAgentResource middleware', () => {
// Create ACL entry for the author (owner permissions)
await AclEntry.create({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUser._id,
principalModel: 'User',
resourceType: ResourceType.AGENT,
@ -134,7 +134,7 @@ describe('canAccessAgentResource middleware', () => {
// Create ACL entry for the other user (owner)
await AclEntry.create({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: otherUser._id,
principalModel: 'User',
resourceType: ResourceType.AGENT,
@ -175,7 +175,7 @@ describe('canAccessAgentResource middleware', () => {
// Create ACL entry granting view permission to test user
await AclEntry.create({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUser._id,
principalModel: 'User',
resourceType: ResourceType.AGENT,
@ -212,7 +212,7 @@ describe('canAccessAgentResource middleware', () => {
// Create ACL entry granting only view permission
await AclEntry.create({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUser._id,
principalModel: 'User',
resourceType: ResourceType.AGENT,
@ -259,7 +259,7 @@ describe('canAccessAgentResource middleware', () => {
// Create ACL entry for the author
await AclEntry.create({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUser._id,
principalModel: 'User',
resourceType: ResourceType.AGENT,
@ -295,7 +295,7 @@ describe('canAccessAgentResource middleware', () => {
// Create ACL entry with all permissions for the owner
await AclEntry.create({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUser._id,
principalModel: 'User',
resourceType: ResourceType.AGENT,
@ -355,7 +355,7 @@ describe('canAccessAgentResource middleware', () => {
// Create ACL entry for the author
await AclEntry.create({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUser._id,
principalModel: 'User',
resourceType: ResourceType.AGENT,

View file

@ -2,9 +2,9 @@ const express = require('express');
const request = require('supertest');
const mongoose = require('mongoose');
const { v4: uuidv4 } = require('uuid');
const { MongoMemoryServer } = require('mongodb-memory-server');
const { createMethods } = require('@librechat/data-schemas');
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
const { MongoMemoryServer } = require('mongodb-memory-server');
const { AccessRoleIds, ResourceType, PrincipalType } = require('librechat-data-provider');
const { createAgent } = require('~/models/Agent');
const { createFile } = require('~/models/File');
@ -185,7 +185,7 @@ describe('File Routes - Agent Files Endpoint', () => {
// Grant EDIT permission to user on the agent using PermissionService
const { grantPermission } = require('~/server/services/PermissionService');
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: otherUserId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,
@ -240,7 +240,7 @@ describe('File Routes - Agent Files Endpoint', () => {
// Grant only VIEW permission to user on the agent
const { grantPermission } = require('~/server/services/PermissionService');
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: otherUserId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,

View file

@ -4,7 +4,7 @@ const mongoose = require('mongoose');
const { v4: uuidv4 } = require('uuid');
const { createMethods } = require('@librechat/data-schemas');
const { MongoMemoryServer } = require('mongodb-memory-server');
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
const { AccessRoleIds, ResourceType, PrincipalType } = require('librechat-data-provider');
const { createAgent } = require('~/models/Agent');
const { createFile } = require('~/models/File');
@ -227,7 +227,7 @@ describe('File Routes - Delete with Agent Access', () => {
// Grant EDIT permission to user on the agent
const { grantPermission } = require('~/server/services/PermissionService');
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: otherUserId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,
@ -281,7 +281,7 @@ describe('File Routes - Delete with Agent Access', () => {
// Grant EDIT permission to user on the agent
const { grantPermission } = require('~/server/services/PermissionService');
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: otherUserId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,
@ -347,7 +347,7 @@ describe('File Routes - Delete with Agent Access', () => {
// Grant EDIT permission to user on the agent
const { grantPermission } = require('~/server/services/PermissionService');
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: otherUserId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,
@ -390,7 +390,7 @@ describe('File Routes - Delete with Agent Access', () => {
// Grant only VIEW permission to user on the agent
const { grantPermission } = require('~/server/services/PermissionService');
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: otherUserId,
resourceType: ResourceType.AGENT,
resourceId: agent._id,

View file

@ -6,8 +6,9 @@ const {
SystemRoles,
ResourceType,
AccessRoleIds,
PermissionTypes,
PrincipalType,
PermissionBits,
PermissionTypes,
} = require('librechat-data-provider');
const {
makePromptProduction,
@ -189,7 +190,7 @@ const createNewPromptGroup = async (req, res) => {
if (result.prompt && result.prompt._id && result.prompt.groupId) {
try {
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: req.user.id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: result.prompt.groupId,

View file

@ -7,6 +7,7 @@ const {
SystemRoles,
ResourceType,
AccessRoleIds,
PrincipalType,
PermissionBits,
} = require('librechat-data-provider');
@ -223,7 +224,7 @@ describe('Prompt Routes - ACL Permissions', () => {
const aclEntry = await AclEntry.findOne({
resourceType: ResourceType.PROMPTGROUP,
resourceId: response.body.prompt.groupId,
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.owner._id,
});
@ -253,7 +254,7 @@ describe('Prompt Routes - ACL Permissions', () => {
const aclEntry = await AclEntry.findOne({
resourceType: ResourceType.PROMPTGROUP,
resourceId: response.body.group._id,
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.owner._id,
});
@ -294,7 +295,7 @@ describe('Prompt Routes - ACL Permissions', () => {
it('should retrieve prompt when user has view permissions', async () => {
// Grant view permissions on the promptGroup
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.owner._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: testGroup._id,
@ -379,7 +380,7 @@ describe('Prompt Routes - ACL Permissions', () => {
// Grant owner permissions on the promptGroup
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.owner._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: testGroup._id,
@ -426,7 +427,7 @@ describe('Prompt Routes - ACL Permissions', () => {
// Grant only viewer permissions to viewer user on the promptGroup
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.viewer._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: testGroup._id,
@ -493,7 +494,7 @@ describe('Prompt Routes - ACL Permissions', () => {
it('should make prompt production when user has edit permissions', async () => {
// Grant edit permissions on the promptGroup
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.owner._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: testGroup._id,
@ -531,7 +532,7 @@ describe('Prompt Routes - ACL Permissions', () => {
it('should deny making production when user lacks edit permissions', async () => {
// Grant only view permissions to viewer on the promptGroup
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: testUsers.viewer._id,
resourceType: ResourceType.PROMPTGROUP,
resourceId: testGroup._id,
@ -588,7 +589,7 @@ describe('Prompt Routes - ACL Permissions', () => {
// Grant public viewer access on the promptGroup
await grantPermission({
principalType: 'public',
principalType: PrincipalType.PUBLIC,
principalId: null,
resourceType: ResourceType.PROMPTGROUP,
resourceId: publicGroup._id,

View file

@ -17,6 +17,11 @@ jest.mock('~/config', () => ({
jest.mock('librechat-data-provider', () => ({
isUUID: { parse: jest.fn() },
megabyte: 1024 * 1024,
PrincipalType: {
USER: 'user',
GROUP: 'group',
PUBLIC: 'public',
},
FileContext: { message_attachment: 'message_attachment' },
FileSources: { local: 'local' },
EModelEndpoint: { assistants: 'assistants' },

View file

@ -1,6 +1,6 @@
const mongoose = require('mongoose');
const { isEnabled } = require('@librechat/api');
const { ResourceType } = require('librechat-data-provider');
const { ResourceType, PrincipalType } = require('librechat-data-provider');
const { getTransactionSupport, logger } = require('@librechat/data-schemas');
const {
entraIdPrincipalFeatureEnabled,
@ -65,11 +65,11 @@ const grantPermission = async ({
session,
}) => {
try {
if (!['user', 'group', 'public'].includes(principalType)) {
if (!Object.values(PrincipalType).includes(principalType)) {
throw new Error(`Invalid principal type: ${principalType}`);
}
if (principalType !== 'public' && !principalId) {
if (principalType !== PrincipalType.PUBLIC && !principalId) {
throw new Error('Principal ID is required for user and group principals');
}
@ -221,7 +221,7 @@ const findPubliclyAccessibleResources = async ({ resourceType, requiredPermissio
// Find all public ACL entries where the public principal has at least the required permission bits
const entries = await AclEntry.find({
principalType: 'public',
principalType: PrincipalType.PUBLIC,
resourceType,
permBits: { $bitsAllSet: requiredPermissions },
}).distinct('resourceId');
@ -505,7 +505,7 @@ const hasPublicPermission = async ({ resourceType, resourceId, requiredPermissio
validateResourceType(resourceType);
// Use public principal to check permissions
const publicPrincipal = [{ principalType: 'public' }];
const publicPrincipal = [{ principalType: PrincipalType.PUBLIC }];
const entries = await findEntriesByPrincipalsAndResource(
publicPrincipal,

View file

@ -1,7 +1,7 @@
const mongoose = require('mongoose');
const { RoleBits, createModels } = require('@librechat/data-schemas');
const { MongoMemoryServer } = require('mongodb-memory-server');
const { AccessRoleIds, ResourceType } = require('librechat-data-provider');
const { AccessRoleIds, ResourceType, PrincipalType } = require('librechat-data-provider');
const {
bulkUpdateResourcePermissions,
getEffectivePermissions,
@ -78,7 +78,7 @@ describe('PermissionService', () => {
describe('grantPermission', () => {
test('should grant permission to a user with a role', async () => {
const entry = await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId,
@ -103,7 +103,7 @@ describe('PermissionService', () => {
test('should grant permission to a group with a role', async () => {
const entry = await grantPermission({
principalType: 'group',
principalType: PrincipalType.GROUP,
principalId: groupId,
resourceType: ResourceType.AGENT,
resourceId,
@ -112,7 +112,7 @@ describe('PermissionService', () => {
});
expect(entry).toBeDefined();
expect(entry.principalType).toBe('group');
expect(entry.principalType).toBe(PrincipalType.GROUP);
expect(entry.principalId.toString()).toBe(groupId.toString());
expect(entry.principalModel).toBe('Group');
@ -124,7 +124,7 @@ describe('PermissionService', () => {
test('should grant public permission with a role', async () => {
const entry = await grantPermission({
principalType: 'public',
principalType: PrincipalType.PUBLIC,
principalId: null,
resourceType: ResourceType.AGENT,
resourceId,
@ -133,7 +133,7 @@ describe('PermissionService', () => {
});
expect(entry).toBeDefined();
expect(entry.principalType).toBe('public');
expect(entry.principalType).toBe(PrincipalType.PUBLIC);
expect(entry.principalId).toBeUndefined();
expect(entry.principalModel).toBeUndefined();
@ -159,7 +159,7 @@ describe('PermissionService', () => {
test('should throw error for missing principalId with user type', async () => {
await expect(
grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: null,
resourceType: ResourceType.AGENT,
resourceId,
@ -172,7 +172,7 @@ describe('PermissionService', () => {
test('should throw error for non-existent role', async () => {
await expect(
grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId,
@ -185,7 +185,7 @@ describe('PermissionService', () => {
test('should throw error for role-resource type mismatch', async () => {
await expect(
grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId,
@ -198,7 +198,7 @@ describe('PermissionService', () => {
test('should update existing permission when granting to same principal and resource', async () => {
// First grant with viewer role
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId,
@ -208,7 +208,7 @@ describe('PermissionService', () => {
// Then update to editor role
const updated = await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId,
@ -222,7 +222,7 @@ describe('PermissionService', () => {
// Verify there's only one entry
const entries = await AclEntry.find({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId,
@ -240,7 +240,7 @@ describe('PermissionService', () => {
// Setup test data
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId,
@ -250,7 +250,7 @@ describe('PermissionService', () => {
otherResourceId = new mongoose.Types.ObjectId();
await grantPermission({
principalType: 'group',
principalType: PrincipalType.GROUP,
principalId: groupId,
resourceType: ResourceType.AGENT,
resourceId: otherResourceId,
@ -261,7 +261,9 @@ describe('PermissionService', () => {
test('should check permission for user principal', async () => {
// Mock getUserPrincipals to return just the user principal
getUserPrincipals.mockResolvedValue([{ principalType: 'user', principalId: userId }]);
getUserPrincipals.mockResolvedValue([
{ principalType: PrincipalType.USER, principalId: userId },
]);
const hasViewPermission = await checkPermission({
userId,
@ -286,8 +288,8 @@ describe('PermissionService', () => {
test('should check permission for user and group principals', async () => {
// Mock getUserPrincipals to return both user and group principals
getUserPrincipals.mockResolvedValue([
{ principalType: 'user', principalId: userId },
{ principalType: 'group', principalId: groupId },
{ principalType: PrincipalType.USER, principalId: userId },
{ principalType: PrincipalType.GROUP, principalId: groupId },
]);
// Check original resource (user has access)
@ -317,7 +319,7 @@ describe('PermissionService', () => {
// Grant public access to a resource
await grantPermission({
principalType: 'public',
principalType: PrincipalType.PUBLIC,
principalId: null,
resourceType: ResourceType.AGENT,
resourceId: publicResourceId,
@ -327,9 +329,9 @@ describe('PermissionService', () => {
// Mock getUserPrincipals to return user, group, and public principals
getUserPrincipals.mockResolvedValue([
{ principalType: 'user', principalId: userId },
{ principalType: 'group', principalId: groupId },
{ principalType: 'public' },
{ principalType: PrincipalType.USER, principalId: userId },
{ principalType: PrincipalType.GROUP, principalId: groupId },
{ principalType: PrincipalType.PUBLIC },
]);
const hasPublicAccess = await checkPermission({
@ -343,7 +345,9 @@ describe('PermissionService', () => {
});
test('should return false for invalid permission bits', async () => {
getUserPrincipals.mockResolvedValue([{ principalType: 'user', principalId: userId }]);
getUserPrincipals.mockResolvedValue([
{ principalType: PrincipalType.USER, principalId: userId },
]);
await expect(
checkPermission({
@ -385,7 +389,7 @@ describe('PermissionService', () => {
// Setup test data with multiple permissions from different sources
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId,
@ -394,7 +398,7 @@ describe('PermissionService', () => {
});
await grantPermission({
principalType: 'group',
principalType: PrincipalType.GROUP,
principalId: groupId,
resourceType: ResourceType.AGENT,
resourceId,
@ -405,7 +409,7 @@ describe('PermissionService', () => {
// Create another resource with public permission
const publicResourceId = new mongoose.Types.ObjectId();
await grantPermission({
principalType: 'public',
principalType: PrincipalType.PUBLIC,
principalId: null,
resourceType: ResourceType.AGENT,
resourceId: publicResourceId,
@ -418,7 +422,7 @@ describe('PermissionService', () => {
const childResourceId = new mongoose.Types.ObjectId();
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.PROMPTGROUP,
resourceId: parentResourceId,
@ -427,7 +431,7 @@ describe('PermissionService', () => {
});
await AclEntry.create({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
principalModel: 'User',
resourceType: ResourceType.AGENT,
@ -443,8 +447,8 @@ describe('PermissionService', () => {
test('should get effective permissions from multiple sources', async () => {
// Mock getUserPrincipals to return both user and group principals
getUserPrincipals.mockResolvedValue([
{ principalType: 'user', principalId: userId },
{ principalType: 'group', principalId: groupId },
{ principalType: PrincipalType.USER, principalId: userId },
{ principalType: PrincipalType.GROUP, principalId: groupId },
]);
const effective = await getEffectivePermissions({
@ -464,7 +468,9 @@ describe('PermissionService', () => {
const childResourceId = inheritedEntry.resourceId;
// Mock getUserPrincipals to return user principal
getUserPrincipals.mockResolvedValue([{ principalType: 'user', principalId: userId }]);
getUserPrincipals.mockResolvedValue([
{ principalType: PrincipalType.USER, principalId: userId },
]);
const effective = await getEffectivePermissions({
userId,
@ -516,7 +522,7 @@ describe('PermissionService', () => {
// User can view resource 1
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId: resource1,
@ -526,7 +532,7 @@ describe('PermissionService', () => {
// User can edit resource 2
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId: resource2,
@ -536,7 +542,7 @@ describe('PermissionService', () => {
// Group can view resource 3
await grantPermission({
principalType: 'group',
principalType: PrincipalType.GROUP,
principalId: groupId,
resourceType: ResourceType.AGENT,
resourceId: resource3,
@ -547,7 +553,9 @@ describe('PermissionService', () => {
test('should find resources user can view', async () => {
// Mock getUserPrincipals to return user principal
getUserPrincipals.mockResolvedValue([{ principalType: 'user', principalId: userId }]);
getUserPrincipals.mockResolvedValue([
{ principalType: PrincipalType.USER, principalId: userId },
]);
const viewableResources = await findAccessibleResources({
userId,
@ -561,7 +569,9 @@ describe('PermissionService', () => {
test('should find resources user can edit', async () => {
// Mock getUserPrincipals to return user principal
getUserPrincipals.mockResolvedValue([{ principalType: 'user', principalId: userId }]);
getUserPrincipals.mockResolvedValue([
{ principalType: PrincipalType.USER, principalId: userId },
]);
const editableResources = await findAccessibleResources({
userId,
@ -576,8 +586,8 @@ describe('PermissionService', () => {
test('should find resources accessible via group membership', async () => {
// Mock getUserPrincipals to return user and group principals
getUserPrincipals.mockResolvedValue([
{ principalType: 'user', principalId: userId },
{ principalType: 'group', principalId: groupId },
{ principalType: PrincipalType.USER, principalId: userId },
{ principalType: PrincipalType.GROUP, principalId: groupId },
]);
const viewableResources = await findAccessibleResources({
@ -591,7 +601,9 @@ describe('PermissionService', () => {
});
test('should return empty array for invalid permissions', async () => {
getUserPrincipals.mockResolvedValue([{ principalType: 'user', principalId: userId }]);
getUserPrincipals.mockResolvedValue([
{ principalType: PrincipalType.USER, principalId: userId },
]);
await expect(
findAccessibleResources({
@ -652,7 +664,7 @@ describe('PermissionService', () => {
await seedDefaultRoles();
// Setup existing permissions for testing
await grantPermission({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId,
@ -661,7 +673,7 @@ describe('PermissionService', () => {
});
await grantPermission({
principalType: 'group',
principalType: PrincipalType.GROUP,
principalId: groupId,
resourceType: ResourceType.AGENT,
resourceId,
@ -670,7 +682,7 @@ describe('PermissionService', () => {
});
await grantPermission({
principalType: 'public',
principalType: PrincipalType.PUBLIC,
principalId: null,
resourceType: ResourceType.AGENT,
resourceId,
@ -683,17 +695,17 @@ describe('PermissionService', () => {
const newResourceId = new mongoose.Types.ObjectId();
const updatedPrincipals = [
{
type: 'user',
type: PrincipalType.USER,
id: userId,
accessRoleId: AccessRoleIds.AGENT_VIEWER,
},
{
type: 'user',
type: PrincipalType.USER,
id: otherUserId,
accessRoleId: AccessRoleIds.AGENT_EDITOR,
},
{
type: 'group',
type: PrincipalType.GROUP,
id: groupId,
accessRoleId: AccessRoleIds.AGENT_OWNER,
},
@ -722,17 +734,17 @@ describe('PermissionService', () => {
test('should update existing permissions in bulk', async () => {
const updatedPrincipals = [
{
type: 'user',
type: PrincipalType.USER,
id: userId,
accessRoleId: AccessRoleIds.AGENT_EDITOR, // Upgrade from viewer to editor
},
{
type: 'group',
type: PrincipalType.GROUP,
id: groupId,
accessRoleId: AccessRoleIds.AGENT_OWNER, // Upgrade from editor to owner
},
{
type: 'public',
type: PrincipalType.PUBLIC,
accessRoleId: AccessRoleIds.AGENT_VIEWER, // Keep same role
},
];
@ -752,7 +764,7 @@ describe('PermissionService', () => {
// Verify updates
const userEntry = await AclEntry.findOne({
principalType: 'user',
principalType: PrincipalType.USER,
principalId: userId,
resourceType: ResourceType.AGENT,
resourceId,
@ -760,7 +772,7 @@ describe('PermissionService', () => {
expect(userEntry.roleId.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR);
const groupEntry = await AclEntry.findOne({
principalType: 'group',
principalType: PrincipalType.GROUP,
principalId: groupId,
resourceType: ResourceType.AGENT,
resourceId,
@ -771,11 +783,11 @@ describe('PermissionService', () => {
test('should revoke specified permissions', async () => {
const revokedPrincipals = [
{
type: 'group',
type: PrincipalType.GROUP,
id: groupId,
},
{
type: 'public',
type: PrincipalType.PUBLIC,
},
];
@ -797,19 +809,19 @@ describe('PermissionService', () => {
resourceId,
});
expect(remainingEntries).toHaveLength(1);
expect(remainingEntries[0].principalType).toBe('user');
expect(remainingEntries[0].principalType).toBe(PrincipalType.USER);
expect(remainingEntries[0].principalId.toString()).toBe(userId.toString());
});
test('should handle mixed operations (grant, update, revoke)', async () => {
const updatedPrincipals = [
{
type: 'user',
type: PrincipalType.USER,
id: userId,
accessRoleId: AccessRoleIds.AGENT_OWNER, // Update existing
},
{
type: 'user',
type: PrincipalType.USER,
id: otherUserId,
accessRoleId: AccessRoleIds.AGENT_VIEWER, // New permission
},
@ -817,11 +829,11 @@ describe('PermissionService', () => {
const revokedPrincipals = [
{
type: 'group',
type: PrincipalType.GROUP,
id: groupId,
},
{
type: 'public',
type: PrincipalType.PUBLIC,
},
];
@ -858,17 +870,17 @@ describe('PermissionService', () => {
test('should handle errors for invalid roles gracefully', async () => {
const updatedPrincipals = [
{
type: 'user',
type: PrincipalType.USER,
id: userId,
accessRoleId: AccessRoleIds.AGENT_VIEWER, // Valid
},
{
type: 'user',
type: PrincipalType.USER,
id: otherUserId,
accessRoleId: 'non_existent_role', // Invalid
},
{
type: 'group',
type: PrincipalType.GROUP,
id: groupId,
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER, // Wrong resource type
},
@ -938,11 +950,11 @@ describe('PermissionService', () => {
test('should handle public permissions correctly', async () => {
const updatedPrincipals = [
{
type: 'public',
type: PrincipalType.PUBLIC,
accessRoleId: AccessRoleIds.AGENT_EDITOR, // Update public permission
},
{
type: 'user',
type: PrincipalType.USER,
id: otherUserId,
accessRoleId: AccessRoleIds.AGENT_VIEWER, // New user permission
},
@ -950,11 +962,11 @@ describe('PermissionService', () => {
const revokedPrincipals = [
{
type: 'user',
type: PrincipalType.USER,
id: userId,
},
{
type: 'group',
type: PrincipalType.GROUP,
id: groupId,
},
];
@ -974,7 +986,7 @@ describe('PermissionService', () => {
// Verify public permission was updated
const publicEntry = await AclEntry.findOne({
principalType: 'public',
principalType: PrincipalType.PUBLIC,
resourceType: ResourceType.AGENT,
resourceId,
}).populate('roleId', 'accessRoleId');
@ -988,12 +1000,12 @@ describe('PermissionService', () => {
const promptGroupResourceId = new mongoose.Types.ObjectId();
const updatedPrincipals = [
{
type: 'user',
type: PrincipalType.USER,
id: userId,
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
},
{
type: 'group',
type: PrincipalType.GROUP,
id: groupId,
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
},