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

  1. Ensure the caller has room access BEFORE invoking resetRoomKey.
  2. Patch e2e.ts: move the canAccessRoomIdAsync check ABOVE LockMap.set, or move LockMap.set inside the try so the finally releases it.
  3. Until patched, restart the server process to clear leaked locks.
  4. 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

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


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