mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-04-07 00:15:23 +02:00
* fix: Resolve custom role permissions not loading in frontend Users assigned to custom roles (non-USER/ADMIN) had all permission checks fail because AuthContext only fetched system role permissions. The roles map keyed by USER/ADMIN never contained the custom role name, so useHasAccess returned false for every feature gate. - Fetch the user's custom role in AuthContext and include it in the roles map so useHasAccess can resolve permissions correctly - Use encodeURIComponent instead of toLowerCase for role name URLs to preserve custom role casing through the API roundtrip - Only uppercase system role names on the backend GET route; pass custom role names through as-is for exact DB lookup - Allow users to fetch their own assigned role without READ_ROLES capability * refactor: Normalize all role names to uppercase Custom role names were stored in original casing, causing case-sensitivity bugs across the stack — URL lowercasing, route uppercasing, and case-sensitive DB lookups all conflicted for mixed-case custom roles. Enforce uppercase normalization at every boundary: - createRoleByName trims and uppercases the name before storage - createRoleHandler uppercases before passing to createRoleByName - All admin route handlers (get, update, delete, members, permissions) uppercase the :name URL param before DB lookups - addRoleMemberHandler uppercases before setting user.role - Startup migration (normalizeRoleNames) finds non-uppercase custom roles, renames them, and updates affected user.role values with collision detection Legacy GET /api/roles/:roleName retains always-uppercase behavior. Tests updated to expect uppercase role names throughout. * fix: Use case-preserved role names with strict equality Remove uppercase normalization — custom role names are stored and compared exactly as the user sets them, with only trimming applied. USER and ADMIN remain reserved case-insensitively via isSystemRoleName. - Remove toUpperCase from createRoleByName, createRoleHandler, and all admin route handlers (get, update, delete, members, permissions) - Remove toUpperCase from legacy GET and PUT routes in roles.js; the frontend now sends exact casing via encodeURIComponent - Remove normalizeRoleNames startup migration - Revert test expectations to original casing * fix: Format useMemo dependency array for Prettier * feat: Add custom role support to admin settings + review fixes - Add backend tests for isOwnRole authorization gate on GET /api/roles/:roleName - Add frontend tests for custom role detection and fetching in AuthContext - Fix transient null permission flash by only spreading custom role once loaded - Add isSystemRoleName helper to data-provider for case-insensitive system role detection - Use sentinel value in useGetRole to avoid ghost cache entry from empty string - Add useListRoles hook and listRoles data service for fetching all roles - Update AdminSettingsDialog and PeoplePickerAdminSettings to dynamically list custom roles in the role dropdown, with proper fallback defaults * fix: Address review findings for custom role permissions - Add assertions to AuthContext test verifying custom role in roles map - Fix empty array bypassing nullish coalescing fallback in role dropdowns - Add null/undefined guard to isSystemRoleName helper - Memoize role dropdown items to avoid unnecessary re-renders - Apply sentinel pattern to useGetRole in admin settings for consistency - Mark ListRolesResponse description as required to match schema * fix: Prevent prototype pollution in role authorization gate - Replace roleDefaults[roleName] with Object.hasOwn to prevent prototype chain bypass for names like constructor or __proto__ - Add dedicated rolesList query key to avoid cache collision when a custom role is named 'list' - Add regression test for prototype property name authorization * fix: Resolve Prettier formatting and unused variable lint errors * fix: Address review findings for custom role permissions - Add ADMIN self-read test documenting isOwnRole bypass behavior - Guard save button while custom role data loads to prevent data loss - Extract useRoleSelector hook eliminating ~55 lines of duplication - Unify defaultValues/useEffect permission resolution (fixes inconsistency) - Make ListRolesResponse.description and _id optional to match schema - Fix vacuous test assertions to verify sentinel calls exist - Only fetch userRole when user.role === USER (avoid unnecessary requests) - Remove redundant empty string guard in custom role detection * fix: Revert USER role fetch restriction to preserve admin settings Admins need the USER role loaded in AuthContext.roles so the admin settings dialog shows persisted USER permissions instead of defaults. * fix: Remove unused useEffect import from useRoleSelector * fix: Clean up useRoleSelector hook - Use existing isCustom variable instead of re-calling isSystemRoleName - Remove unused roles and availableRoleNames from return object * fix: Address review findings for custom role permissions - Use Set-based isSystemRoleName to auto-expand with future SystemRoles - Add isCustomRoleError handling: guard useEffect reset and disable Save - Remove resolvePermissions from hook return; use defaultValues in useEffect to eliminate redundant computation and stale-closure reset race - Rename customRoleName to userRoleName in AuthContext for clarity * fix: Request server-max roles for admin dropdown listRoles now passes limit=200 (the server's MAX_PAGE_LIMIT) so the admin role selector shows all roles instead of silently truncating at the default page size of 50. --------- Co-authored-by: Danny Avila <danny@librechat.ai>
100 lines
3 KiB
TypeScript
100 lines
3 KiB
TypeScript
export enum QueryKeys {
|
|
messages = 'messages',
|
|
sharedMessages = 'sharedMessages',
|
|
sharedLinks = 'sharedLinks',
|
|
allConversations = 'allConversations',
|
|
archivedConversations = 'archivedConversations',
|
|
searchConversations = 'searchConversations',
|
|
conversation = 'conversation',
|
|
searchEnabled = 'searchEnabled',
|
|
user = 'user',
|
|
name = 'name', // user key name
|
|
models = 'models',
|
|
balance = 'balance',
|
|
endpoints = 'endpoints',
|
|
presets = 'presets',
|
|
searchResults = 'searchResults',
|
|
tokenCount = 'tokenCount',
|
|
availablePlugins = 'availablePlugins',
|
|
startupConfig = 'startupConfig',
|
|
assistants = 'assistants',
|
|
assistant = 'assistant',
|
|
agents = 'agents',
|
|
agent = 'agent',
|
|
files = 'files',
|
|
fileConfig = 'fileConfig',
|
|
tools = 'tools',
|
|
toolAuth = 'toolAuth',
|
|
toolCalls = 'toolCalls',
|
|
mcpTools = 'mcpTools',
|
|
mcpConnectionStatus = 'mcpConnectionStatus',
|
|
mcpAuthValues = 'mcpAuthValues',
|
|
agentTools = 'agentTools',
|
|
actions = 'actions',
|
|
assistantDocs = 'assistantDocs',
|
|
agentDocs = 'agentDocs',
|
|
fileDownload = 'fileDownload',
|
|
voices = 'voices',
|
|
customConfigSpeech = 'customConfigSpeech',
|
|
prompts = 'prompts',
|
|
prompt = 'prompt',
|
|
promptGroups = 'promptGroups',
|
|
allPromptGroups = 'allPromptGroups',
|
|
promptGroup = 'promptGroup',
|
|
categories = 'categories',
|
|
randomPrompts = 'randomPrompts',
|
|
agentCategories = 'agentCategories',
|
|
marketplaceAgents = 'marketplaceAgents',
|
|
roles = 'roles',
|
|
rolesList = 'rolesList',
|
|
conversationTags = 'conversationTags',
|
|
health = 'health',
|
|
userTerms = 'userTerms',
|
|
banner = 'banner',
|
|
/* Memories */
|
|
memories = 'memories',
|
|
principalSearch = 'principalSearch',
|
|
accessRoles = 'accessRoles',
|
|
resourcePermissions = 'resourcePermissions',
|
|
effectivePermissions = 'effectivePermissions',
|
|
graphToken = 'graphToken',
|
|
/* MCP Servers */
|
|
mcpServers = 'mcpServers',
|
|
mcpServer = 'mcpServer',
|
|
/* Active Jobs */
|
|
activeJobs = 'activeJobs',
|
|
/* Agent API Keys */
|
|
agentApiKeys = 'agentApiKeys',
|
|
}
|
|
|
|
// Dynamic query keys that require parameters
|
|
export const DynamicQueryKeys = {
|
|
agentFiles: (agentId: string) => ['agentFiles', agentId] as const,
|
|
} as const;
|
|
|
|
export enum MutationKeys {
|
|
createAgentApiKey = 'createAgentApiKey',
|
|
deleteAgentApiKey = 'deleteAgentApiKey',
|
|
fileUpload = 'fileUpload',
|
|
fileDelete = 'fileDelete',
|
|
updatePreset = 'updatePreset',
|
|
deletePreset = 'deletePreset',
|
|
loginUser = 'loginUser',
|
|
logoutUser = 'logoutUser',
|
|
refreshToken = 'refreshToken',
|
|
avatarUpload = 'avatarUpload',
|
|
speechToText = 'speechToText',
|
|
textToSpeech = 'textToSpeech',
|
|
assistantAvatarUpload = 'assistantAvatarUpload',
|
|
agentAvatarUpload = 'agentAvatarUpload',
|
|
updateAction = 'updateAction',
|
|
updateAgentAction = 'updateAgentAction',
|
|
deleteAction = 'deleteAction',
|
|
deleteAgentAction = 'deleteAgentAction',
|
|
revertAgentVersion = 'revertAgentVersion',
|
|
deleteUser = 'deleteUser',
|
|
updateRole = 'updateRole',
|
|
enableTwoFactor = 'enableTwoFactor',
|
|
verifyTwoFactor = 'verifyTwoFactor',
|
|
updateMemoryPreferences = 'updateMemoryPreferences',
|
|
}
|