diff options
| author | tv <tv@krebsco.de> | 2026-11-09 02:24:51 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-09 02:24:51 +0000 |
| commit | 8a6ed7cc711e797542a531091b3bdbc73e5f61e4 (patch) | |
| tree | a599731bdab3b3cbe07a8bf1d42887da907a2ea7 /src/api/users.rs | |
| parent | 764d3e07400854b333583b2efaa1c9eae18ae5e1 (diff) | |
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.
Diffstat (limited to 'src/api/users.rs')
| -rw-r--r-- | src/api/users.rs | 26 |
1 files changed, 19 insertions, 7 deletions
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<JsonValue> { pub(crate) async fn own_keys( State(state): State<AppState>, Extension(user): Extension<AuthenticatedUser>, + Extension(auth): Extension<super::auth::AuthState>, ) -> Result<Json<JsonValue>, 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<StatusCode, ApiError> { 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<AppState>, Extension(user): Extension<AuthenticatedUser>, + Extension(auth): Extension<super::auth::AuthState>, Path(id): Path<String>, ) -> Result<StatusCode, ApiError> { - 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<String>, + password: bool, ) -> Result<StatusCode, ApiError> { 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(), + ), )); } } |
