mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20:15 +01:00
🔐 feat: Granular Role-based Permissions + Entra ID Group Discovery (#7804)
WIP: pre-granular-permissions commit
feat: Add category and support contact fields to Agent schema and UI components
Revert "feat: Add category and support contact fields to Agent schema and UI components"
This reverts commit c43a52b4c9.
Fix: Update import for renderHook in useAgentCategories.spec.tsx
fix: Update icon rendering in AgentCategoryDisplay tests to use empty spans
refactor: Improve category synchronization logic and clean up AgentConfig component
refactor: Remove unused UI flow translations from translation.json
feat: agent marketplace features
🔐 feat: Granular Role-based Permissions + Entra ID Group Discovery (#7804)
This commit is contained in:
parent
aa42759ffd
commit
66bd419baa
147 changed files with 17564 additions and 645 deletions
180
packages/data-schemas/src/methods/accessRole.ts
Normal file
180
packages/data-schemas/src/methods/accessRole.ts
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import type { Model, Types, DeleteResult } from 'mongoose';
|
||||
import { RoleBits, PermissionBits } from '~/common';
|
||||
import type { IAccessRole } from '~/types';
|
||||
|
||||
export function createAccessRoleMethods(mongoose: typeof import('mongoose')) {
|
||||
/**
|
||||
* Find an access role by its ID
|
||||
* @param roleId - The role ID
|
||||
* @returns The role document or null if not found
|
||||
*/
|
||||
async function findRoleById(roleId: string | Types.ObjectId): Promise<IAccessRole | null> {
|
||||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
return await AccessRole.findById(roleId).lean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an access role by its unique identifier
|
||||
* @param accessRoleId - The unique identifier (e.g., "agent_viewer")
|
||||
* @returns The role document or null if not found
|
||||
*/
|
||||
async function findRoleByIdentifier(
|
||||
accessRoleId: string | Types.ObjectId,
|
||||
): Promise<IAccessRole | null> {
|
||||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
return await AccessRole.findOne({ accessRoleId }).lean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all access roles for a specific resource type
|
||||
* @param resourceType - The type of resource ('agent', 'project', 'file')
|
||||
* @returns Array of role documents
|
||||
*/
|
||||
async function findRolesByResourceType(resourceType: string): Promise<IAccessRole[]> {
|
||||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
return await AccessRole.find({ resourceType }).lean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an access role by resource type and permission bits
|
||||
* @param resourceType - The type of resource
|
||||
* @param permBits - The permission bits (use PermissionBits or RoleBits enum)
|
||||
* @returns The role document or null if not found
|
||||
*/
|
||||
async function findRoleByPermissions(
|
||||
resourceType: string,
|
||||
permBits: PermissionBits | RoleBits,
|
||||
): Promise<IAccessRole | null> {
|
||||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
return await AccessRole.findOne({ resourceType, permBits }).lean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new access role
|
||||
* @param roleData - Role data (accessRoleId, name, description, resourceType, permBits)
|
||||
* @returns The created role document
|
||||
*/
|
||||
async function createRole(roleData: Partial<IAccessRole>): Promise<IAccessRole> {
|
||||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
return await AccessRole.create(roleData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing access role
|
||||
* @param accessRoleId - The unique identifier of the role to update
|
||||
* @param updateData - Data to update
|
||||
* @returns The updated role document or null if not found
|
||||
*/
|
||||
async function updateRole(
|
||||
accessRoleId: string | Types.ObjectId,
|
||||
updateData: Partial<IAccessRole>,
|
||||
): Promise<IAccessRole | null> {
|
||||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
return await AccessRole.findOneAndUpdate(
|
||||
{ accessRoleId },
|
||||
{ $set: updateData },
|
||||
{ new: true },
|
||||
).lean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an access role
|
||||
* @param accessRoleId - The unique identifier of the role to delete
|
||||
* @returns The result of the delete operation
|
||||
*/
|
||||
async function deleteRole(accessRoleId: string | Types.ObjectId): Promise<DeleteResult> {
|
||||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
return await AccessRole.deleteOne({ accessRoleId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all predefined roles
|
||||
* @returns Array of all role documents
|
||||
*/
|
||||
async function getAllRoles(): Promise<IAccessRole[]> {
|
||||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
return await AccessRole.find().lean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed default roles if they don't exist
|
||||
* @returns Object containing created roles
|
||||
*/
|
||||
async function seedDefaultRoles() {
|
||||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
const defaultRoles = [
|
||||
{
|
||||
accessRoleId: 'agent_viewer',
|
||||
name: 'com_ui_role_viewer',
|
||||
description: 'com_ui_role_viewer_desc',
|
||||
resourceType: 'agent',
|
||||
permBits: RoleBits.VIEWER,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'agent_editor',
|
||||
name: 'com_ui_role_editor',
|
||||
description: 'com_ui_role_editor_desc',
|
||||
resourceType: 'agent',
|
||||
permBits: RoleBits.EDITOR,
|
||||
},
|
||||
{
|
||||
accessRoleId: 'agent_owner',
|
||||
name: 'com_ui_role_owner',
|
||||
description: 'com_ui_role_owner_desc',
|
||||
resourceType: 'agent',
|
||||
permBits: RoleBits.OWNER,
|
||||
},
|
||||
];
|
||||
|
||||
const result: Record<string, IAccessRole> = {};
|
||||
|
||||
for (const role of defaultRoles) {
|
||||
const upsertedRole = await AccessRole.findOneAndUpdate(
|
||||
{ accessRoleId: role.accessRoleId },
|
||||
{ $setOnInsert: role },
|
||||
{ upsert: true, new: true },
|
||||
).lean();
|
||||
|
||||
result[role.accessRoleId] = upsertedRole;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the appropriate role for a set of permissions
|
||||
* @param resourceType - The type of resource
|
||||
* @param permBits - The permission bits
|
||||
* @returns The matching role or null if none found
|
||||
*/
|
||||
async function getRoleForPermissions(
|
||||
resourceType: string,
|
||||
permBits: PermissionBits | RoleBits,
|
||||
): Promise<IAccessRole | null> {
|
||||
const AccessRole = mongoose.models.AccessRole as Model<IAccessRole>;
|
||||
const exactMatch = await AccessRole.findOne({ resourceType, permBits }).lean();
|
||||
if (exactMatch) {
|
||||
return exactMatch;
|
||||
}
|
||||
|
||||
/** If no exact match, the closest role without exceeding permissions */
|
||||
const roles = await AccessRole.find({ resourceType }).sort({ permBits: -1 }).lean();
|
||||
|
||||
return roles.find((role) => (role.permBits & permBits) === role.permBits) || null;
|
||||
}
|
||||
|
||||
return {
|
||||
createRole,
|
||||
updateRole,
|
||||
deleteRole,
|
||||
getAllRoles,
|
||||
findRoleById,
|
||||
seedDefaultRoles,
|
||||
findRoleByIdentifier,
|
||||
getRoleForPermissions,
|
||||
findRoleByPermissions,
|
||||
findRolesByResourceType,
|
||||
};
|
||||
}
|
||||
|
||||
export type AccessRoleMethods = ReturnType<typeof createAccessRoleMethods>;
|
||||
Loading…
Add table
Add a link
Reference in a new issue