{"record":{"id":"aa60a1cf80cabfd7","repo":"danny-avila/LibreChat","slug":"principal-id-is-required-for-user-group-and-role-aa60a1","errorCode":null,"errorMessage":"Principal ID is required for user, group, and role principals","messagePattern":"Principal ID is required for user, group, and role principals","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/api/src/acl/accessControlService.ts","lineNumber":66,"sourceCode":"    expiredAt?: Date;\n  }): Promise<IAclEntry | null> {\n    const {\n      principalType,\n      principalId,\n      resourceType,\n      resourceId,\n      accessRoleId,\n      grantedBy,\n      session,\n      expiredAt,\n    } = args;\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        (!principalId || !Types.ObjectId.isValid(principalId))\n      ) {\n        // User and Group IDs must be valid ObjectIds\n        throw new Error(`Invalid principal ID: ${principalId}`);\n      }\n\n      if (!resourceId || !Types.ObjectId.isValid(resourceId)) {","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/packages/api/src/acl/accessControlService.ts#L48-L84","documentation":"grantPermission requires a non-empty principalId for every principalType except PUBLIC. The check fires when principalType is USER, GROUP, or ROLE but principalId is null, undefined, or otherwise falsy. PUBLIC is the only principal that may omit it.","triggerScenarios":"grantPermission called with principalType: PrincipalType.USER and principalId omitted/null/undefined; passing PrincipalType.PUBLIC's id-less shape to a USER grant; a form or API payload where the user/group selector returned no selection.","commonSituations":"UI bug where the user picker was skipped; a migration script that iterates principals but encounters a null foreign key; misusing a PUBLIC grant flow for a specific user.","solutions":["Ensure the caller resolves and passes principalId for USER/GROUP/ROLE grants.","If you genuinely want everyone, switch principalType to PrincipalType.PUBLIC (no id).","Add a precondition check in the route handler so the request returns 400 before reaching the service."],"exampleFix":"// before\nawait grantPermission({ principalType: PrincipalType.USER, principalId: null, ... });\n\n// after\nawait grantPermission({ principalType: PrincipalType.PUBLIC, ... });\n// or\nawait grantPermission({ principalType: PrincipalType.USER, principalId: resolvedUserId, ... });","handlingStrategy":"validation","validationCode":"import { PrincipalType } from 'librechat-data-provider';\n\nfunction resolvePrincipalArgs(p: { principalType: PrincipalType; principalId?: string | null }) {\n  if (p.principalType !== PrincipalType.PUBLIC && !p.principalId) {\n    throw new Error('principalId is required for non-PUBLIC principals');\n  }\n  return p;\n}","typeGuard":"import { PrincipalType } from 'librechat-data-provider';\nconst needsPrincipalId = (t: PrincipalType): boolean => t !== PrincipalType.PUBLIC;","tryCatchPattern":null,"preventionTips":["Resolve principalId in the route handler and return 400 if it is missing for USER/GROUP/ROLE.","Use PrincipalType.PUBLIC explicitly when you intend everyone."],"tags":["acl","validation","permissions","input-validation"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}