Watch
0
0
Fork
You've already forked hyperhive
0

swarm UI: delete linked accounts

Each row of an agent's linked accounts, except its own `main` matrix
account, gets a delete action. swarm-controller serves DELETE beside each
PUT (matrix-accounts/{account}, forge-accounts/{label}, github-account),
answers 404 for an account the store does not hold, refuses `main`, and
removes every version through `delete_all_versions`.

The matrix confirmation has a revoke checkbox, off by default: the
controller logs the stored token out at its homeserver first, and keeps
the account when that fails or no homeserver is stored.

The controller's policy gains `delete` on each agent's
`metadata/.../matrix/+`, `forge/+` and `github-token`, pinned in
bao-grants.nix.

Refs #4855
This commit is contained in:
atlas 2026-10-02 23:31:39 +02:00
commit bbf931207f
7 changed files with 705 additions and 35 deletions

View file

@ -196,7 +196,7 @@ pub async fn put_matrix_account(
/// Whether `account` is the agent's own account, which [`agent_token`] mints
/// and `nix/agent-modules/matrix.nix` declares per agent — see
/// [`put_matrix_account`]'s comment on it for why that route must never write
/// one.
/// one. `linked_accounts::delete_matrix_account` refuses to delete it too.
pub(crate) fn is_reserved_account(account: &str) -> bool {
account == agent_token::ACCOUNT
}
@ -356,6 +356,40 @@ async fn matrix_password_login(
Ok((token.to_owned(), uid.to_owned()))
}
/// POST `<homeserver>/_matrix/client/v3/logout` with `token`, which
/// invalidates that token at the homeserver.
///
/// The error names the status and the homeserver's `error` text, never the
/// token.
pub(crate) async fn matrix_logout(homeserver: &str, token: &str) -> Result<(), String> {
let url = format!(
"{}/_matrix/client/v3/logout",
homeserver.trim_end_matches('/')
);
let resp = http_client()?
.post(&url)
.bearer_auth(token)
.json(&serde_json::json!({}))
.send()
.await
.map_err(|e| http_error("POST /logout", &e))?;
let status = resp.status();
if status.is_success() {
return Ok(());
}
let err = resp
.json::<serde_json::Value>()
.await
.ok()
.and_then(|j| {
j.get("error")
.and_then(serde_json::Value::as_str)
.map(str::to_owned)
})
.unwrap_or_else(|| "logout failed".to_owned());
Err(format!("/logout HTTP {status}: {err}"))
}
#[cfg(test)]
mod tests {
use super::{