{"record":{"id":"ed23a8187437e5d0","repo":"danny-avila/LibreChat","slug":"invalid-principal-type-principaltype","errorCode":null,"errorMessage":"Invalid principal type: ${principalType}","messagePattern":"Invalid principal type: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/server/services/PermissionService.js","lineNumber":77,"sourceCode":" * @param {string} params.resourceType - Type of resource (e.g., 'agent')\n * @param {string|mongoose.Types.ObjectId} params.resourceId - The ID of the resource\n * @param {string} params.accessRoleId - The ID of the role (e.g., AccessRoleIds.AGENT_VIEWER, AccessRoleIds.AGENT_EDITOR)\n * @param {string|mongoose.Types.ObjectId} params.grantedBy - User ID granting the permission\n * @param {mongoose.ClientSession} [params.session] - Optional MongoDB session for transactions\n * @returns {Promise<Object>} The created or updated ACL entry\n */\nconst grantPermission = async ({\n  principalType,\n  principalId,\n  resourceType,\n  resourceId,\n  accessRoleId,\n  grantedBy,\n  session,\n}) => {\n  try {\n    if (!Object.values(PrincipalType).includes(principalType)) {\n      throw new Error(`Invalid principal type: ${principalType}`);\n    }\n\n    if (principalType !== PrincipalType.PUBLIC && !principalId) {\n      throw new Error('Principal ID is required for user, group, and role principals');\n    }\n\n    // Validate principalId based on type\n    if (principalId && principalType === PrincipalType.ROLE) {\n      // Role IDs are strings (role names)\n      if (typeof principalId !== 'string' || principalId.trim().length === 0) {\n        throw new Error(`Invalid role ID: ${principalId}`);\n      }\n    } else if (\n      principalType &&\n      principalType !== PrincipalType.PUBLIC &&\n      !mongoose.Types.ObjectId.isValid(principalId)\n    ) {\n      // User and Group IDs must be valid ObjectIds","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/api/server/services/PermissionService.js#L59-L95","documentation":"grantPermission() in PermissionService.js:77 throws this as its first guard when principalType is not a value of the PrincipalType enum (user, group, public, role). It prevents persisting ACL records with an unknown principal category, which would be silently invisible to every authorization check.","triggerScenarios":"A caller passes a principalType not equal to 'user'|'group'|'public'|'role' — e.g. 'team', 'org', an integer, or undefined. Reached before the principalId/resourceId checks, so any malformed principalType surfaces here first.","commonSituations":"Frontend introduced a new share target type without backend enum support. A serialization bug converts the enum value to its index. A refactored call site forgets to pass principalType at all (undefined).","solutions":["Pass principalType as one of PrincipalType.USER, .GROUP, .PUBLIC, or .ROLE.","Type the parameter as PrincipalType in TypeScript so the compiler rejects invalid values.","If you genuinely need a new principal category, extend the PrincipalType enum and the ACL schema first."],"exampleFix":"// before\ngrantPermission({ principalType: 'team', ... });\n// after\nimport { PrincipalType } from 'librechat-data-provider';\ngrantPermission({ principalType: PrincipalType.GROUP, ... });","handlingStrategy":"type-guard","validationCode":"const VALID_PRINCIPAL_TYPES = new Set(Object.values(PrincipalType));\nfunction assertPrincipalType(t) {\n  if (!VALID_PRINCIPAL_TYPES.has(t)) throw new Error(`unsupported principalType: ${t}`);\n}","typeGuard":"const isPrincipalType = (t) => Object.values(PrincipalType).includes(t);","tryCatchPattern":null,"preventionTips":["Type the parameter as PrincipalType in TS so invalid values fail to compile.","Drive the principalType dropdown in the UI directly from the enum values."],"tags":["permissions","acl","validation","enums"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}