From 8a6ed7cc711e797542a531091b3bdbc73e5f61e4 Mon Sep 17 00:00:00 2001 From: tv Date: Mon, 9 Nov 2026 02:24:51 +0000 Subject: dashboard: keys edited, the last removed with a password, failures kept The credentials dialog listed a user's keys whole, to add one or remove one but the last, and any failure replaced the dialog with the error, the form lost. * A key is shown short: its type, the first and last four characters of what tells it apart (an Ed25519 key's base64 after the part all of them share), its comment; the whole line in its tooltip. * A key can be edited, its name and its line: saved under the name, a new name written first and the old one removed. * A user with a password on this node (`--htpasswd`) may remove its last key: it logs in by the password. GET /auth/keys says so (`password`), and DELETE /auth/keys/{id} takes it then; the rules no longer keep the last key on every node (no other node can tell who has a password where; under a strict policy the signed entry keeps one still). * What fails says why beside where it was asked (the add form, the edited row, the removed one), which stays as it was, to be set right and tried again. --- src/api/users.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'src/api/users.rs') diff --git a/src/api/users.rs b/src/api/users.rs index 1dda33b..d6950af 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -24,8 +24,10 @@ //! * `GET /auth/keys`: the caller's keys, its entry, and the envelope holding it under a //! strict policy (`signed`), to sign the next one against. //! * `PUT /auth/keys/{id}`: one added or replaced (the line as the body); -//! `DELETE /auth/keys/{id}` removes one. Under a strict policy `409`: the user signs -//! its entry with the keys changed, nothing else (`continuum-signed key-add`). +//! `DELETE /auth/keys/{id}` removes one, the last only of a user with a password on +//! this node (`--htpasswd`, `password` in `GET /auth/keys`), which logs in by it. Under +//! a strict policy `409`: the user signs its entry with the keys changed, nothing else +//! (`continuum-signed key-add`). //! //! Looking needs `read` on `_continuum/users`, changing `write` on it (in a swarm //! without a registry, being in `--protocol-writers`). Every change is one change to @@ -142,6 +144,7 @@ fn shown(doc: &impl crate::document::ReadDoc, name: &str) -> Option { pub(crate) async fn own_keys( State(state): State, Extension(user): Extension, + Extension(auth): Extension, ) -> Result, ApiError> { let replica = state.doc.read().await; let path = registry::user_path(&user.0); @@ -157,6 +160,7 @@ pub(crate) async fn own_keys( "keys": entry.get("keys").cloned().unwrap_or_else(|| json!({})), "entry": entry, "signed": signed, + "password": auth.has_password(&user.0), }))) } @@ -168,24 +172,28 @@ pub(crate) async fn put_own_key( body: String, ) -> Result { let key = crate::access::sshsig::PublicKey::parse(body.trim()).map_err(|why| bad(&why))?; - own_key(&state, &user.0, id, Some(key.line())).await + own_key(&state, &user.0, id, Some(key.line()), false).await } /// `DELETE /auth/keys/{id}`. pub(crate) async fn delete_own_key( State(state): State, Extension(user): Extension, + Extension(auth): Extension, Path(id): Path, ) -> Result { - own_key(&state, &user.0, id, None).await + let password = auth.has_password(&user.0); + own_key(&state, &user.0, id, None, password).await } -/// `user`'s key `id` set to `line`, or removed: one change, as `user`. +/// `user`'s key `id` set to `line`, or removed: one change, as `user`. Its last key only +/// with `password` (it has one here, and logs in by it). async fn own_key( state: &AppState, user: &str, id: String, line: Option, + password: bool, ) -> Result { if !registry::valid_name(&id) { return Err(bad("not a key's name: 1 to 64 of A-Z a-z 0-9 . _ @ -")); @@ -216,7 +224,7 @@ async fn own_key( Some("no entry of yours".to_owned()), )); } - if line.is_none() { + if line.is_none() && !password { let mut keys = entry.clone(); keys.push("keys".to_owned()); let others = store::keys_at(&*doc, &keys)? @@ -227,7 +235,11 @@ async fn own_key( if others == 0 { return Err(ApiError( StatusCode::CONFLICT, - Some("your last key: add another first".to_owned()), + Some( + "your last key, and you have no password on this node: add \ + another first" + .to_owned(), + ), )); } } -- cgit v1.3.1