🔧 refactor: Permission handling for Resource Sharing (#11283)

* 🔧 refactor: permission handling for public sharing

- Updated permission keys from SHARED_GLOBAL to SHARE across various files for consistency.
- Added public access configuration in librechat.example.yaml.
- Adjusted related tests and components to reflect the new permission structure.

* chore: Update default SHARE permission to false

* fix: Update SHARE permissions in tests and implementation

- Added SHARE permission handling for user and admin roles in permissions.spec.ts and permissions.ts.
- Updated expected permissions in tests to reflect new SHARE permission values for various permission types.

* fix: Handle undefined values in PeoplePickerAdminSettings component

- Updated the checked and value props of the Switch component to handle undefined values gracefully by defaulting to false. This ensures consistent behavior when the field value is not set.

* feat: Add CREATE permission handling for prompts and agents

- Introduced CREATE permission for user and admin roles in permissions.spec.ts and permissions.ts.
- Updated expected permissions in tests to include CREATE permission for various permission types.

* 🔧 refactor: Enhance permission handling for sharing dialog usability

* refactor: public sharing permissions for resources

- Added middleware to check SHARE_PUBLIC permissions for agents, prompts, and MCP servers.
- Updated interface configuration in librechat.example.yaml to include public sharing options.
- Enhanced components and hooks to support public sharing functionality.
- Adjusted tests to validate new permission handling for public sharing across various resource types.

* refactor: update Share2Icon styling in GenericGrantAccessDialog

* refactor: update Share2Icon size in GenericGrantAccessDialog for consistency

* refactor: improve layout and styling of Share2Icon in GenericGrantAccessDialog

* refactor: update Share2Icon size in GenericGrantAccessDialog for improved consistency

* chore: remove redundant public sharing option from People Picker

* refactor: add SHARE_PUBLIC permission handling in updateInterfacePermissions tests
This commit is contained in:
Danny Avila 2026-01-10 14:02:56 -05:00 committed by GitHub
parent 083251508e
commit 76e17ba701
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 646 additions and 109 deletions

View file

@ -8,9 +8,10 @@ import { useLocalize } from '~/hooks';
import type { PermissionConfig } from '~/components/ui';
const permissions: PermissionConfig[] = [
{ permission: Permissions.SHARED_GLOBAL, labelKey: 'com_ui_prompts_allow_share' },
{ permission: Permissions.CREATE, labelKey: 'com_ui_prompts_allow_create' },
{ permission: Permissions.USE, labelKey: 'com_ui_prompts_allow_use' },
{ permission: Permissions.CREATE, labelKey: 'com_ui_prompts_allow_create' },
{ permission: Permissions.SHARE, labelKey: 'com_ui_prompts_allow_share' },
{ permission: Permissions.SHARE_PUBLIC, labelKey: 'com_ui_prompts_allow_share_public' },
];
const AdminSettings = () => {

View file

@ -65,7 +65,7 @@ const RightPanel = React.memo(
const editorMode = useRecoilValue(store.promptsEditorMode);
const hasShareAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
permission: Permissions.SHARED_GLOBAL,
permission: Permissions.SHARE,
});
const updateGroupMutation = useUpdatePromptGroup({

View file

@ -16,10 +16,10 @@ const SharePrompt = React.memo(
({ group, disabled }: { group?: TPromptGroup; disabled: boolean }) => {
const { user } = useAuthContext();
// Check if user has permission to share prompts globally
// Check if user has permission to share prompts
const hasAccessToSharePrompts = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
permission: Permissions.SHARED_GLOBAL,
permission: Permissions.SHARE,
});
// Check user's permissions on this specific promptGroup

View file

@ -18,6 +18,7 @@ import {
usePeoplePickerPermissions,
useResourcePermissionState,
useCopyToClipboard,
useCanSharePublic,
useLocalize,
} from '~/hooks';
import UnifiedPeopleSearch from './PeoplePicker/UnifiedPeopleSearch';
@ -33,6 +34,7 @@ export default function GenericGrantAccessDialog({
resourceType,
onGrantAccess,
disabled = false,
buttonClassName,
children,
}: {
resourceDbId?: string | null;
@ -41,15 +43,19 @@ export default function GenericGrantAccessDialog({
resourceType: ResourceType;
onGrantAccess?: (shares: TPrincipal[], isPublic: boolean, publicRole?: AccessRoleIds) => void;
disabled?: boolean;
buttonClassName?: string;
children?: React.ReactNode;
}) {
const localize = useLocalize();
const { showToast } = useToastContext();
const [isModalOpen, setIsModalOpen] = useState(false);
const [isCopying, setIsCopying] = useState(false);
// Use shared hooks
const [isModalOpen, setIsModalOpen] = useState(false);
const canSharePublic = useCanSharePublic(resourceType);
const { hasPeoplePickerAccess, peoplePickerTypeFilter } = usePeoplePickerPermissions();
/** User can use the share dialog if they have people picker access OR can share publicly */
const canUseShareDialog = hasPeoplePickerAccess || canSharePublic;
const {
config,
permissionsData,
@ -65,7 +71,7 @@ export default function GenericGrantAccessDialog({
setPublicRole,
} = useResourcePermissionState(resourceType, resourceDbId, isModalOpen);
// State for unified list of all shares (existing + newly added)
/** State for unified list of all shares (existing + newly added) */
const [allShares, setAllShares] = useState<TPrincipal[]>([]);
const [hasChanges, setHasChanges] = useState(false);
const [defaultPermissionId, setDefaultPermissionId] = useState<AccessRoleIds | undefined>(
@ -88,6 +94,11 @@ export default function GenericGrantAccessDialog({
return null;
}
// Don't render if user has no useful sharing permissions
if (!canUseShareDialog) {
return null;
}
if (!config) {
console.error(`Unsupported resource type: ${resourceType}`);
return null;
@ -238,11 +249,11 @@ export default function GenericGrantAccessDialog({
})}
type="button"
disabled={disabled}
className="h-full"
className={cn('h-9', buttonClassName)}
>
<div className="flex min-w-[32px] items-center justify-center gap-2 text-blue-500">
<span className="flex h-6 w-6 items-center justify-center">
<Share2Icon className="icon-md h-4 w-4" aria-hidden="true" />
<Share2Icon className="icon-md h-4 w-4" />
</span>
{totalCurrentShares > 0 && (
<Label className="cursor-pointer text-sm font-medium text-text-secondary">
@ -332,16 +343,20 @@ export default function GenericGrantAccessDialog({
)}
</div>
<div className="flex border-t border-border-light" />
{canSharePublic && (
<>
<div className="flex border-t border-border-light" />
{/* Public Access Section */}
<PublicSharingToggle
isPublic={isPublic}
publicRole={publicRole}
onPublicToggle={handlePublicToggle}
onPublicRoleChange={handlePublicRoleChange}
resourceType={resourceType}
/>
{/* Public Access Section */}
<PublicSharingToggle
isPublic={isPublic}
publicRole={publicRole}
onPublicToggle={handlePublicToggle}
onPublicRoleChange={handlePublicRoleChange}
resourceType={resourceType}
/>
</>
)}
{/* Footer Actions */}
<div className="flex justify-between pt-4">

View file

@ -57,9 +57,9 @@ const LabelController: React.FC<LabelControllerProps> = ({
render={({ field }) => (
<Switch
{...field}
checked={field.value}
checked={field.value ?? false}
onCheckedChange={field.onChange}
value={field.value.toString()}
value={(field.value ?? false).toString()}
aria-label={label}
/>
)}

View file

@ -6,9 +6,10 @@ import { useLocalize } from '~/hooks';
import type { PermissionConfig } from '~/components/ui';
const permissions: PermissionConfig[] = [
{ permission: Permissions.SHARED_GLOBAL, labelKey: 'com_ui_agents_allow_share' },
{ permission: Permissions.CREATE, labelKey: 'com_ui_agents_allow_create' },
{ permission: Permissions.USE, labelKey: 'com_ui_agents_allow_use' },
{ permission: Permissions.CREATE, labelKey: 'com_ui_agents_allow_create' },
{ permission: Permissions.SHARE, labelKey: 'com_ui_agents_allow_share' },
{ permission: Permissions.SHARE_PUBLIC, labelKey: 'com_ui_agents_allow_share_public' },
];
const AdminSettings = () => {

View file

@ -42,7 +42,7 @@ export default function AgentFooter({
const agent_id = useWatch({ control, name: 'id' });
const hasAccessToShareAgents = useHasAccess({
permissionType: PermissionTypes.AGENTS,
permission: Permissions.SHARED_GLOBAL,
permission: Permissions.SHARE,
});
const { hasPermission, isLoading: permissionsLoading } = useResourcePermissions(
ResourceType.AGENT,

View file

@ -9,6 +9,7 @@ const permissions: PermissionConfig[] = [
{ permission: Permissions.USE, labelKey: 'com_ui_mcp_servers_allow_use' },
{ permission: Permissions.CREATE, labelKey: 'com_ui_mcp_servers_allow_create' },
{ permission: Permissions.SHARE, labelKey: 'com_ui_mcp_servers_allow_share' },
{ permission: Permissions.SHARE_PUBLIC, labelKey: 'com_ui_mcp_servers_allow_share_public' },
];
const MCPAdminSettings = () => {