diff --git a/client/src/components/Prompts/SharePrompt.tsx b/client/src/components/Prompts/SharePrompt.tsx index 9dda100668..ea5bfe62dc 100644 --- a/client/src/components/Prompts/SharePrompt.tsx +++ b/client/src/components/Prompts/SharePrompt.tsx @@ -4,8 +4,8 @@ import { SystemRoles, Permissions, ResourceType, - PermissionTypes, PermissionBits, + PermissionTypes, } from 'librechat-data-provider'; import { Button } from '@librechat/client'; import type { TPromptGroup } from 'librechat-data-provider'; diff --git a/client/src/components/Sharing/GenericGrantAccessDialog.tsx b/client/src/components/Sharing/GenericGrantAccessDialog.tsx index 8dfa7704ae..89225ea52e 100644 --- a/client/src/components/Sharing/GenericGrantAccessDialog.tsx +++ b/client/src/components/Sharing/GenericGrantAccessDialog.tsx @@ -36,7 +36,7 @@ export default function GenericGrantAccessDialog({ resourceId?: string | null; resourceName?: string; resourceType: ResourceType; - onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole: AccessRoleIds) => void; + onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole?: AccessRoleIds) => void; disabled?: boolean; children?: React.ReactNode; }) { diff --git a/client/src/components/Sharing/GenericManagePermissionsDialog.tsx b/client/src/components/Sharing/GenericManagePermissionsDialog.tsx index 1935f2e67d..c02795891c 100644 --- a/client/src/components/Sharing/GenericManagePermissionsDialog.tsx +++ b/client/src/components/Sharing/GenericManagePermissionsDialog.tsx @@ -30,7 +30,7 @@ export default function GenericManagePermissionsDialog({ onUpdatePermissions?: ( shares: TPrincipal[], isPublic: boolean, - publicRole: AccessRoleIds, + publicRole?: AccessRoleIds, ) => void; children?: React.ReactNode; }) { @@ -84,7 +84,7 @@ export default function GenericManagePermissionsDialog({ setHasChanges(true); }; - const handleRoleChange = (idOnTheSource: string, newRole: string) => { + const handleRoleChange = (idOnTheSource: string, newRole: AccessRoleIds) => { setManagedShares( managedShares.map((s) => s.idOnTheSource === idOnTheSource ? { ...s, accessRoleId: newRole } : s, @@ -162,7 +162,7 @@ export default function GenericManagePermissionsDialog({ setManagedPublicRole(config?.defaultViewerRoleId); } }; - const handlePublicRoleChange = (role: string) => { + const handlePublicRoleChange = (role: AccessRoleIds) => { setManagedPublicRole(role); setHasChanges(true); }; diff --git a/client/src/components/Sharing/GrantAccessDialog.tsx b/client/src/components/Sharing/GrantAccessDialog.tsx index 4f8bb7833a..61ab5f7f3e 100644 --- a/client/src/components/Sharing/GrantAccessDialog.tsx +++ b/client/src/components/Sharing/GrantAccessDialog.tsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect } from 'react'; +import { ResourceType, AccessRoleIds } from 'librechat-data-provider'; import { Share2Icon, Users, Loader, Shield, Link, CopyCheck } from 'lucide-react'; -import { Permissions, ResourceType, PermissionTypes, AccessRoleIds } from 'librechat-data-provider'; import { useGetResourcePermissionsQuery, useUpdateResourcePermissionsMutation, @@ -15,7 +15,7 @@ import { useToastContext, } from '@librechat/client'; import type { TPrincipal } from 'librechat-data-provider'; -import { useLocalize, useCopyToClipboard, useHasAccess } from '~/hooks'; +import { useLocalize, useCopyToClipboard, usePeoplePickerPermissions } from '~/hooks'; import ManagePermissionsDialog from './ManagePermissionsDialog'; import PublicSharingToggle from './PublicSharingToggle'; import AccessRolesPicker from './AccessRolesPicker'; @@ -37,29 +37,7 @@ export default function GrantAccessDialog({ }) { const localize = useLocalize(); const { showToast } = useToastContext(); - - // Check if user has permission to access people picker - const canViewUsers = useHasAccess({ - permissionType: PermissionTypes.PEOPLE_PICKER, - permission: Permissions.VIEW_USERS, - }); - const canViewGroups = useHasAccess({ - permissionType: PermissionTypes.PEOPLE_PICKER, - permission: Permissions.VIEW_GROUPS, - }); - const hasPeoplePickerAccess = canViewUsers || canViewGroups; - - /** Type filter based on permissions */ - const peoplePickerTypeFilter = useMemo(() => { - if (canViewUsers && canViewGroups) { - return null; // Both types allowed - } else if (canViewUsers) { - return 'user' as const; - } else if (canViewGroups) { - return 'group' as const; - } - return null; - }, [canViewUsers, canViewGroups]); + const { hasPeoplePickerAccess, peoplePickerTypeFilter } = usePeoplePickerPermissions(); const { data: permissionsData, @@ -72,7 +50,7 @@ export default function GrantAccessDialog({ const updatePermissionsMutation = useUpdateResourcePermissionsMutation(); const [newShares, setNewShares] = useState([]); - const [defaultPermissionId, setDefaultPermissionId] = useState( + const [defaultPermissionId, setDefaultPermissionId] = useState( AccessRoleIds.AGENT_VIEWER, ); const [isModalOpen, setIsModalOpen] = useState(false); diff --git a/client/src/components/Sharing/PeoplePicker/PeoplePicker.tsx b/client/src/components/Sharing/PeoplePicker/PeoplePicker.tsx index 7c2dec5b90..8aa4c7fdbb 100644 --- a/client/src/components/Sharing/PeoplePicker/PeoplePicker.tsx +++ b/client/src/components/Sharing/PeoplePicker/PeoplePicker.tsx @@ -1,4 +1,5 @@ import React, { useState, useMemo } from 'react'; +import { PrincipalType } from 'librechat-data-provider'; import type { TPrincipal, PrincipalSearchParams } from 'librechat-data-provider'; import { useSearchPrincipalsQuery } from 'librechat-data-provider/react-query'; import PeoplePickerSearchItem from './PeoplePickerSearchItem'; @@ -10,7 +11,7 @@ interface PeoplePickerProps { onSelectionChange: (principals: TPrincipal[]) => void; placeholder?: string; className?: string; - typeFilter?: 'user' | 'group' | null; + typeFilter?: PrincipalType.USER | PrincipalType.GROUP | PrincipalType.ROLE | null; } export default function PeoplePicker({ diff --git a/client/src/components/Sharing/PublicSharingToggle.tsx b/client/src/components/Sharing/PublicSharingToggle.tsx index 62f9d8e086..a48960d853 100644 --- a/client/src/components/Sharing/PublicSharingToggle.tsx +++ b/client/src/components/Sharing/PublicSharingToggle.tsx @@ -14,7 +14,7 @@ export default function PublicSharingToggle({ resourceType = ResourceType.AGENT, }: { isPublic: boolean; - publicRole: AccessRoleIds; + publicRole?: AccessRoleIds; onPublicToggle: (isPublic: boolean) => void; onPublicRoleChange: (role: AccessRoleIds) => void; resourceType?: ResourceType; diff --git a/client/src/components/SidePanel/Agents/AgentFooter.tsx b/client/src/components/SidePanel/Agents/AgentFooter.tsx index 4c63c94cda..5b070369ee 100644 --- a/client/src/components/SidePanel/Agents/AgentFooter.tsx +++ b/client/src/components/SidePanel/Agents/AgentFooter.tsx @@ -4,8 +4,8 @@ import { SystemRoles, Permissions, ResourceType, - PermissionTypes, PermissionBits, + PermissionTypes, } from 'librechat-data-provider'; import type { AgentForm, AgentPanelProps } from '~/common'; import { useLocalize, useAuthContext, useHasAccess, useResourcePermissions } from '~/hooks'; @@ -43,7 +43,7 @@ export default function AgentFooter({ permission: Permissions.SHARED_GLOBAL, }); const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions( - 'agent', + ResourceType.AGENT, agent?._id || '', ); diff --git a/client/src/hooks/Sharing/usePeoplePickerPermissions.ts b/client/src/hooks/Sharing/usePeoplePickerPermissions.ts index 953f87f8f6..940439105f 100644 --- a/client/src/hooks/Sharing/usePeoplePickerPermissions.ts +++ b/client/src/hooks/Sharing/usePeoplePickerPermissions.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { PermissionTypes, Permissions } from 'librechat-data-provider'; +import { PermissionTypes, PrincipalType, Permissions } from 'librechat-data-provider'; import { useHasAccess } from '~/hooks'; /** @@ -17,21 +17,33 @@ export const usePeoplePickerPermissions = () => { permission: Permissions.VIEW_GROUPS, }); - const hasPeoplePickerAccess = canViewUsers || canViewGroups; + const canViewRoles = useHasAccess({ + permissionType: PermissionTypes.PEOPLE_PICKER, + permission: Permissions.VIEW_ROLES, + }); - const peoplePickerTypeFilter = useMemo(() => { - if (canViewUsers && canViewGroups) { - return null; // Both types allowed + const hasPeoplePickerAccess = canViewUsers || canViewGroups || canViewRoles; + + const peoplePickerTypeFilter: + | PrincipalType.USER + | PrincipalType.GROUP + | PrincipalType.ROLE + | null = useMemo(() => { + if (canViewUsers && canViewGroups && canViewRoles) { + return null; // All types allowed } else if (canViewUsers) { - return 'user' as const; + return PrincipalType.USER; } else if (canViewGroups) { - return 'group' as const; + return PrincipalType.GROUP; + } else if (canViewRoles) { + return PrincipalType.ROLE; } return null; - }, [canViewUsers, canViewGroups]); + }, [canViewUsers, canViewGroups, canViewRoles]); return { canViewUsers, + canViewRoles, canViewGroups, hasPeoplePickerAccess, peoplePickerTypeFilter, diff --git a/packages/data-provider/src/roles.ts b/packages/data-provider/src/roles.ts index d36ae72f03..22d20ebd5a 100644 --- a/packages/data-provider/src/roles.ts +++ b/packages/data-provider/src/roles.ts @@ -30,7 +30,6 @@ export enum SystemRoles { USER = 'USER', } -// The role schema now only needs to reference the permissions schema. export const roleSchema = z.object({ name: z.string(), permissions: permissionsSchema, @@ -38,7 +37,6 @@ export const roleSchema = z.object({ export type TRole = z.infer; -// Define default roles using the new structure. const defaultRolesSchema = z.object({ [SystemRoles.ADMIN]: roleSchema.extend({ name: z.literal(SystemRoles.ADMIN), @@ -80,6 +78,7 @@ const defaultRolesSchema = z.object({ [PermissionTypes.PEOPLE_PICKER]: peoplePickerPermissionsSchema.extend({ [Permissions.VIEW_USERS]: z.boolean().default(true), [Permissions.VIEW_GROUPS]: z.boolean().default(true), + [Permissions.VIEW_ROLES]: z.boolean().default(true), }), [PermissionTypes.MARKETPLACE]: z.object({ [Permissions.USE]: z.boolean().default(false), @@ -137,6 +136,7 @@ export const roleDefaults = defaultRolesSchema.parse({ [PermissionTypes.PEOPLE_PICKER]: { [Permissions.VIEW_USERS]: true, [Permissions.VIEW_GROUPS]: true, + [Permissions.VIEW_ROLES]: true, }, [PermissionTypes.MARKETPLACE]: { [Permissions.USE]: true, @@ -163,6 +163,7 @@ export const roleDefaults = defaultRolesSchema.parse({ [PermissionTypes.PEOPLE_PICKER]: { [Permissions.VIEW_USERS]: false, [Permissions.VIEW_GROUPS]: false, + [Permissions.VIEW_ROLES]: false, }, [PermissionTypes.MARKETPLACE]: { [Permissions.USE]: false, diff --git a/packages/data-provider/src/types/queries.ts b/packages/data-provider/src/types/queries.ts index 44bfa9fece..f5ebebb2c0 100644 --- a/packages/data-provider/src/types/queries.ts +++ b/packages/data-provider/src/types/queries.ts @@ -1,5 +1,5 @@ import type { InfiniteData } from '@tanstack/react-query'; -import type { AccessRoleIds } from '../accessPermissions'; +import type * as p from '../accessPermissions'; import type * as a from '../types/agents'; import type * as s from '../schemas'; import type * as t from '../types'; @@ -129,28 +129,14 @@ export type MemoriesResponse = { export type PrincipalSearchParams = { q: string; limit?: number; - type?: 'user' | 'group'; -}; - -export type PrincipalSearchResult = { - id?: string | null; - type: 'user' | 'group'; - name: string; - email?: string; - username?: string; - avatar?: string; - provider?: string; - source: 'local' | 'entra'; - memberCount?: number; - description?: string; - idOnTheSource?: string; + type?: p.PrincipalType.USER | p.PrincipalType.GROUP | p.PrincipalType.ROLE; }; export type PrincipalSearchResponse = { query: string; limit: number; - type?: 'user' | 'group'; - results: PrincipalSearchResult[]; + type?: p.PrincipalType.USER | p.PrincipalType.GROUP | p.PrincipalType.ROLE; + results: p.TPrincipalSearchResult[]; count: number; sources: { local: number; @@ -159,7 +145,7 @@ export type PrincipalSearchResponse = { }; export type AccessRole = { - accessRoleId: AccessRoleIds; + accessRoleId: p.AccessRoleIds; name: string; description: string; permBits: number; diff --git a/packages/data-schemas/src/types/aclEntry.ts b/packages/data-schemas/src/types/aclEntry.ts index e2ac769c27..026b852aa8 100644 --- a/packages/data-schemas/src/types/aclEntry.ts +++ b/packages/data-schemas/src/types/aclEntry.ts @@ -2,13 +2,13 @@ import type { Document, Types } from 'mongoose'; import { PrincipalType, PrincipalModel, ResourceType } from 'librechat-data-provider'; export type AclEntry = { - /** The type of principal ('user', 'group', 'public') */ + /** The type of principal (PrincipalType.USER, PrincipalType.GROUP, PrincipalType.PUBLIC) */ principalType: PrincipalType; - /** The ID of the principal (null for 'public', string for 'role') */ + /** The ID of the principal (null for PrincipalType.PUBLIC, string for PrincipalType.ROLE) */ principalId?: Types.ObjectId | string; - /** The model name for the principal ('User' or 'Group') */ + /** The model name for the principal (`PrincipalModel`) */ principalModel?: PrincipalModel; - /** The type of resource ('agent', 'project', 'file', 'promptGroup') */ + /** The type of resource (`ResourceType`) */ resourceType: ResourceType; /** The ID of the resource */ resourceId: Types.ObjectId;