diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index c6814952..187c93a4 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -33,7 +33,6 @@ mod approvals; mod build_logs; mod journal; mod lifecycle_ops; -mod matrix_accounts; pub(crate) mod permissions; mod questions; mod reminders; @@ -101,10 +100,6 @@ pub async fn serve(port: u16, coord: Arc) -> Result<()> { .route("/api/journal-host", get(journal::get_journal_host)) .route("/api/approval-diff/{id}", get(approvals::get_approval_diff)) .route("/api/state-file", get(state_files::get_state_file)) - .route( - "/api/matrix-accounts", - get(matrix_accounts::get_matrix_accounts), - ) .route("/api/reminders", get(reminders::api_reminders)) .route("/api/operator-inbox", get(api_operator_inbox)) .route("/api/stats-hive", get(api_stats_hive)) diff --git a/hive-c0re/src/dashboard/matrix_accounts.rs b/hive-c0re/src/dashboard/matrix_accounts.rs deleted file mode 100644 index a172a241..00000000 --- a/hive-c0re/src/dashboard/matrix_accounts.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! Matrix-account listing for the dashboard (BE-1 of the external -//! matrix-account provisioning backend). -//! -//! `GET /api/matrix-accounts?agent=` returns the matrix accounts an -//! agent currently has a token provisioned for. v1 derives the list cheaply -//! from the agent's state dir — every `matrix-token*` file is a provisioned -//! account (`matrix-token` = the hive-internal `main` account; -//! `matrix-token-` = an extra account, matching the daemon's -//! path-watcher glob). It does not read the nix config, so it lists what is -//! *provisioned*, not the full configured set: a config-declared account -//! without a token yet appears once it is provisioned via the login form. -//! Homeserver + live up/down status arrive in v2 from the daemon's account -//! registry; here `homeserver` is always `null`. - -use axum::extract::Query; -use axum::response::{IntoResponse, Response}; -use serde::{Deserialize, Serialize}; - -use super::error_response; -use crate::coordinator::Coordinator; - -#[derive(Deserialize)] -pub(super) struct MatrixAccountsQuery { - agent: String, -} - -#[derive(Serialize)] -struct MatrixAccount { - name: String, - /// Always `None` in v1 (derived from the token filename, which carries no - /// homeserver); populated in v2 from the daemon's account registry. - homeserver: Option, - token_present: bool, -} - -#[derive(Serialize)] -struct MatrixAccountsResponse { - accounts: Vec, -} - -/// Map a state-dir filename to the matrix account name it provisions, or -/// `None` if it is not a token file. `matrix-token` → the hive-internal -/// `main` account; `matrix-token-` → the extra account ``. -fn account_name_from_filename(fname: &str) -> Option { - if fname == "matrix-token" { - return Some("main".to_owned()); - } - let suffix = fname.strip_prefix("matrix-token-")?; - if suffix.is_empty() { - return None; - } - Some(suffix.to_owned()) -} - -pub(super) async fn get_matrix_accounts(Query(q): Query) -> Response { - let agent = q.agent.trim(); - // Agent names are simple identifiers; reject anything else so a crafted - // `agent` can't escape the per-agent state root via path components. - if agent.is_empty() - || !agent - .chars() - .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') - { - return error_response(&format!("matrix-accounts: invalid agent name {agent:?}")); - } - - let dir = Coordinator::agent_notes_dir(agent); - let mut accounts = Vec::new(); - match std::fs::read_dir(&dir) { - Ok(entries) => { - for entry in entries.flatten() { - if !entry.file_type().is_ok_and(|ft| ft.is_file()) { - continue; - } - let fname = entry.file_name(); - let Some(fname) = fname.to_str() else { - continue; - }; - if let Some(name) = account_name_from_filename(fname) { - accounts.push(MatrixAccount { - name, - homeserver: None, - token_present: true, - }); - } - } - } - // No state dir / no tokens yet is a normal empty result, not an error. - Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} - Err(e) => { - return error_response(&format!("matrix-accounts: read {}: {e}", dir.display())); - } - } - accounts.sort_by(|a, b| a.name.cmp(&b.name)); - axum::Json(MatrixAccountsResponse { accounts }).into_response() -} - -#[cfg(test)] -mod tests { - use super::account_name_from_filename; - - #[test] - fn main_account_from_bare_token() { - assert_eq!( - account_name_from_filename("matrix-token").as_deref(), - Some("main") - ); - } - - #[test] - fn extra_account_from_suffixed_token() { - assert_eq!( - account_name_from_filename("matrix-token-catgirl").as_deref(), - Some("catgirl") - ); - } - - #[test] - fn empty_suffix_is_not_an_account() { - assert_eq!(account_name_from_filename("matrix-token-"), None); - } - - #[test] - fn non_token_files_ignored() { - assert_eq!(account_name_from_filename("matrix-sdk-state"), None); - assert_eq!(account_name_from_filename("notes.md"), None); - assert_eq!(account_name_from_filename("matrix-avatar-icon-hash"), None); - } -}