mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-17 21:26:33 +01:00
* 🛂 fix: Validate `types` query param in people picker access middleware checkPeoplePickerAccess only inspected `req.query.type` (singular), allowing callers to bypass type-specific permission checks by using the `types` (plural) parameter accepted by the controller. Now both `type` and `types` are collected and each requested principal type is validated against the caller's role permissions. * 🛂 refactor: Hoist valid types constant, improve logging, and add edge-case tests - Hoist VALID_PRINCIPAL_TYPES to module-level Set to avoid per-request allocation - Include both `type` and `types` in error log for debuggability - Restore detailed JSDoc documenting per-type permission requirements - Add missing .json() assertion on partial-denial test - Add edge-case tests: all-invalid types, empty string types, PrincipalType.PUBLIC * 🏷️ fix: Align TPrincipalSearchParams with actual controller API The stale type used `type` (singular) but the controller and all callers use `types` (plural array). Aligns with PrincipalSearchParams in types/queries.ts.
106 lines
3.2 KiB
JavaScript
106 lines
3.2 KiB
JavaScript
const { logger } = require('@librechat/data-schemas');
|
|
const { PrincipalType, PermissionTypes, Permissions } = require('librechat-data-provider');
|
|
const { getRoleByName } = require('~/models/Role');
|
|
|
|
const VALID_PRINCIPAL_TYPES = new Set([
|
|
PrincipalType.USER,
|
|
PrincipalType.GROUP,
|
|
PrincipalType.ROLE,
|
|
]);
|
|
|
|
/**
|
|
* Middleware to check if user has permission to access people picker functionality.
|
|
* Validates requested principal types via `type` (singular) and `types` (comma-separated or array)
|
|
* query parameters against the caller's role permissions:
|
|
* - user: requires VIEW_USERS permission
|
|
* - group: requires VIEW_GROUPS permission
|
|
* - role: requires VIEW_ROLES permission
|
|
* - no type filter (mixed search): requires at least one of the above
|
|
*/
|
|
const checkPeoplePickerAccess = async (req, res, next) => {
|
|
try {
|
|
const user = req.user;
|
|
if (!user || !user.role) {
|
|
return res.status(401).json({
|
|
error: 'Unauthorized',
|
|
message: 'Authentication required',
|
|
});
|
|
}
|
|
|
|
const role = await getRoleByName(user.role);
|
|
if (!role || !role.permissions) {
|
|
return res.status(403).json({
|
|
error: 'Forbidden',
|
|
message: 'No permissions configured for user role',
|
|
});
|
|
}
|
|
|
|
const { type, types } = req.query;
|
|
const peoplePickerPerms = role.permissions[PermissionTypes.PEOPLE_PICKER] || {};
|
|
const canViewUsers = peoplePickerPerms[Permissions.VIEW_USERS] === true;
|
|
const canViewGroups = peoplePickerPerms[Permissions.VIEW_GROUPS] === true;
|
|
const canViewRoles = peoplePickerPerms[Permissions.VIEW_ROLES] === true;
|
|
|
|
const permissionChecks = {
|
|
[PrincipalType.USER]: {
|
|
hasPermission: canViewUsers,
|
|
message: 'Insufficient permissions to search for users',
|
|
},
|
|
[PrincipalType.GROUP]: {
|
|
hasPermission: canViewGroups,
|
|
message: 'Insufficient permissions to search for groups',
|
|
},
|
|
[PrincipalType.ROLE]: {
|
|
hasPermission: canViewRoles,
|
|
message: 'Insufficient permissions to search for roles',
|
|
},
|
|
};
|
|
|
|
const requestedTypes = new Set();
|
|
|
|
if (type && VALID_PRINCIPAL_TYPES.has(type)) {
|
|
requestedTypes.add(type);
|
|
}
|
|
|
|
if (types) {
|
|
const typesArray = Array.isArray(types) ? types : types.split(',');
|
|
for (const t of typesArray) {
|
|
if (VALID_PRINCIPAL_TYPES.has(t)) {
|
|
requestedTypes.add(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const requested of requestedTypes) {
|
|
const check = permissionChecks[requested];
|
|
if (!check.hasPermission) {
|
|
return res.status(403).json({
|
|
error: 'Forbidden',
|
|
message: check.message,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (requestedTypes.size === 0 && !canViewUsers && !canViewGroups && !canViewRoles) {
|
|
return res.status(403).json({
|
|
error: 'Forbidden',
|
|
message: 'Insufficient permissions to search for users, groups, or roles',
|
|
});
|
|
}
|
|
|
|
next();
|
|
} catch (error) {
|
|
logger.error(
|
|
`[checkPeoplePickerAccess][${req.user?.id}] error for type=${req.query.type}, types=${req.query.types}`,
|
|
error,
|
|
);
|
|
return res.status(500).json({
|
|
error: 'Internal Server Error',
|
|
message: 'Failed to check permissions',
|
|
});
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
checkPeoplePickerAccess,
|
|
};
|