mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🔧 refactor: Organize Sharing/Agent Components and Improve Type Safety
refactor: organize Sharing/Agent components, improve type safety for resource types and access role ids, rename enums to PascalCase refactor: organize Sharing/Agent components, improve type safety for resource types and access role ids chore: move sharing related components to dedicated "Sharing" directory chore: remove PublicSharingToggle component and update index exports chore: move non-sidepanel agent components to `~/components/Agents` chore: move AgentCategoryDisplay component with tests chore: remove commented out code refactor: change PERMISSION_BITS from const to enum for better type safety refactor: reorganize imports in GenericGrantAccessDialog and update index exports for hooks refactor: update type definitions to use ACCESS_ROLE_IDS for improved type safety refactor: remove unused canAccessPromptResource middleware and related code refactor: remove unused prompt access roles from createAccessRoleMethods refactor: update resourceType in AclEntry type definition to remove unused 'prompt' value refactor: introduce ResourceType enum and update resourceType usage across data provider files for improved type safety refactor: update resourceType usage to ResourceType enum across sharing and permissions components for improved type safety refactor: standardize resourceType usage to ResourceType enum across agent and prompt models, permissions controller, and middleware for enhanced type safety refactor: update resourceType references from PROMPT_GROUP to PROMPTGROUP for consistency across models, middleware, and components refactor: standardize access role IDs and resource type usage across agent, file, and prompt models for improved type safety and consistency chore: add typedefs for TUpdateResourcePermissionsRequest and TUpdateResourcePermissionsResponse to enhance type definitions chore: move SearchPicker to PeoplePicker dir refactor: implement debouncing for query changes in SearchPicker for improved performance chore: fix typing, import order for agent admin settings fix: agent admin settings, prevent agent form submission refactor: rename `ACCESS_ROLE_IDS` to `AccessRoleIds` refactor: replace PermissionBits with PERMISSION_BITS refactor: replace PERMISSION_BITS with PermissionBits
This commit is contained in:
parent
ae732b2ebc
commit
81b32e400a
96 changed files with 781 additions and 798 deletions
|
|
@ -25,26 +25,35 @@ export type TPrincipalSource = 'local' | 'entra';
|
|||
*/
|
||||
export type TAccessLevel = 'none' | 'viewer' | 'editor' | 'owner';
|
||||
|
||||
/**
|
||||
* Resource types for permission system
|
||||
*/
|
||||
export enum ResourceType {
|
||||
AGENT = 'agent',
|
||||
PROMPTGROUP = 'promptGroup',
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission bit constants for bitwise operations
|
||||
*/
|
||||
export const PERMISSION_BITS = {
|
||||
VIEW: 1, // 001 - Can view and use agent
|
||||
EDIT: 2, // 010 - Can modify agent settings
|
||||
DELETE: 4, // 100 - Can delete agent
|
||||
SHARE: 8, // 1000 - Can share agent with others (future)
|
||||
} as const;
|
||||
export enum PermissionBits {
|
||||
/** 001 - Can view and use agent */
|
||||
VIEW = 1,
|
||||
/** 010 - Can modify agent settings */
|
||||
EDIT = 2,
|
||||
/** 100 - Can delete agent */
|
||||
DELETE = 4,
|
||||
/** 1000 - Can share agent with others (future) */
|
||||
SHARE = 8,
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard access role IDs
|
||||
*/
|
||||
export enum ACCESS_ROLE_IDS {
|
||||
export enum AccessRoleIds {
|
||||
AGENT_VIEWER = 'agent_viewer',
|
||||
AGENT_EDITOR = 'agent_editor',
|
||||
AGENT_OWNER = 'agent_owner', // Future use
|
||||
PROMPT_VIEWER = 'prompt_viewer',
|
||||
PROMPT_EDITOR = 'prompt_editor',
|
||||
PROMPT_OWNER = 'prompt_owner',
|
||||
AGENT_OWNER = 'agent_owner',
|
||||
PROMPTGROUP_VIEWER = 'promptGroup_viewer',
|
||||
PROMPTGROUP_EDITOR = 'promptGroup_editor',
|
||||
PROMPTGROUP_OWNER = 'promptGroup_owner',
|
||||
|
|
@ -64,7 +73,7 @@ export const principalSchema = z.object({
|
|||
avatar: z.string().optional(), // for user and group types
|
||||
description: z.string().optional(), // for group type
|
||||
idOnTheSource: z.string().optional(), // Entra ID for users/groups
|
||||
accessRoleId: z.nativeEnum(ACCESS_ROLE_IDS).optional(), // Access role ID for permissions
|
||||
accessRoleId: z.nativeEnum(AccessRoleIds).optional(), // Access role ID for permissions
|
||||
memberCount: z.number().optional(), // for group type
|
||||
});
|
||||
|
||||
|
|
@ -72,10 +81,10 @@ export const principalSchema = z.object({
|
|||
* Access role schema - defines named permission sets
|
||||
*/
|
||||
export const accessRoleSchema = z.object({
|
||||
accessRoleId: z.nativeEnum(ACCESS_ROLE_IDS),
|
||||
accessRoleId: z.nativeEnum(AccessRoleIds),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
resourceType: z.string().default('agent'),
|
||||
resourceType: z.nativeEnum(ResourceType).default(ResourceType.AGENT),
|
||||
permBits: z.number(),
|
||||
});
|
||||
|
||||
|
|
@ -98,7 +107,7 @@ export const permissionEntrySchema = z.object({
|
|||
* Resource permissions response schema
|
||||
*/
|
||||
export const resourcePermissionsResponseSchema = z.object({
|
||||
resourceType: z.string(),
|
||||
resourceType: z.nativeEnum(ResourceType),
|
||||
resourceId: z.string(),
|
||||
permissions: z.array(permissionEntrySchema),
|
||||
});
|
||||
|
|
@ -210,7 +219,7 @@ export type TPrincipalSearchResponse = {
|
|||
* Available roles response
|
||||
*/
|
||||
export type TAvailableRolesResponse = {
|
||||
resourceType: string;
|
||||
resourceType: ResourceType;
|
||||
roles: TAccessRole[];
|
||||
};
|
||||
|
||||
|
|
@ -219,11 +228,11 @@ export type TAvailableRolesResponse = {
|
|||
* This matches the enhanced aggregation-based endpoint response format
|
||||
*/
|
||||
export const getResourcePermissionsResponseSchema = z.object({
|
||||
resourceType: z.string(),
|
||||
resourceId: z.string(),
|
||||
resourceType: z.nativeEnum(ResourceType),
|
||||
resourceId: z.nativeEnum(AccessRoleIds),
|
||||
principals: z.array(principalSchema),
|
||||
public: z.boolean(),
|
||||
publicAccessRoleId: z.string().optional(),
|
||||
publicAccessRoleId: z.nativeEnum(AccessRoleIds).optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -265,9 +274,9 @@ export interface TPermissionCheck {
|
|||
* Convert permission bits to access level
|
||||
*/
|
||||
export function permBitsToAccessLevel(permBits: number): TAccessLevel {
|
||||
if ((permBits & PERMISSION_BITS.DELETE) > 0) return 'owner';
|
||||
if ((permBits & PERMISSION_BITS.EDIT) > 0) return 'editor';
|
||||
if ((permBits & PERMISSION_BITS.VIEW) > 0) return 'viewer';
|
||||
if ((permBits & PermissionBits.DELETE) > 0) return 'owner';
|
||||
if ((permBits & PermissionBits.EDIT) > 0) return 'editor';
|
||||
if ((permBits & PermissionBits.VIEW) > 0) return 'viewer';
|
||||
return 'none';
|
||||
}
|
||||
|
||||
|
|
@ -276,14 +285,14 @@ export function permBitsToAccessLevel(permBits: number): TAccessLevel {
|
|||
*/
|
||||
export function accessRoleToPermBits(accessRoleId: string): number {
|
||||
switch (accessRoleId) {
|
||||
case ACCESS_ROLE_IDS.AGENT_VIEWER:
|
||||
return PERMISSION_BITS.VIEW;
|
||||
case ACCESS_ROLE_IDS.AGENT_EDITOR:
|
||||
return PERMISSION_BITS.VIEW | PERMISSION_BITS.EDIT;
|
||||
case ACCESS_ROLE_IDS.AGENT_OWNER:
|
||||
return PERMISSION_BITS.VIEW | PERMISSION_BITS.EDIT | PERMISSION_BITS.DELETE;
|
||||
case AccessRoleIds.AGENT_VIEWER:
|
||||
return PermissionBits.VIEW;
|
||||
case AccessRoleIds.AGENT_EDITOR:
|
||||
return PermissionBits.VIEW | PermissionBits.EDIT;
|
||||
case AccessRoleIds.AGENT_OWNER:
|
||||
return PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE;
|
||||
default:
|
||||
return PERMISSION_BITS.VIEW;
|
||||
return PermissionBits.VIEW;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { AssistantsEndpoint } from './schemas';
|
||||
import * as q from './types/queries';
|
||||
import { ResourceType } from './accessPermissions';
|
||||
|
||||
// Testing this buildQuery function
|
||||
const buildQuery = (params: Record<string, unknown>): string => {
|
||||
|
|
@ -323,15 +324,16 @@ export const searchPrincipals = (params: q.PrincipalSearchParams) => {
|
|||
return url;
|
||||
};
|
||||
|
||||
export const getAccessRoles = (resourceType: string) => `/api/permissions/${resourceType}/roles`;
|
||||
export const getAccessRoles = (resourceType: ResourceType) =>
|
||||
`/api/permissions/${resourceType}/roles`;
|
||||
|
||||
export const getResourcePermissions = (resourceType: string, resourceId: string) =>
|
||||
export const getResourcePermissions = (resourceType: ResourceType, resourceId: string) =>
|
||||
`/api/permissions/${resourceType}/${resourceId}`;
|
||||
|
||||
export const updateResourcePermissions = (resourceType: string, resourceId: string) =>
|
||||
export const updateResourcePermissions = (resourceType: ResourceType, resourceId: string) =>
|
||||
`/api/permissions/${resourceType}/${resourceId}`;
|
||||
|
||||
export const getEffectivePermissions = (resourceType: string, resourceId: string) =>
|
||||
export const getEffectivePermissions = (resourceType: ResourceType, resourceId: string) =>
|
||||
`/api/permissions/${resourceType}/${resourceId}/effective`;
|
||||
|
||||
// SharePoint Graph API Token
|
||||
|
|
|
|||
|
|
@ -909,19 +909,21 @@ export function searchPrincipals(
|
|||
return request.get(endpoints.searchPrincipals(params));
|
||||
}
|
||||
|
||||
export function getAccessRoles(resourceType: string): Promise<q.AccessRolesResponse> {
|
||||
export function getAccessRoles(
|
||||
resourceType: permissions.ResourceType,
|
||||
): Promise<q.AccessRolesResponse> {
|
||||
return request.get(endpoints.getAccessRoles(resourceType));
|
||||
}
|
||||
|
||||
export function getResourcePermissions(
|
||||
resourceType: string,
|
||||
resourceType: permissions.ResourceType,
|
||||
resourceId: string,
|
||||
): Promise<permissions.TGetResourcePermissionsResponse> {
|
||||
return request.get(endpoints.getResourcePermissions(resourceType, resourceId));
|
||||
}
|
||||
|
||||
export function updateResourcePermissions(
|
||||
resourceType: string,
|
||||
resourceType: permissions.ResourceType,
|
||||
resourceId: string,
|
||||
data: permissions.TUpdateResourcePermissionsRequest,
|
||||
): Promise<permissions.TUpdateResourcePermissionsResponse> {
|
||||
|
|
@ -929,7 +931,7 @@ export function updateResourcePermissions(
|
|||
}
|
||||
|
||||
export function getEffectivePermissions(
|
||||
resourceType: string,
|
||||
resourceType: permissions.ResourceType,
|
||||
resourceId: string,
|
||||
): Promise<permissions.TEffectivePermissionsResponse> {
|
||||
return request.get(endpoints.getEffectivePermissions(resourceType, resourceId));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { QueryKeys } from '../keys';
|
|||
import * as s from '../schemas';
|
||||
import * as t from '../types';
|
||||
import * as permissions from '../accessPermissions';
|
||||
import { ResourceType } from '../accessPermissions';
|
||||
|
||||
export { hasPermissions } from '../accessPermissions';
|
||||
|
||||
|
|
@ -405,7 +406,7 @@ export const useSearchPrincipalsQuery = (
|
|||
};
|
||||
|
||||
export const useGetAccessRolesQuery = (
|
||||
resourceType: string,
|
||||
resourceType: ResourceType,
|
||||
config?: UseQueryOptions<q.AccessRolesResponse>,
|
||||
): QueryObserverResult<q.AccessRolesResponse> => {
|
||||
return useQuery<q.AccessRolesResponse>(
|
||||
|
|
@ -423,7 +424,7 @@ export const useGetAccessRolesQuery = (
|
|||
};
|
||||
|
||||
export const useGetResourcePermissionsQuery = (
|
||||
resourceType: string,
|
||||
resourceType: ResourceType,
|
||||
resourceId: string,
|
||||
config?: UseQueryOptions<permissions.TGetResourcePermissionsResponse>,
|
||||
): QueryObserverResult<permissions.TGetResourcePermissionsResponse> => {
|
||||
|
|
@ -445,7 +446,7 @@ export const useUpdateResourcePermissionsMutation = (): UseMutationResult<
|
|||
permissions.TUpdateResourcePermissionsResponse,
|
||||
Error,
|
||||
{
|
||||
resourceType: string;
|
||||
resourceType: ResourceType;
|
||||
resourceId: string;
|
||||
data: permissions.TUpdateResourcePermissionsRequest;
|
||||
}
|
||||
|
|
@ -472,7 +473,7 @@ export const useUpdateResourcePermissionsMutation = (): UseMutationResult<
|
|||
};
|
||||
|
||||
export const useGetEffectivePermissionsQuery = (
|
||||
resourceType: string,
|
||||
resourceType: ResourceType,
|
||||
resourceId: string,
|
||||
config?: UseQueryOptions<permissions.TEffectivePermissionsResponse>,
|
||||
): QueryObserverResult<permissions.TEffectivePermissionsResponse> => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { InfiniteData } from '@tanstack/react-query';
|
||||
import type { ACCESS_ROLE_IDS } from '../accessPermissions';
|
||||
import type { AccessRoleIds } from '../accessPermissions';
|
||||
import type * as a from '../types/agents';
|
||||
import type * as s from '../schemas';
|
||||
import type * as t from '../types';
|
||||
|
|
@ -159,7 +159,7 @@ export type PrincipalSearchResponse = {
|
|||
};
|
||||
|
||||
export type AccessRole = {
|
||||
accessRoleId: ACCESS_ROLE_IDS;
|
||||
accessRoleId: AccessRoleIds;
|
||||
name: string;
|
||||
description: string;
|
||||
permBits: number;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,4 @@
|
|||
/**
|
||||
* Permission bit flags
|
||||
*/
|
||||
export enum PermissionBits {
|
||||
/** 0001 - Can view/access the resource */
|
||||
VIEW = 1,
|
||||
/** 0010 - Can modify the resource */
|
||||
EDIT = 2,
|
||||
/** 0100 - Can delete the resource */
|
||||
DELETE = 4,
|
||||
/** 1000 - Can share the resource with others */
|
||||
SHARE = 8,
|
||||
}
|
||||
import { PermissionBits } from 'librechat-data-provider';
|
||||
|
||||
/**
|
||||
* Common role combinations
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import mongoose from 'mongoose';
|
||||
import { AccessRoleIds, ResourceType, PermissionBits } from 'librechat-data-provider';
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
||||
import { createAccessRoleMethods } from './accessRole';
|
||||
import { PermissionBits, RoleBits } from '~/common';
|
||||
import accessRoleSchema from '~/schema/accessRole';
|
||||
import type * as t from '~/types';
|
||||
import { createAccessRoleMethods } from './accessRole';
|
||||
import accessRoleSchema from '~/schema/accessRole';
|
||||
import { RoleBits } from '~/common';
|
||||
|
||||
let mongoServer: MongoMemoryServer;
|
||||
let AccessRole: mongoose.Model<t.IAccessRole>;
|
||||
|
|
@ -32,7 +33,7 @@ describe('AccessRole Model Tests', () => {
|
|||
accessRoleId: 'test_viewer',
|
||||
name: 'Test Viewer',
|
||||
description: 'Test role for viewer permissions',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER,
|
||||
};
|
||||
|
||||
|
|
@ -98,7 +99,7 @@ describe('AccessRole Model Tests', () => {
|
|||
accessRoleId: 'test_editor',
|
||||
name: 'Test Editor',
|
||||
description: 'Test role for editor permissions',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.EDITOR,
|
||||
},
|
||||
];
|
||||
|
|
@ -120,17 +121,17 @@ describe('AccessRole Model Tests', () => {
|
|||
// Create sample roles for testing
|
||||
await Promise.all([
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
name: 'Agent Viewer',
|
||||
description: 'Can view agents',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER,
|
||||
}),
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
name: 'Agent Editor',
|
||||
description: 'Can edit agents',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.EDITOR,
|
||||
}),
|
||||
methods.createRole({
|
||||
|
|
@ -154,7 +155,7 @@ describe('AccessRole Model Tests', () => {
|
|||
const agentRoles = await methods.findRolesByResourceType('agent');
|
||||
expect(agentRoles).toHaveLength(2);
|
||||
expect(agentRoles.map((r) => r.accessRoleId).sort()).toEqual(
|
||||
['agent_editor', 'agent_viewer'].sort(),
|
||||
[AccessRoleIds.AGENT_EDITOR, AccessRoleIds.AGENT_VIEWER].sort(),
|
||||
);
|
||||
|
||||
const projectRoles = await methods.findRolesByResourceType('project');
|
||||
|
|
@ -167,11 +168,11 @@ describe('AccessRole Model Tests', () => {
|
|||
test('should find role by permissions', async () => {
|
||||
const viewerRole = await methods.findRoleByPermissions('agent', RoleBits.VIEWER);
|
||||
expect(viewerRole).toBeDefined();
|
||||
expect(viewerRole?.accessRoleId).toBe('agent_viewer');
|
||||
expect(viewerRole?.accessRoleId).toBe(AccessRoleIds.AGENT_VIEWER);
|
||||
|
||||
const editorRole = await methods.findRoleByPermissions('agent', RoleBits.EDITOR);
|
||||
expect(editorRole).toBeDefined();
|
||||
expect(editorRole?.accessRoleId).toBe('agent_editor');
|
||||
expect(editorRole?.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR);
|
||||
});
|
||||
|
||||
test('should return null when no role matches the permissions', async () => {
|
||||
|
|
@ -192,19 +193,26 @@ describe('AccessRole Model Tests', () => {
|
|||
|
||||
// Verify the result contains the default roles
|
||||
expect(Object.keys(result).sort()).toEqual(
|
||||
['agent_editor', 'agent_owner', 'agent_viewer'].sort(),
|
||||
[
|
||||
AccessRoleIds.AGENT_EDITOR,
|
||||
AccessRoleIds.AGENT_OWNER,
|
||||
AccessRoleIds.AGENT_VIEWER,
|
||||
AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
].sort(),
|
||||
);
|
||||
|
||||
// Verify each role exists in the database
|
||||
const agentViewerRole = await methods.findRoleByIdentifier('agent_viewer');
|
||||
const agentViewerRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER);
|
||||
expect(agentViewerRole).toBeDefined();
|
||||
expect(agentViewerRole?.permBits).toBe(RoleBits.VIEWER);
|
||||
|
||||
const agentEditorRole = await methods.findRoleByIdentifier('agent_editor');
|
||||
const agentEditorRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR);
|
||||
expect(agentEditorRole).toBeDefined();
|
||||
expect(agentEditorRole?.permBits).toBe(RoleBits.EDITOR);
|
||||
|
||||
const agentOwnerRole = await methods.findRoleByIdentifier('agent_owner');
|
||||
const agentOwnerRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_OWNER);
|
||||
expect(agentOwnerRole).toBeDefined();
|
||||
expect(agentOwnerRole?.permBits).toBe(RoleBits.OWNER);
|
||||
});
|
||||
|
|
@ -212,10 +220,10 @@ describe('AccessRole Model Tests', () => {
|
|||
test('should not modify existing roles when seeding', async () => {
|
||||
// Create a modified version of a default role
|
||||
const customRole = {
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
name: 'Custom Viewer',
|
||||
description: 'Custom viewer description',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER,
|
||||
};
|
||||
|
||||
|
|
@ -225,7 +233,7 @@ describe('AccessRole Model Tests', () => {
|
|||
await methods.seedDefaultRoles();
|
||||
|
||||
// Verify the custom role was not modified
|
||||
const role = await methods.findRoleByIdentifier('agent_viewer');
|
||||
const role = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER);
|
||||
expect(role?.name).toBe(customRole.name);
|
||||
expect(role?.description).toBe(customRole.description);
|
||||
});
|
||||
|
|
@ -238,27 +246,27 @@ describe('AccessRole Model Tests', () => {
|
|||
// Create sample roles with ascending permission levels
|
||||
await Promise.all([
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
name: 'Agent Viewer',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER, // 1
|
||||
}),
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
name: 'Agent Editor',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.EDITOR, // 3
|
||||
}),
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_manager',
|
||||
name: 'Agent Manager',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.MANAGER, // 7
|
||||
}),
|
||||
methods.createRole({
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
name: 'Agent Owner',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.OWNER, // 15
|
||||
}),
|
||||
]);
|
||||
|
|
@ -267,7 +275,7 @@ describe('AccessRole Model Tests', () => {
|
|||
test('should find exact matching role', async () => {
|
||||
const role = await methods.getRoleForPermissions('agent', RoleBits.EDITOR);
|
||||
expect(role).toBeDefined();
|
||||
expect(role?.accessRoleId).toBe('agent_editor');
|
||||
expect(role?.accessRoleId).toBe(AccessRoleIds.AGENT_EDITOR);
|
||||
expect(role?.permBits).toBe(RoleBits.EDITOR);
|
||||
});
|
||||
|
||||
|
|
@ -278,7 +286,7 @@ describe('AccessRole Model Tests', () => {
|
|||
// Should return VIEWER (1) as closest matching role without exceeding the permission bits
|
||||
const role = await methods.getRoleForPermissions('agent', customPerm);
|
||||
expect(role).toBeDefined();
|
||||
expect(role?.accessRoleId).toBe('agent_viewer');
|
||||
expect(role?.accessRoleId).toBe(AccessRoleIds.AGENT_VIEWER);
|
||||
});
|
||||
|
||||
test('should return null when no compatible role is found', async () => {
|
||||
|
|
@ -301,7 +309,7 @@ describe('AccessRole Model Tests', () => {
|
|||
// Query for agent roles
|
||||
const agentRole = await methods.getRoleForPermissions('agent', RoleBits.VIEWER);
|
||||
expect(agentRole).toBeDefined();
|
||||
expect(agentRole?.accessRoleId).toBe('agent_viewer');
|
||||
expect(agentRole?.accessRoleId).toBe(AccessRoleIds.AGENT_VIEWER);
|
||||
|
||||
// Query for project roles
|
||||
const projectRole = await methods.getRoleForPermissions('project', RoleBits.VIEWER);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { AccessRoleIds, ResourceType, PermissionBits } from 'librechat-data-provider';
|
||||
import type { Model, Types, DeleteResult } from 'mongoose';
|
||||
import { RoleBits, PermissionBits } from '~/common';
|
||||
import type { IAccessRole } from '~/types';
|
||||
import { RoleBits } from '~/common';
|
||||
|
||||
export function createAccessRoleMethods(mongoose: typeof import('mongoose')) {
|
||||
/**
|
||||
|
|
@ -104,68 +105,45 @@ export function createAccessRoleMethods(mongoose: typeof import('mongoose')) {
|
|||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
const defaultRoles = [
|
||||
{
|
||||
accessRoleId: 'agent_viewer',
|
||||
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
||||
name: 'com_ui_role_viewer',
|
||||
description: 'com_ui_role_viewer_desc',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.VIEWER,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'agent_editor',
|
||||
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
||||
name: 'com_ui_role_editor',
|
||||
description: 'com_ui_role_editor_desc',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.EDITOR,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'agent_owner',
|
||||
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
||||
name: 'com_ui_role_owner',
|
||||
description: 'com_ui_role_owner_desc',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
permBits: RoleBits.OWNER,
|
||||
},
|
||||
// Prompt access roles
|
||||
{
|
||||
accessRoleId: 'prompt_viewer',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
||||
name: 'com_ui_role_viewer',
|
||||
description: 'com_ui_role_viewer_desc',
|
||||
resourceType: 'prompt',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: RoleBits.VIEWER,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'prompt_editor',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
||||
name: 'com_ui_role_editor',
|
||||
description: 'com_ui_role_editor_desc',
|
||||
resourceType: 'prompt',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: RoleBits.EDITOR,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'prompt_owner',
|
||||
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
||||
name: 'com_ui_role_owner',
|
||||
description: 'com_ui_role_owner_desc',
|
||||
resourceType: 'prompt',
|
||||
permBits: RoleBits.OWNER,
|
||||
},
|
||||
// PromptGroup access roles
|
||||
{
|
||||
accessRoleId: 'promptGroup_viewer',
|
||||
name: 'com_ui_role_viewer',
|
||||
description: 'com_ui_role_viewer_desc',
|
||||
resourceType: 'promptGroup',
|
||||
permBits: RoleBits.VIEWER,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'promptGroup_editor',
|
||||
name: 'com_ui_role_editor',
|
||||
description: 'com_ui_role_editor_desc',
|
||||
resourceType: 'promptGroup',
|
||||
permBits: RoleBits.EDITOR,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'promptGroup_owner',
|
||||
name: 'com_ui_role_owner',
|
||||
description: 'com_ui_role_owner_desc',
|
||||
resourceType: 'promptGroup',
|
||||
resourceType: ResourceType.PROMPTGROUP,
|
||||
permBits: RoleBits.OWNER,
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import mongoose from 'mongoose';
|
||||
import { ResourceType, PermissionBits } from 'librechat-data-provider';
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
||||
import { createAclEntryMethods } from './aclEntry';
|
||||
import { PermissionBits } from '~/common';
|
||||
import aclEntrySchema from '~/schema/aclEntry';
|
||||
import type * as t from '~/types';
|
||||
import { createAclEntryMethods } from './aclEntry';
|
||||
import aclEntrySchema from '~/schema/aclEntry';
|
||||
|
||||
let mongoServer: MongoMemoryServer;
|
||||
let AclEntry: mongoose.Model<t.IAclEntry>;
|
||||
|
|
@ -38,7 +38,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const entry = await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -59,7 +59,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const entry = await methods.grantPermission(
|
||||
'group',
|
||||
groupId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW | PermissionBits.EDIT,
|
||||
grantedById,
|
||||
|
|
@ -76,7 +76,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const entry = await methods.grantPermission(
|
||||
'public',
|
||||
null,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -93,7 +93,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -122,7 +122,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -130,7 +130,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'group',
|
||||
groupId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
grantedById,
|
||||
|
|
@ -138,7 +138,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'public',
|
||||
null,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -155,7 +155,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -163,7 +163,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'group',
|
||||
groupId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
grantedById,
|
||||
|
|
@ -173,7 +173,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'public',
|
||||
null,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
otherResourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -188,7 +188,7 @@ describe('AclEntry Model Tests', () => {
|
|||
|
||||
const entries = await methods.findEntriesByPrincipalsAndResource(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
);
|
||||
expect(entries).toHaveLength(2);
|
||||
|
|
@ -200,7 +200,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** User has VIEW permission */
|
||||
const hasViewPermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
);
|
||||
|
|
@ -209,7 +209,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** User doesn't have EDIT permission */
|
||||
const hasEditPermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
);
|
||||
|
|
@ -222,7 +222,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** Group has EDIT permission */
|
||||
const hasEditPermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
);
|
||||
|
|
@ -238,7 +238,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** User has VIEW and group has EDIT, together they should have both */
|
||||
const hasViewPermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
);
|
||||
|
|
@ -246,7 +246,7 @@ describe('AclEntry Model Tests', () => {
|
|||
|
||||
const hasEditPermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
);
|
||||
|
|
@ -255,7 +255,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** Neither has DELETE permission */
|
||||
const hasDeletePermission = await methods.hasPermission(
|
||||
principalsList,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.DELETE,
|
||||
);
|
||||
|
|
@ -281,7 +281,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -292,7 +292,7 @@ describe('AclEntry Model Tests', () => {
|
|||
expect(entriesBefore).toHaveLength(1);
|
||||
|
||||
/** Revoke it */
|
||||
const result = await methods.revokePermission('user', userId, 'agent', resourceId);
|
||||
const result = await methods.revokePermission('user', userId, ResourceType.AGENT, resourceId);
|
||||
expect(result.deletedCount).toBe(1);
|
||||
|
||||
/** Verify it's gone */
|
||||
|
|
@ -305,7 +305,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -315,7 +315,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const updated = await methods.modifyPermissionBits(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.EDIT,
|
||||
null,
|
||||
|
|
@ -330,7 +330,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW | PermissionBits.EDIT,
|
||||
grantedById,
|
||||
|
|
@ -340,7 +340,7 @@ describe('AclEntry Model Tests', () => {
|
|||
const updated = await methods.modifyPermissionBits(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
null,
|
||||
PermissionBits.EDIT,
|
||||
|
|
@ -355,7 +355,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -387,7 +387,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId1,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -397,7 +397,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'user',
|
||||
userId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId2,
|
||||
PermissionBits.VIEW | PermissionBits.EDIT,
|
||||
grantedById,
|
||||
|
|
@ -407,7 +407,7 @@ describe('AclEntry Model Tests', () => {
|
|||
await methods.grantPermission(
|
||||
'group',
|
||||
groupId,
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
resourceId3,
|
||||
PermissionBits.VIEW,
|
||||
grantedById,
|
||||
|
|
@ -416,7 +416,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** Find resources with VIEW permission for user */
|
||||
const userViewableResources = await methods.findAccessibleResources(
|
||||
[{ principalType: 'user', principalId: userId }],
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
PermissionBits.VIEW,
|
||||
);
|
||||
|
||||
|
|
@ -431,7 +431,7 @@ describe('AclEntry Model Tests', () => {
|
|||
{ principalType: 'user', principalId: userId },
|
||||
{ principalType: 'group', principalId: groupId },
|
||||
],
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
PermissionBits.VIEW,
|
||||
);
|
||||
|
||||
|
|
@ -440,7 +440,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** Find resources with EDIT permission for user */
|
||||
const editableResources = await methods.findAccessibleResources(
|
||||
[{ principalType: 'user', principalId: userId }],
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
PermissionBits.EDIT,
|
||||
);
|
||||
|
||||
|
|
@ -467,7 +467,7 @@ describe('AclEntry Model Tests', () => {
|
|||
principalType: 'user',
|
||||
principalId: userId,
|
||||
principalModel: 'User',
|
||||
resourceType: 'agent',
|
||||
resourceType: ResourceType.AGENT,
|
||||
resourceId: childResourceId,
|
||||
permBits: PermissionBits.VIEW,
|
||||
grantedBy: grantedById,
|
||||
|
|
@ -477,7 +477,7 @@ describe('AclEntry Model Tests', () => {
|
|||
/** Get effective permissions */
|
||||
const effective = await methods.getEffectivePermissions(
|
||||
[{ principalType: 'user', principalId: userId }],
|
||||
'agent',
|
||||
ResourceType.AGENT,
|
||||
childResourceId,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const accessRoleSchema = new Schema<IAccessRole>(
|
|||
description: String,
|
||||
resourceType: {
|
||||
type: String,
|
||||
enum: ['agent', 'project', 'file', 'prompt', 'promptGroup'],
|
||||
enum: ['agent', 'project', 'file', 'promptGroup'],
|
||||
required: true,
|
||||
default: 'agent',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ export type AclEntry = {
|
|||
principalId?: Types.ObjectId;
|
||||
/** The model name for the principal ('User' or 'Group') */
|
||||
principalModel?: 'User' | 'Group';
|
||||
/** The type of resource ('agent', 'project', 'file', 'prompt', 'promptGroup') */
|
||||
resourceType: 'agent' | 'project' | 'file' | 'prompt' | 'promptGroup';
|
||||
/** The type of resource ('agent', 'project', 'file', 'promptGroup') */
|
||||
resourceType: 'agent' | 'project' | 'file' | 'promptGroup';
|
||||
/** The ID of the resource */
|
||||
resourceId: Types.ObjectId;
|
||||
/** Permission bits for this entry */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue