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:
parent
ed9c0f53ed
commit
bbf931207f
7 changed files with 705 additions and 35 deletions
|
|
@ -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::{
|
||||
|
|
|
|||
Loading…
Reference in a new issue