{"record":{"id":"6dca132552069ba7","repo":"RocketChat/Rocket.Chat","slug":"error-not-allowed-6dca13","errorCode":"error-not-allowed","errorMessage":"error-not-allowed","messagePattern":"error-not-allowed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/api/v1/e2e.ts","lineNumber":451,"sourceCode":"\t\t\t\t200: ajv.compile<void>({\n\t\t\t\t\ttype: 'object',\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\n\t\tasync function action() {\n\t\t\tconst { rid, e2eKey, e2eKeyId } = this.bodyParams;\n\t\t\tif (!(await hasPermissionAsync(this.user, 'toggle-room-e2e-encryption', rid))) {\n\t\t\t\treturn API.v1.forbidden('error-not-allowed');\n\t\t\t}\n\t\t\tif (LockMap.has(rid)) {\n\t\t\t\tthrow new Error('error-e2e-key-reset-in-progress');\n\t\t\t}\n\n\t\t\tLockMap.set(rid, true);\n\n\t\t\tif (!(await canAccessRoomIdAsync(rid, this.userId))) {\n\t\t\t\tthrow new Error('error-not-allowed');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait resetRoomKey(rid, this.userId, e2eKey, e2eKeyId);\n\t\t\t\treturn API.v1.success();\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(e);\n\t\t\t\treturn API.v1.failure('error-e2e-key-reset-failed');\n\t\t\t} finally {\n\t\t\t\tLockMap.delete(rid);\n\t\t\t}\n\t\t},\n\t)\n\t.post(\n\t\t'e2e.setUserPublicAndPrivateKeys',\n\t\t{\n\t\t\tauthRequired: true,\n\t\t\tbody: ise2eSetUserPublicAndPrivateKeysParamsPOST,","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/server/api/v1/e2e.ts#L433-L469","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (apps/meteor/server/api/v1/e2e.ts)\nif (LockMap.has(rid)) { throw new Error('error-e2e-key-reset-in-progress'); }\nLockMap.set(rid, true);\nif (!(await canAccessRoomIdAsync(rid, this.userId))) { throw new Error('error-not-allowed'); } // leaks lock\ntry { await resetRoomKey(...); } finally { LockMap.delete(rid); }\n\n// after\nif (LockMap.has(rid)) { throw new Error('error-e2e-key-reset-in-progress'); }\nif (!(await canAccessRoomIdAsync(rid, this.userId))) { throw new Error('error-not-allowed'); }\nLockMap.set(rid, true);\ntry { await resetRoomKey(...); } finally { LockMap.delete(rid); }","handlingStrategy":"validation","validationCode":"// ensure the caller is a room member BEFORE calling resetRoomKey\nif (!(await canAccessRoomId(rid, userId))) { /* refuse locally; do not call */ }","typeGuard":null,"tryCatchPattern":"try { await resetRoomKey({ rid, e2eKey, e2eKeyId }); }\ncatch (e) {\n  if (e?.message === 'error-not-allowed') { /* access denied; AND warn: this call may have leaked the server lock */) }\n  else throw e;\n}","preventionTips":["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."],"tags":["e2e","permissions","concurrency","bug","lock-leak","rest-api"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}