RocketChat/Rocket.Chat · error · Meteor.Error

error-not-allowed

error-not-allowed

Error message

Not allowed

What it means

After fetching the requested messages, getMessages maps every distinct room through canAccessRoomIdAsync; if ANY room is inaccessible it throws 'error-not-allowed' (note: the thrown details say method 'getSingleMessage' — a copy-paste artifact in the source). One inaccessible message rejects the whole batch, including all accessible messages.

Source

Thrown at apps/meteor/server/meteor-methods/messages/getMessages.ts:29

	interface ServerMethods {
		getMessages(messages: IMessage['_id'][]): Promise<IMessage[]>;
	}
}

Meteor.methods<ServerMethods>({
	async getMessages(messages) {
		check(messages, [String]);
		const uid = Meteor.userId();

		if (!uid) {
			throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getMessages' });
		}

		const msgs = await Messages.findVisibleByIds(messages).toArray();
		const rids = await Promise.all([...new Set(msgs.map((m) => m.rid))].map((_id) => canAccessRoomIdAsync(_id, uid)));

		if (!rids.every(Boolean)) {
			throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getSingleMessage' });
		}

		return msgs;
	},
});

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Pre-filter ids: only request messages the client knows belong to rooms the user is subscribed to
  2. Split the request into per-room or smaller batches so one bad id does not sink the rest
  3. Catch 'error-not-allowed' and degrade to per-message lookups (getSingleMessage), skipping the inaccessible ones

Example fix

// before
const msgs = await Meteor.callAsync('getMessages', ids);

// after — fall back to per-id fetch on batch rejection
try {
  msgs = await Meteor.callAsync('getMessages', ids);
} catch (e) {
  if (e.error !== 'error-not-allowed') throw e;
  msgs = (await Promise.allSettled(
    ids.map((id) => Meteor.callAsync('getSingleMessage', id)),
  )).filter((r) => r.status === 'fulfilled').map((r) => r.value);
}
Defensive patterns

Strategy: fallback

Try / catch

async function getMessagesSafe(ids: string[]) {
  try {
    return await Meteor.callAsync('getMessages', ids);
  } catch (e) {
    if ((e as Meteor.Error).error !== 'error-not-allowed') throw e;
    // one bad room poisons the batch: degrade to per-id fetch, skip failures
    const results = await Promise.allSettled(
      ids.map((id) => Meteor.callAsync('getSingleMessage', id)),
    );
    return results
      .filter((r): r is PromiseFulfilledResult<any> => r.status === 'fulfilled')
      .map((r) => r.value)
      .filter(Boolean);
  }
}

Prevention

When it happens

Trigger: Meteor.call('getMessages', ids) where at least one id belongs to a private room or DM the caller cannot access — e.g. quoted messages from channels the user was removed from, or ids harvested from search results or links.

Common situations: Resolving quoted-message previews across mixed rooms; users removed from channels retaining old references; ids aggregated from multiple sources into one request.

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