RocketChat/Rocket.Chat · error · Meteor.Error
error-not-authorized
error-not-authorized
Error message
Not authorized
What it means
Thrown when hasPermissionAsync(userId, 'archive-room', room._id) returns false: the user is valid, the room exists and its type allows archiving, but this user lacks the archive-room permission for that specific room. The permission is evaluated with the room id as scope, so both global role grants and room-scoped overrides apply.
Source
Thrown at apps/meteor/server/meteor-methods/rooms/archiveRoom.ts:38
export const executeArchiveRoom = async (userId: string, rid: string) => {
check(rid, String);
const user = await Users.findOneById(userId, { projection: { username: 1, name: 1 } });
if (!user || !isRegisterUser(user)) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'archiveRoom' });
}
const room = await Rooms.findOneById(rid);
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'archiveRoom' });
}
if (!(await roomCoordinator.getRoomDirectives(room.t).allowMemberAction(room, RoomMemberActions.ARCHIVE, userId))) {
throw new Meteor.Error('error-direct-message-room', `rooms type: ${room.t} can not be archived`, { method: 'archiveRoom' });
}
if (!(await hasPermissionAsync(userId, 'archive-room', room._id))) {
throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'archiveRoom' });
}
return archiveRoom(rid, user);
};
Meteor.methods<ServerMethods>({
async archiveRoom(rid) {
methodDeprecationLogger.method('archiveRoom', '9.0.0', '/v1/channels.archive');
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'archiveRoom' });
}
return executeArchiveRoom(userId, rid);
},
});
View on GitHub (pinned to b2c16d5842)
Solutions
- Grant archive-room to the acting user's role (Administration > Permissions), globally or scoped to the room.
- Have a user who holds the permission (room owner or admin) perform the archive.
- Gate the archive action client-side on the same permission so the method is never called without it.
Example fix
// before
Meteor.call('archiveRoom', rid);
// after
if (hasPermission(Meteor.userId(), 'archive-room', rid)) {
Meteor.call('archiveRoom', rid);
} else {
showToast('Not authorized to archive this room');
} Defensive patterns
Strategy: try-catch
Validate before calling
// with the roles subscription active, mirror the server check:
if (!hasPermission(Meteor.userId(), 'archive-room', rid)) {
// hide/disable the archive action instead of calling the method
} Try / catch
try {
await Meteor.callAsync('archiveRoom', rid);
} catch (e) {
if (e instanceof Meteor.Error && e.error === 'error-not-authorized') {
showToast('You are not allowed to archive this room');
return; // do not retry - a permission will not appear by retrying
}
throw e;
} Prevention
- Mirror the server permission name ('archive-room') in client UI gating, including room scope.
- Run archive automations under a role that always holds archive-room.
- After permission changes, refresh the roles/authorization subscription before trusting client checks.
When it happens
Trigger: A plain member (not owner/moderator) calls archiveRoom; the archive-room permission was revoked from the user's role after the UI rendered; room-scoped permission settings deny the caller.
Common situations: Workspace policies limiting archive to owners/moderators; permission changes not propagated to the client UI; custom roles created without archive-room.
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/00e7a3527ddff01e.
Report an issue: GitHub.