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

  1. Grant 'assign-roles' to the caller's role if role management is intended for them.
  2. 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).
  3. 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

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


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/90d0b3535094f5e5. Report an issue: GitHub.