RocketChat/Rocket.Chat · error · Error
error-not-allowed
error-not-allowed
Error message
error-not-allowed
What it means
Thrown by POST e2e.resetRoomKey when canAccessRoomIdAsync(rid, userId) returns false: the caller is not a member of the room. CRITICAL DEFECT: the access check runs AFTER 'LockMap.set(rid, true)' but OUTSIDE the try/finally that calls 'LockMap.delete(rid)'. So this throw leaks the lock, and every subsequent resetRoomKey call for that room fails with 'error-e2e-key-reset-in-progress' (error 388) until the server restarts. The permission decision itself is correct; the cleanup is not.
Source
Thrown at apps/meteor/server/api/v1/e2e.ts:451
200: ajv.compile<void>({
type: 'object',
}),
},
},
async function action() {
const { rid, e2eKey, e2eKeyId } = this.bodyParams;
if (!(await hasPermissionAsync(this.user, 'toggle-room-e2e-encryption', rid))) {
return API.v1.forbidden('error-not-allowed');
}
if (LockMap.has(rid)) {
throw new Error('error-e2e-key-reset-in-progress');
}
LockMap.set(rid, true);
if (!(await canAccessRoomIdAsync(rid, this.userId))) {
throw new Error('error-not-allowed');
}
try {
await resetRoomKey(rid, this.userId, e2eKey, e2eKeyId);
return API.v1.success();
} catch (e) {
console.error(e);
return API.v1.failure('error-e2e-key-reset-failed');
} finally {
LockMap.delete(rid);
}
},
)
.post(
'e2e.setUserPublicAndPrivateKeys',
{
authRequired: true,
body: ise2eSetUserPublicAndPrivateKeysParamsPOST,View on GitHub (pinned to f9d3ec372b)
Solutions
- Ensure the caller has room access BEFORE invoking resetRoomKey.
- Patch e2e.ts: move the canAccessRoomIdAsync check ABOVE LockMap.set, or move LockMap.set inside the try so the finally releases it.
- Until patched, restart the server process to clear leaked locks.
- Wrap the access check in its own try/finally that deletes the lock on denial.
Example fix
// before (apps/meteor/server/api/v1/e2e.ts)
if (LockMap.has(rid)) { throw new Error('error-e2e-key-reset-in-progress'); }
LockMap.set(rid, true);
if (!(await canAccessRoomIdAsync(rid, this.userId))) { throw new Error('error-not-allowed'); } // leaks lock
try { await resetRoomKey(...); } finally { LockMap.delete(rid); }
// after
if (LockMap.has(rid)) { throw new Error('error-e2e-key-reset-in-progress'); }
if (!(await canAccessRoomIdAsync(rid, this.userId))) { throw new Error('error-not-allowed'); }
LockMap.set(rid, true);
try { await resetRoomKey(...); } finally { LockMap.delete(rid); } Defensive patterns
Strategy: validation
Validate before calling
// ensure the caller is a room member BEFORE calling resetRoomKey
if (!(await canAccessRoomId(rid, userId))) { /* refuse locally; do not call */ } Try / catch
try { await resetRoomKey({ rid, e2eKey, e2eKeyId }); }
catch (e) {
if (e?.message === 'error-not-allowed') { /* access denied; AND warn: this call may have leaked the server lock */) }
else throw e;
} Prevention
- Never call resetRoomKey without confirmed room access — a denied call can poison the room's lock until restart.
- Patch the server: move the access check above LockMap.set, or move LockMap.set inside the try/finally.
- After any 'error-not-allowed' here, expect 'error-e2e-key-reset-in-progress' on subsequent attempts and restart the server.
When it happens
Trigger: Any user without room membership calls resetRoomKey; bot token lacking access; permission regression. After the first such call, the room's reset capability is poisoned.
Common situations: Token/user without access triggers reset; e2e reset attempted right after a user left the room; load-balanced setup where one server's lock leaks.
Related errors
- error-e2e-key-reset-in-progress
- error-not-authorized
- error-creating-custom-user-status
- error-updating-custom-user-status
- error-roomId-param-invalid
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/6dca132552069ba7.
Report an issue: GitHub.