RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-user

error-invalid-user

Error message

Invalid user

What it means

deleteMessageValidatingPermission (deleteMessage.ts:17-19) requires a truthy userId before performing a delete. Without a user id there is no principal to authorize, so the call fails fast with error-invalid-user 'Invalid user' - before any DB lookup happens.

Source

Thrown at apps/meteor/server/lib/messages/deleteMessage.ts:18

import { AppEvents, Apps } from '@rocket.chat/apps';
import { api, Message } from '@rocket.chat/core-services';
import { isThreadMessage, type AtLeast, type IMessage, type IRoom, type IThreadMessage, type IUser } from '@rocket.chat/core-typings';
import { Messages, Rooms, Uploads, Users, ReadReceipts, ReadReceiptsArchive, Subscriptions } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

import { settings } from '../../settings';
import { canDeleteMessageAsync } from '../authorization/canDeleteMessage';
import { callbacks } from '../callbacks';
import { FileUpload } from '../media/file-upload';
import { notifyOnRoomChangedById, notifyOnMessageChange, notifyOnSubscriptionChangedByRoomIdAndUserIds } from '../notifyListener';

export const deleteMessageValidatingPermission = async (message: AtLeast<IMessage, '_id'>, userId: IUser['_id']): Promise<void> => {
	if (!message?._id) {
		throw new Meteor.Error('error-invalid-message', 'Invalid message');
	}
	if (!userId) {
		throw new Meteor.Error('error-invalid-user', 'Invalid user');
	}

	const user = await Users.findOneById(userId);
	const originalMessage = await Messages.findOneById(message._id);

	if (!originalMessage || !user || !(await canDeleteMessageAsync(user, originalMessage))) {
		throw new Meteor.Error('error-action-not-allowed', 'Not allowed');
	}

	return deleteMessage(originalMessage, user);
};

export async function deleteMessage(message: IMessage, user: IUser): Promise<void> {
	const deletedMsg: IMessage | null = await Messages.findOneById(message._id);
	const isThread = (deletedMsg?.tcount || 0) > 0;
	const keepHistory = settings.get('Message_KeepHistory') || isThread;
	const showDeletedStatus = settings.get('Message_ShowDeletedStatus') || isThread;

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Ensure the call runs inside an authenticated session and forward that userId (this.userId in a Meteor method)
  2. For server-side jobs, resolve the acting user first (e.g. the app/bot owner) and pass its _id explicitly
  3. Re-authenticate the client when the session expired, then retry the delete
  4. Guard the call site: skip deletion when userId is falsy instead of throwing

Example fix

// before
deleteMessageValidatingPermission(message, undefined); // -> error-invalid-user
// after
const userId = this.userId ?? (await resolveBotUserId());
if (userId) await deleteMessageValidatingPermission(message, userId);
Defensive patterns

Strategy: validation

Validate before calling

if (!userId) throw new Error('Deleting requires an authenticated user');
await deleteMessageValidatingPermission(message, userId);

Try / catch

try {
  await deleteMessageValidatingPermission(message, userId);
} catch (error: any) {
  if (error instanceof Meteor.Error && error.error === 'error-invalid-user') {
    await reauthenticate(); // then retry once with a fresh session
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: Invoking the deleteMessage method from an unauthenticated DDP connection; server code calling the helper with undefined userId (e.g. a background job that lost its user context); REST wrappers that drop the authenticated user when forwarding the call.

Common situations: Expired login sessions where the client still queues the delete; custom server modules calling deletion logic outside a request scope; tests that forget to stub the user; federation/app bridges that must pick a user to act as.

Related errors


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