{"record":{"id":"90d0b3535094f5e5","repo":"RocketChat/Rocket.Chat","slug":"error-action-not-allowed-90d0b3","errorCode":"error-action-not-allowed","errorMessage":"Assign roles is not allowed","messagePattern":"Assign roles is not allowed","errorType":"exception","errorClass":"MeteorError","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/users/saveUser/validateUserEditing.ts","lineNumber":45,"sourceCode":"/**\n * Validate permissions to edit user fields\n *\n * @param {string} userId\n * @param {{ _id: string, roles?: string[], username?: string, name?: string, statusText?: string, email?: string, password?: string}} userData\n */\nexport async function validateUserEditing(userId: IUser['_id'], userData: UpdateUserData): Promise<void> {\n\tconst editingMyself = userData._id && userId === userData._id;\n\n\tconst canEditOtherUserInfo = await hasPermissionAsync(userId, 'edit-other-user-info');\n\tconst canEditOtherUserPassword = await hasPermissionAsync(userId, 'edit-other-user-password');\n\tconst user = await Users.findOneById(userData._id);\n\n\tif (!user) {\n\t\tthrow new MeteorError('error-invalid-user', 'Invalid user');\n\t}\n\n\tif (isEditingUserRoles(user.roles, userData.roles) && !(await hasPermissionAsync(userId, 'assign-roles'))) {\n\t\tthrow new MeteorError('error-action-not-allowed', 'Assign roles is not allowed', {\n\t\t\tmethod: 'insertOrUpdateUser',\n\t\t\taction: 'Assign_role',\n\t\t});\n\t}\n\n\tif (!settings.get('Accounts_AllowUserProfileChange') && !canEditOtherUserInfo && !canEditOtherUserPassword) {\n\t\tthrow new MeteorError('error-action-not-allowed', 'Edit user profile is not allowed', {\n\t\t\tmethod: 'insertOrUpdateUser',\n\t\t\taction: 'Update_user',\n\t\t});\n\t}\n\n\tif (\n\t\tisEditingField(user.username, userData.username) &&\n\t\t!settings.get('Accounts_AllowUsernameChange') &&\n\t\t(editingMyself ? user.username : !canEditOtherUserInfo)\n\t) {\n\t\tthrow new MeteorError('error-action-not-allowed', 'Edit username is not allowed', {","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/users/saveUser/validateUserEditing.ts#L27-L63","documentation":"Thrown by validateUserEditing() when the incoming roles array differs from the user's current roles (isEditingUserRoles detects ANY add or remove via two-way diff) and the caller lacks the 'assign-roles' permission. Note the diff is symmetric: REMOVING a role also counts as editing roles, not just adding. Distinct from the admin-specific gate (1104) which additionally requires assign-admin-role for the admin role.","triggerScenarios":"users.update with a roles array that adds or drops any role while the caller (even one with edit-other-user-info) lacks assign-roles; syncing group memberships from an IdP where the computed role list differs by one entry; a form that always sends the full role array, triggering the diff unintentionally.","commonSituations":"Custom 'user manager' roles granted edit-other-user-info but not assign-roles; client resubmitting the roles it received in users.info even when the admin only meant to change a profile field; role pruning during offboarding done by a script with an under-privileged token.","solutions":["Grant 'assign-roles' to the caller's role if role management is intended for them.","Do not send the roles field at all when only profile fields change — omitting it skips the diff entirely (isEditingUserRoles returns false when roles is undefined).","Diff the payload against GET /api/v1/users.info beforehand so the roles array is only sent when a change is truly required and permitted."],"exampleFix":"// before: form echoes roles back, caller has no assign-roles\nawait POST '/api/v1/users.update', { userId, data: { name: 'Bob', roles: ['user', 'bot'] } }); // throws\n\n// after: profile-only update omits roles\nawait POST '/api/v1/users.update', { userId, data: { name: 'Bob' } });","handlingStrategy":"validation","validationCode":"const { userinfo } = await GET `/api/v1/users.info?userId=${encodeURIComponent(payload._id)}`;\nconst rolesChanged = payload.roles !== undefined &&\n  (payload.roles.some((r) => !userinfo.roles.includes(r)) || userinfo.roles.some((r) => !payload.roles!.includes(r)));\nif (rolesChanged && !(await hasPermission('assign-roles'))) {\n  delete payload.roles; // profile-only edit\n}","typeGuard":"const isRoleEdit = (prev: string[], next?: string[]) =>\n  next !== undefined && (next.some((r) => !prev.includes(r)) || prev.some((r) => !next.includes(r)));","tryCatchPattern":"catch (e) {\n  if (e.error === 'error-action-not-allowed' && e.details?.action === 'Assign_role' && /Assign roles/i.test(e.reason)) {\n    // omit roles and resubmit, or escalate to a role that has assign-roles\n  }\n}","preventionTips":["Only include roles in the payload when deliberately changing them.","Remember removal counts as editing roles too.","Give user-manager roles assign-roles if that is their job."],"tags":["users","roles","permissions","update","save-user"],"backgroundTag":"permission-denied","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}