RocketChat/Rocket.Chat · error · MeteorError
error-action-not-allowed
error-action-not-allowed
Error message
Assign roles is not allowed
What it means
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.
Source
Thrown at apps/meteor/server/lib/users/saveUser/validateUserEditing.ts:45
/**
* Validate permissions to edit user fields
*
* @param {string} userId
* @param {{ _id: string, roles?: string[], username?: string, name?: string, statusText?: string, email?: string, password?: string}} userData
*/
export async function validateUserEditing(userId: IUser['_id'], userData: UpdateUserData): Promise<void> {
const editingMyself = userData._id && userId === userData._id;
const canEditOtherUserInfo = await hasPermissionAsync(userId, 'edit-other-user-info');
const canEditOtherUserPassword = await hasPermissionAsync(userId, 'edit-other-user-password');
const user = await Users.findOneById(userData._id);
if (!user) {
throw new MeteorError('error-invalid-user', 'Invalid user');
}
if (isEditingUserRoles(user.roles, userData.roles) && !(await hasPermissionAsync(userId, 'assign-roles'))) {
throw new MeteorError('error-action-not-allowed', 'Assign roles is not allowed', {
method: 'insertOrUpdateUser',
action: 'Assign_role',
});
}
if (!settings.get('Accounts_AllowUserProfileChange') && !canEditOtherUserInfo && !canEditOtherUserPassword) {
throw new MeteorError('error-action-not-allowed', 'Edit user profile is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}
if (
isEditingField(user.username, userData.username) &&
!settings.get('Accounts_AllowUsernameChange') &&
(editingMyself ? user.username : !canEditOtherUserInfo)
) {
throw new MeteorError('error-action-not-allowed', 'Edit username is not allowed', {View on GitHub (pinned to b2c16d5842)
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.
Example fix
// before: form echoes roles back, caller has no assign-roles
await POST '/api/v1/users.update', { userId, data: { name: 'Bob', roles: ['user', 'bot'] } }); // throws
// after: profile-only update omits roles
await POST '/api/v1/users.update', { userId, data: { name: 'Bob' } }); Defensive patterns
Strategy: validation
Validate before calling
const { userinfo } = await GET `/api/v1/users.info?userId=${encodeURIComponent(payload._id)}`;
const rolesChanged = payload.roles !== undefined &&
(payload.roles.some((r) => !userinfo.roles.includes(r)) || userinfo.roles.some((r) => !payload.roles!.includes(r)));
if (rolesChanged && !(await hasPermission('assign-roles'))) {
delete payload.roles; // profile-only edit
} Type guard
const isRoleEdit = (prev: string[], next?: string[]) => next !== undefined && (next.some((r) => !prev.includes(r)) || prev.some((r) => !next.includes(r)));
Try / catch
catch (e) {
if (e.error === 'error-action-not-allowed' && e.details?.action === 'Assign_role' && /Assign roles/i.test(e.reason)) {
// omit roles and resubmit, or escalate to a role that has assign-roles
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- error-user-not-found
- error-action-not-allowed
- error-invalid-user
- error-duplicate-role-names-not-allowed
- error-invalid-scope
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/90d0b3535094f5e5.
Report an issue: GitHub.