RocketChat/Rocket.Chat · error · Meteor.Error

error-not-allowed

error-not-allowed

Error message

Not Allowed

What it means

Thrown by POST rooms.mail when the calling user can't be loaded or fails canAccessRoomAsync for the target room. This is the access-control gate after the room is confirmed to exist; it blocks users who exist in the system but have no access to the channel (e.g. a private channel they're not in).

Source

Thrown at apps/meteor/server/api/v1/rooms.ts:1023

			401: validateUnauthorizedErrorResponse,
		},
	},
	async function action() {
		const { rid, type } = this.bodyParams;

		if (!(await hasPermissionAsync(this.user, 'mail-messages', rid))) {
			throw new Meteor.Error('error-action-not-allowed', 'Mailing is not allowed');
		}

		const room = await Rooms.findOneById(rid);
		if (!room) {
			throw new Meteor.Error('error-invalid-room');
		}

		const user = await Users.findOneById(this.userId);

		if (!user || !(await canAccessRoomAsync(room, user))) {
			throw new Meteor.Error('error-not-allowed', 'Not Allowed');
		}

		if (type === 'file') {
			const { dateFrom, dateTo } = this.bodyParams;
			const { format } = this.bodyParams;

			const convertedDateFrom = dateFrom ? new Date(dateFrom) : new Date(0);
			const convertedDateTo = dateTo ? new Date(dateTo) : new Date();
			convertedDateTo.setDate(convertedDateTo.getDate() + 1);

			if (convertedDateFrom > convertedDateTo) {
				throw new Meteor.Error('error-invalid-dates', 'From date cannot be after To date');
			}

			void dataExport.sendFile(
				{
					rid,
					format,

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Only offer the mail action on rooms the user can access (check via rooms.canAccess or by only listing joined rooms).
  2. Re-fetch /me on session resume to confirm the account is still valid.
  3. For private channels, ensure membership before exposing the action.

Example fix

// before
await rest.post('/api/v1/rooms.mail', { rid, type: 'file' });

// after
const accessible = await rest.get(`/api/v1/rooms.info?roomId=${rid}`);
if (!accessible.room || !accessible.room._accessible) {
  notify('You do not have access to this room.');
  return;
}
await rest.post('/api/v1/rooms.mail', { rid, type: 'file' });
Defensive patterns

Strategy: validation

Validate before calling

const accessible = await canAccessRoom(rid, userId);
if (!accessible) throw new Error('User cannot access this room');

Type guard

function canAccess(room: { _accessible?: boolean } | undefined): boolean {
  return !!room?._accessible;
}

Try / catch

try {
  await rest.post('/api/v1/rooms.mail', { rid, type });
} catch (e) {
  if (isMeteorError(e, 'error-not-allowed')) {
    notify('You do not have access to this room.');
  } else throw e;
}

Prevention

When it happens

Trigger: POST /api/v1/rooms.mail where the user was deleted between auth and lookup, or where the room is private/DM and the user is not a member.

Common situations: Trying to mail/export a private channel the user isn't part of; cross-workroom access attempts; user account disabled mid-session.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/245accf3c37f3c66. Report an issue: GitHub.