RocketChat/Rocket.Chat · error · MeteorError

error-action-not-allowed

error-action-not-allowed

Error message

Editing email verification is not allowed

What it means

Thrown by validateUserData() during saveUser (reached via POST /api/v1/users.update or the admin Users screen) when a caller submits the 'verified' flag for their OWN account. The server treats self-service email verification as a security bypass: letting a user mark their own email verified defeats verification, so the payload is rejected outright with code error-action-not-allowed. Only users whose _id differs from the edited user's _id (i.e. admins acting on others) may set this flag.

Source

Thrown at apps/meteor/server/lib/users/saveUser/validateUserData.ts:19

import { MeteorError } from '@rocket.chat/core-services';
import type { IUser } from '@rocket.chat/core-typings';
import { makeFunction } from '@rocket.chat/patch-injection';
import escape from 'lodash.escape';

import type { SaveUserData } from './saveUser';
import { isUpdateUserData } from './saveUser';
import { trim } from '../../../../lib/utils/stringUtils';
import { settings } from '../../../settings';
import { getRoleIds } from '../../authorization/getRoles';
import { hasPermissionAsync } from '../../authorization/hasPermission';
import { checkEmailAvailability } from '../checkEmailAvailability';
import { checkUsernameAvailability } from '../checkUsernameAvailability';

export const validateUserData = makeFunction(async (userId: IUser['_id'], userData: SaveUserData): Promise<void> => {
	const existingRoles = await getRoleIds();

	if (userData.verified && userData._id && userId === userData._id) {
		throw new MeteorError('error-action-not-allowed', 'Editing email verification is not allowed', {
			method: 'insertOrUpdateUser',
			action: 'Editing_user',
		});
	}

	if (isUpdateUserData(userData) && userId !== userData._id && !(await hasPermissionAsync(userId, 'edit-other-user-info'))) {
		throw new MeteorError('error-action-not-allowed', 'Editing user is not allowed', {
			method: 'insertOrUpdateUser',
			action: 'Editing_user',
		});
	}

	if (!isUpdateUserData(userData) && !(await hasPermissionAsync(userId, 'create-user'))) {
		throw new MeteorError('error-action-not-allowed', 'Adding user is not allowed', {
			method: 'insertOrUpdateUser',
			action: 'Adding_user',
		});
	}

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Remove 'verified' from the payload when the caller edits their own account (the check is userId === userData._id, so self-edits must never carry it).
  2. If self-service verification is the goal, use the proper verification flow (send a confirmation email via the sendVerificationEmail API) instead of setting the flag.
  3. If an admin legitimately needs to set another user's verified flag, ensure the payload targets a different _id and the caller holds edit-other-user-info (enforced by the next check in the same file).

Example fix

// before (self edit -> throws)
await POST '/api/v1/users.update', { userId: myId, data: { verified: true, name: 'New Name' } };

// after (self edit: omit verified; verify via email flow instead)
await POST '/api/v1/users.update', { userId: myId, data: { name: 'New Name' } };
await POST '/api/v1/users.sendVerificationEmail';
Defensive patterns

Strategy: validation

Validate before calling

const editingSelf = payload._id === currentUserId;
if (editingSelf) {
  delete payload.verified; // never self-send the verified flag
}

Type guard

const isSafeSelfEdit = (p: { _id?: string; verified?: boolean }, uid: string) =>
  !(p.verified && p._id && p._id === uid);

Try / catch

catch (e) {
  if (e.error === 'error-action-not-allowed' && /email verification/i.test(e.reason)) {
    // strip 'verified' and resubmit without it
  }
}

Prevention

When it happens

Trigger: Calling users.update with bodyParams containing both a truthy 'verified' field and an '_id' equal to the authenticated caller's userId. Example: a logged-in user editing their own profile through a custom client that mirrors the full admin form payload, including the verified checkbox.

Common situations: Custom profile-edit forms that reuse the admin user-edit payload verbatim; federation/migration scripts that copy the whole user document; a UI bug that serializes a false-but-present 'verified' field (truthiness check means 'verified: true' is the trigger; the field must simply not be sent).

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/6a1754d3e2966a4f. Report an issue: GitHub.