mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 19:30:15 +01:00
chore: add missing default role schema values for people picker perms, cleanup typing
This commit is contained in:
parent
8e003083dc
commit
b680ba2c75
11 changed files with 47 additions and 69 deletions
|
|
@ -4,8 +4,8 @@ import {
|
||||||
SystemRoles,
|
SystemRoles,
|
||||||
Permissions,
|
Permissions,
|
||||||
ResourceType,
|
ResourceType,
|
||||||
PermissionTypes,
|
|
||||||
PermissionBits,
|
PermissionBits,
|
||||||
|
PermissionTypes,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import { Button } from '@librechat/client';
|
import { Button } from '@librechat/client';
|
||||||
import type { TPromptGroup } from 'librechat-data-provider';
|
import type { TPromptGroup } from 'librechat-data-provider';
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export default function GenericGrantAccessDialog({
|
||||||
resourceId?: string | null;
|
resourceId?: string | null;
|
||||||
resourceName?: string;
|
resourceName?: string;
|
||||||
resourceType: ResourceType;
|
resourceType: ResourceType;
|
||||||
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole: AccessRoleIds) => void;
|
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole?: AccessRoleIds) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export default function GenericManagePermissionsDialog({
|
||||||
onUpdatePermissions?: (
|
onUpdatePermissions?: (
|
||||||
shares: TPrincipal[],
|
shares: TPrincipal[],
|
||||||
isPublic: boolean,
|
isPublic: boolean,
|
||||||
publicRole: AccessRoleIds,
|
publicRole?: AccessRoleIds,
|
||||||
) => void;
|
) => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -84,7 +84,7 @@ export default function GenericManagePermissionsDialog({
|
||||||
setHasChanges(true);
|
setHasChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRoleChange = (idOnTheSource: string, newRole: string) => {
|
const handleRoleChange = (idOnTheSource: string, newRole: AccessRoleIds) => {
|
||||||
setManagedShares(
|
setManagedShares(
|
||||||
managedShares.map((s) =>
|
managedShares.map((s) =>
|
||||||
s.idOnTheSource === idOnTheSource ? { ...s, accessRoleId: newRole } : s,
|
s.idOnTheSource === idOnTheSource ? { ...s, accessRoleId: newRole } : s,
|
||||||
|
|
@ -162,7 +162,7 @@ export default function GenericManagePermissionsDialog({
|
||||||
setManagedPublicRole(config?.defaultViewerRoleId);
|
setManagedPublicRole(config?.defaultViewerRoleId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handlePublicRoleChange = (role: string) => {
|
const handlePublicRoleChange = (role: AccessRoleIds) => {
|
||||||
setManagedPublicRole(role);
|
setManagedPublicRole(role);
|
||||||
setHasChanges(true);
|
setHasChanges(true);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 { Share2Icon, Users, Loader, Shield, Link, CopyCheck } from 'lucide-react';
|
||||||
import { Permissions, ResourceType, PermissionTypes, AccessRoleIds } from 'librechat-data-provider';
|
|
||||||
import {
|
import {
|
||||||
useGetResourcePermissionsQuery,
|
useGetResourcePermissionsQuery,
|
||||||
useUpdateResourcePermissionsMutation,
|
useUpdateResourcePermissionsMutation,
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
useToastContext,
|
useToastContext,
|
||||||
} from '@librechat/client';
|
} from '@librechat/client';
|
||||||
import type { TPrincipal } from 'librechat-data-provider';
|
import type { TPrincipal } from 'librechat-data-provider';
|
||||||
import { useLocalize, useCopyToClipboard, useHasAccess } from '~/hooks';
|
import { useLocalize, useCopyToClipboard, usePeoplePickerPermissions } from '~/hooks';
|
||||||
import ManagePermissionsDialog from './ManagePermissionsDialog';
|
import ManagePermissionsDialog from './ManagePermissionsDialog';
|
||||||
import PublicSharingToggle from './PublicSharingToggle';
|
import PublicSharingToggle from './PublicSharingToggle';
|
||||||
import AccessRolesPicker from './AccessRolesPicker';
|
import AccessRolesPicker from './AccessRolesPicker';
|
||||||
|
|
@ -37,29 +37,7 @@ export default function GrantAccessDialog({
|
||||||
}) {
|
}) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { showToast } = useToastContext();
|
const { showToast } = useToastContext();
|
||||||
|
const { hasPeoplePickerAccess, peoplePickerTypeFilter } = usePeoplePickerPermissions();
|
||||||
// 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 {
|
const {
|
||||||
data: permissionsData,
|
data: permissionsData,
|
||||||
|
|
@ -72,7 +50,7 @@ export default function GrantAccessDialog({
|
||||||
const updatePermissionsMutation = useUpdateResourcePermissionsMutation();
|
const updatePermissionsMutation = useUpdateResourcePermissionsMutation();
|
||||||
|
|
||||||
const [newShares, setNewShares] = useState<TPrincipal[]>([]);
|
const [newShares, setNewShares] = useState<TPrincipal[]>([]);
|
||||||
const [defaultPermissionId, setDefaultPermissionId] = useState<string>(
|
const [defaultPermissionId, setDefaultPermissionId] = useState<AccessRoleIds>(
|
||||||
AccessRoleIds.AGENT_VIEWER,
|
AccessRoleIds.AGENT_VIEWER,
|
||||||
);
|
);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
|
import { PrincipalType } from 'librechat-data-provider';
|
||||||
import type { TPrincipal, PrincipalSearchParams } from 'librechat-data-provider';
|
import type { TPrincipal, PrincipalSearchParams } from 'librechat-data-provider';
|
||||||
import { useSearchPrincipalsQuery } from 'librechat-data-provider/react-query';
|
import { useSearchPrincipalsQuery } from 'librechat-data-provider/react-query';
|
||||||
import PeoplePickerSearchItem from './PeoplePickerSearchItem';
|
import PeoplePickerSearchItem from './PeoplePickerSearchItem';
|
||||||
|
|
@ -10,7 +11,7 @@ interface PeoplePickerProps {
|
||||||
onSelectionChange: (principals: TPrincipal[]) => void;
|
onSelectionChange: (principals: TPrincipal[]) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
typeFilter?: 'user' | 'group' | null;
|
typeFilter?: PrincipalType.USER | PrincipalType.GROUP | PrincipalType.ROLE | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PeoplePicker({
|
export default function PeoplePicker({
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default function PublicSharingToggle({
|
||||||
resourceType = ResourceType.AGENT,
|
resourceType = ResourceType.AGENT,
|
||||||
}: {
|
}: {
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
publicRole: AccessRoleIds;
|
publicRole?: AccessRoleIds;
|
||||||
onPublicToggle: (isPublic: boolean) => void;
|
onPublicToggle: (isPublic: boolean) => void;
|
||||||
onPublicRoleChange: (role: AccessRoleIds) => void;
|
onPublicRoleChange: (role: AccessRoleIds) => void;
|
||||||
resourceType?: ResourceType;
|
resourceType?: ResourceType;
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import {
|
||||||
SystemRoles,
|
SystemRoles,
|
||||||
Permissions,
|
Permissions,
|
||||||
ResourceType,
|
ResourceType,
|
||||||
PermissionTypes,
|
|
||||||
PermissionBits,
|
PermissionBits,
|
||||||
|
PermissionTypes,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { AgentForm, AgentPanelProps } from '~/common';
|
import type { AgentForm, AgentPanelProps } from '~/common';
|
||||||
import { useLocalize, useAuthContext, useHasAccess, useResourcePermissions } from '~/hooks';
|
import { useLocalize, useAuthContext, useHasAccess, useResourcePermissions } from '~/hooks';
|
||||||
|
|
@ -43,7 +43,7 @@ export default function AgentFooter({
|
||||||
permission: Permissions.SHARED_GLOBAL,
|
permission: Permissions.SHARED_GLOBAL,
|
||||||
});
|
});
|
||||||
const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions(
|
const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions(
|
||||||
'agent',
|
ResourceType.AGENT,
|
||||||
agent?._id || '',
|
agent?._id || '',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { PermissionTypes, Permissions } from 'librechat-data-provider';
|
import { PermissionTypes, PrincipalType, Permissions } from 'librechat-data-provider';
|
||||||
import { useHasAccess } from '~/hooks';
|
import { useHasAccess } from '~/hooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -17,21 +17,33 @@ export const usePeoplePickerPermissions = () => {
|
||||||
permission: Permissions.VIEW_GROUPS,
|
permission: Permissions.VIEW_GROUPS,
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasPeoplePickerAccess = canViewUsers || canViewGroups;
|
const canViewRoles = useHasAccess({
|
||||||
|
permissionType: PermissionTypes.PEOPLE_PICKER,
|
||||||
|
permission: Permissions.VIEW_ROLES,
|
||||||
|
});
|
||||||
|
|
||||||
const peoplePickerTypeFilter = useMemo(() => {
|
const hasPeoplePickerAccess = canViewUsers || canViewGroups || canViewRoles;
|
||||||
if (canViewUsers && canViewGroups) {
|
|
||||||
return null; // Both types allowed
|
const peoplePickerTypeFilter:
|
||||||
|
| PrincipalType.USER
|
||||||
|
| PrincipalType.GROUP
|
||||||
|
| PrincipalType.ROLE
|
||||||
|
| null = useMemo(() => {
|
||||||
|
if (canViewUsers && canViewGroups && canViewRoles) {
|
||||||
|
return null; // All types allowed
|
||||||
} else if (canViewUsers) {
|
} else if (canViewUsers) {
|
||||||
return 'user' as const;
|
return PrincipalType.USER;
|
||||||
} else if (canViewGroups) {
|
} else if (canViewGroups) {
|
||||||
return 'group' as const;
|
return PrincipalType.GROUP;
|
||||||
|
} else if (canViewRoles) {
|
||||||
|
return PrincipalType.ROLE;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [canViewUsers, canViewGroups]);
|
}, [canViewUsers, canViewGroups, canViewRoles]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canViewUsers,
|
canViewUsers,
|
||||||
|
canViewRoles,
|
||||||
canViewGroups,
|
canViewGroups,
|
||||||
hasPeoplePickerAccess,
|
hasPeoplePickerAccess,
|
||||||
peoplePickerTypeFilter,
|
peoplePickerTypeFilter,
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ export enum SystemRoles {
|
||||||
USER = 'USER',
|
USER = 'USER',
|
||||||
}
|
}
|
||||||
|
|
||||||
// The role schema now only needs to reference the permissions schema.
|
|
||||||
export const roleSchema = z.object({
|
export const roleSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
permissions: permissionsSchema,
|
permissions: permissionsSchema,
|
||||||
|
|
@ -38,7 +37,6 @@ export const roleSchema = z.object({
|
||||||
|
|
||||||
export type TRole = z.infer<typeof roleSchema>;
|
export type TRole = z.infer<typeof roleSchema>;
|
||||||
|
|
||||||
// Define default roles using the new structure.
|
|
||||||
const defaultRolesSchema = z.object({
|
const defaultRolesSchema = z.object({
|
||||||
[SystemRoles.ADMIN]: roleSchema.extend({
|
[SystemRoles.ADMIN]: roleSchema.extend({
|
||||||
name: z.literal(SystemRoles.ADMIN),
|
name: z.literal(SystemRoles.ADMIN),
|
||||||
|
|
@ -80,6 +78,7 @@ const defaultRolesSchema = z.object({
|
||||||
[PermissionTypes.PEOPLE_PICKER]: peoplePickerPermissionsSchema.extend({
|
[PermissionTypes.PEOPLE_PICKER]: peoplePickerPermissionsSchema.extend({
|
||||||
[Permissions.VIEW_USERS]: z.boolean().default(true),
|
[Permissions.VIEW_USERS]: z.boolean().default(true),
|
||||||
[Permissions.VIEW_GROUPS]: z.boolean().default(true),
|
[Permissions.VIEW_GROUPS]: z.boolean().default(true),
|
||||||
|
[Permissions.VIEW_ROLES]: z.boolean().default(true),
|
||||||
}),
|
}),
|
||||||
[PermissionTypes.MARKETPLACE]: z.object({
|
[PermissionTypes.MARKETPLACE]: z.object({
|
||||||
[Permissions.USE]: z.boolean().default(false),
|
[Permissions.USE]: z.boolean().default(false),
|
||||||
|
|
@ -137,6 +136,7 @@ export const roleDefaults = defaultRolesSchema.parse({
|
||||||
[PermissionTypes.PEOPLE_PICKER]: {
|
[PermissionTypes.PEOPLE_PICKER]: {
|
||||||
[Permissions.VIEW_USERS]: true,
|
[Permissions.VIEW_USERS]: true,
|
||||||
[Permissions.VIEW_GROUPS]: true,
|
[Permissions.VIEW_GROUPS]: true,
|
||||||
|
[Permissions.VIEW_ROLES]: true,
|
||||||
},
|
},
|
||||||
[PermissionTypes.MARKETPLACE]: {
|
[PermissionTypes.MARKETPLACE]: {
|
||||||
[Permissions.USE]: true,
|
[Permissions.USE]: true,
|
||||||
|
|
@ -163,6 +163,7 @@ export const roleDefaults = defaultRolesSchema.parse({
|
||||||
[PermissionTypes.PEOPLE_PICKER]: {
|
[PermissionTypes.PEOPLE_PICKER]: {
|
||||||
[Permissions.VIEW_USERS]: false,
|
[Permissions.VIEW_USERS]: false,
|
||||||
[Permissions.VIEW_GROUPS]: false,
|
[Permissions.VIEW_GROUPS]: false,
|
||||||
|
[Permissions.VIEW_ROLES]: false,
|
||||||
},
|
},
|
||||||
[PermissionTypes.MARKETPLACE]: {
|
[PermissionTypes.MARKETPLACE]: {
|
||||||
[Permissions.USE]: false,
|
[Permissions.USE]: false,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { InfiniteData } from '@tanstack/react-query';
|
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 a from '../types/agents';
|
||||||
import type * as s from '../schemas';
|
import type * as s from '../schemas';
|
||||||
import type * as t from '../types';
|
import type * as t from '../types';
|
||||||
|
|
@ -129,28 +129,14 @@ export type MemoriesResponse = {
|
||||||
export type PrincipalSearchParams = {
|
export type PrincipalSearchParams = {
|
||||||
q: string;
|
q: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
type?: 'user' | 'group';
|
type?: p.PrincipalType.USER | p.PrincipalType.GROUP | p.PrincipalType.ROLE;
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PrincipalSearchResponse = {
|
export type PrincipalSearchResponse = {
|
||||||
query: string;
|
query: string;
|
||||||
limit: number;
|
limit: number;
|
||||||
type?: 'user' | 'group';
|
type?: p.PrincipalType.USER | p.PrincipalType.GROUP | p.PrincipalType.ROLE;
|
||||||
results: PrincipalSearchResult[];
|
results: p.TPrincipalSearchResult[];
|
||||||
count: number;
|
count: number;
|
||||||
sources: {
|
sources: {
|
||||||
local: number;
|
local: number;
|
||||||
|
|
@ -159,7 +145,7 @@ export type PrincipalSearchResponse = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AccessRole = {
|
export type AccessRole = {
|
||||||
accessRoleId: AccessRoleIds;
|
accessRoleId: p.AccessRoleIds;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
permBits: number;
|
permBits: number;
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ import type { Document, Types } from 'mongoose';
|
||||||
import { PrincipalType, PrincipalModel, ResourceType } from 'librechat-data-provider';
|
import { PrincipalType, PrincipalModel, ResourceType } from 'librechat-data-provider';
|
||||||
|
|
||||||
export type AclEntry = {
|
export type AclEntry = {
|
||||||
/** The type of principal ('user', 'group', 'public') */
|
/** The type of principal (PrincipalType.USER, PrincipalType.GROUP, PrincipalType.PUBLIC) */
|
||||||
principalType: PrincipalType;
|
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;
|
principalId?: Types.ObjectId | string;
|
||||||
/** The model name for the principal ('User' or 'Group') */
|
/** The model name for the principal (`PrincipalModel`) */
|
||||||
principalModel?: PrincipalModel;
|
principalModel?: PrincipalModel;
|
||||||
/** The type of resource ('agent', 'project', 'file', 'promptGroup') */
|
/** The type of resource (`ResourceType`) */
|
||||||
resourceType: ResourceType;
|
resourceType: ResourceType;
|
||||||
/** The ID of the resource */
|
/** The ID of the resource */
|
||||||
resourceId: Types.ObjectId;
|
resourceId: Types.ObjectId;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue