aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/users.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-09 02:24:51 +0000
committertv <tv@krebsco.de>2026-11-09 02:24:51 +0000
commit8a6ed7cc711e797542a531091b3bdbc73e5f61e4 (patch)
treea599731bdab3b3cbe07a8bf1d42887da907a2ea7 /src/api/users.rs
parent764d3e07400854b333583b2efaa1c9eae18ae5e1 (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.rs26
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(),
+ ),
));
}
}