From 63c2272a4feb890277ed1362b48a3524d0b07165 Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 8 Nov 2026 16:15:39 +0000 Subject: users: a user can't remove its own last key A user managing its own keys (1168) could remove the last, and then neither sign nor log in by key. It can't now: DELETE /auth/keys/{id} of the last answers 409, the rules take a user's change of its own keys only with one left after it, and under a strict policy its own entry signed only with one key at least. One who may manage users still may. --- src/access/envelope.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'src/access/envelope.rs') diff --git a/src/access/envelope.rs b/src/access/envelope.rs index 7cd0083..e99a06e 100644 --- a/src/access/envelope.rs +++ b/src/access/envelope.rs @@ -218,8 +218,8 @@ pub(crate) fn check( } /// Whether `payload`, signed by its user (`user`, enabled), is that user's own entry -/// with only its keys changed from the one at `path` in `doc`: a user manages its own -/// keys, whatever else it may do. +/// with only its keys changed from the one at `path` in `doc`, one left at least: a user +/// manages its own keys, whatever else it may do. fn own_keys_only( doc: &impl ReadDoc, payload: &Payload, @@ -232,6 +232,15 @@ fn own_keys_only( let Ok(Some(mut before)) = store::read_plain(doc, path) else { return false; }; + // One key left at least: a user can't sign, nor log in by key, without one. + if payload + .value + .get("keys") + .and_then(JsonValue::as_object) + .is_none_or(serde_json::Map::is_empty) + { + return false; + } let mut after = payload.value.clone(); for entry in [&mut before, &mut after] { match entry.as_object_mut() { @@ -355,6 +364,10 @@ mod tests { assert!(check(&doc, &signed_as("alice", more), &path(at), None).is_err()); // bob, holding the same key, for alice: not his entry. assert!(check(&doc, &signed_as("bob", rekeyed), &path(at), None).is_err()); + // Her keys all gone: no. + let mut keyless = entry.clone(); + keyless["keys"] = json!({}); + assert!(check(&doc, &signed_as("alice", keyless), &path(at), None).is_err()); } #[test] -- cgit v1.3.1