From 83a1543483d96ce00d925e702ec03ae9d2d39de0 Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Tue, 19 May 2026 20:06:09 +0200 Subject: [PATCH] Fix ACL: Orphaned ACL entries for deleted accounts cause JMAP session errors --- CHANGELOG.md | 3 +- crates/jmap/src/api/session.rs | 13 ++++++- crates/store/src/dispatch/store.rs | 54 ++++++++++++++++++------------ 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 684f0646..b461ae14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If - DNS updater: - Skip `v=spf1 a -all` records for apex domains. - RFC2136 TSIG: regression related to multiplexer. - - Rout53: Chunk `TXT` records when they exceed 255 characters. + - Route53: Chunk `TXT` records when they exceed 255 characters. - ACME: - Update `defaultCertificateId` when renewing a certificate that is currently set as default. - Perform `DNS-01` authorizations sequentially to avoid race conditions in some DNS providers. @@ -31,6 +31,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If - Websocket: Perform case insensitive matching during upgrade. - LDAP: Synchronize accounts when expanding mailing list recipients. - Sieve: `replace` action adds an extra `From` header. +- ACL: Orphaned ACL entries for deleted accounts cause JMAP session errors. ## [0.16.5] - 2026-05-11 diff --git a/crates/jmap/src/api/session.rs b/crates/jmap/src/api/session.rs index e7d0bba6..d62b8c7b 100644 --- a/crates/jmap/src/api/session.rs +++ b/crates/jmap/src/api/session.rs @@ -60,7 +60,18 @@ impl SessionHandler for Server { // Add secondary accounts for &account_id in access_token.secondary_ids() { let is_owner = access_token.is_member(account_id); - let account = self.account(account_id).await.caused_by(trc::location!())?; + let Some(account) = self + .try_account(account_id) + .await + .caused_by(trc::location!())? + else { + trc::event!( + Auth(trc::AuthEvent::Warning), + AccountId = account_id, + Reason = "Skipping orphan secondary account id in session", + ); + continue; + }; let account_id = Id::from(account_id); let mut account = Account { diff --git a/crates/store/src/dispatch/store.rs b/crates/store/src/dispatch/store.rs index a5d898e2..300ec7f0 100644 --- a/crates/store/src/dispatch/store.rs +++ b/crates/store/src/dispatch/store.rs @@ -357,27 +357,39 @@ impl Store { .caused_by(trc::location!())?; } - for (from_class, to_class) in [ - (ValueClass::Acl(account_id), ValueClass::Acl(account_id + 1)), - (ValueClass::Property(0), ValueClass::Property(0)), - ] { - self.delete_range( - ValueKey { - account_id, - collection: 0, - document_id: 0, - class: from_class, - }, - ValueKey { - account_id: account_id + 1, - collection: 0, - document_id: 0, - class: to_class, - }, - ) - .await - .caused_by(trc::location!())?; - } + self.delete_range( + ValueKey { + account_id: 0, + collection: 0, + document_id: 0, + class: ValueClass::Acl(account_id), + }, + ValueKey { + account_id: 0, + collection: 0, + document_id: 0, + class: ValueClass::Acl(account_id + 1), + }, + ) + .await + .caused_by(trc::location!())?; + + self.delete_range( + ValueKey { + account_id, + collection: 0, + document_id: 0, + class: ValueClass::Property(0), + }, + ValueKey { + account_id: account_id + 1, + collection: 0, + document_id: 0, + class: ValueClass::Property(0), + }, + ) + .await + .caused_by(trc::location!())?; Ok(()) }