chore: add missing default role schema values for people picker perms, cleanup typing

This commit is contained in:
Danny Avila 2025-08-04 16:57:03 -04:00
parent 8e003083dc
commit b680ba2c75
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
11 changed files with 47 additions and 69 deletions

View file

@ -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';

View file

@ -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;
}) {

View file

@ -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);
};

View file

@ -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<TPrincipal[]>([]);
const [defaultPermissionId, setDefaultPermissionId] = useState<string>(
const [defaultPermissionId, setDefaultPermissionId] = useState<AccessRoleIds>(
AccessRoleIds.AGENT_VIEWER,
);
const [isModalOpen, setIsModalOpen] = useState(false);

View file

@ -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({

View file

@ -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;

View file

@ -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 || '',
);

View file

@ -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,

View file

@ -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<typeof roleSchema>;
// 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,

View file

@ -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;

View file

@ -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;