The CR3D3NTIALS page's MATRIX tab was the only caller of `POST /api/matrix-account-login` (provision/log in an external matrix account through the hive) and `GET /api/matrix-accounts` (its account list). External matrix accounts are linked from the swarm UI now (`LinkMatrixAccountForm` -> swarm-controller), so the hive-side UI and both routes go. `priv_client::restart_matrix_daemon` had no other caller and goes with them. Already-provisioned credentials keep working: the `matrix-token-<name>` files and `matrix-account-<name>.json` sidecars the old route wrote are still discovered by hive-matrix-mcp (`accounts::configured` -> `discover_token_accounts`), the `matrix-token*` path unit still re-fires the daemon, and `WriteAgentMatrixToken` stays for the swarm credential worker. Removing that usage waits on moving the existing creds to swarm level. The GITHUB tab is the credentials page's default tab now. Refs #4348
100 lines
3.7 KiB
Rust
100 lines
3.7 KiB
Rust
//! Per-agent GitHub PAT provisioning for the dashboard's GITHUB tab:
|
|
//! `POST /api/github-account` stores it, `GET /api/github-account` reports
|
|
//! whether one is stored.
|
|
|
|
use axum::extract::{Form, Query};
|
|
use axum::response::{IntoResponse, Response};
|
|
use serde::{Deserialize, Serialize};
|
|
use utoipa::{IntoParams, ToSchema};
|
|
|
|
use super::{Ident, error_response};
|
|
use crate::coordinator::Coordinator;
|
|
|
|
/// Form body for `POST /api/github-account` (urlencoded, the dashboard's
|
|
/// mutation convention). Writes the operator-supplied PAT to the agent's
|
|
/// `github-token` file. No account creation and no login modes — the
|
|
/// operator pastes a PAT for an existing account.
|
|
#[derive(Deserialize, ToSchema)]
|
|
pub(super) struct GithubAccountForm {
|
|
agent: String,
|
|
token: String,
|
|
}
|
|
|
|
#[derive(Serialize, ToSchema)]
|
|
struct GithubAccountResult {
|
|
ok: bool,
|
|
}
|
|
|
|
/// Provision (or refresh) an agent's GitHub PAT from the dashboard
|
|
/// credentials tab.
|
|
///
|
|
/// Validates the agent name, then writes the PAT to
|
|
/// `<state>/github-token` (`0600`, agent-owned) via hive-priv. No account
|
|
/// creation and no daemon to kick — the agent's `gh` wrapper / git credential
|
|
/// helper read the file live, so the new token takes effect immediately.
|
|
/// Operator-authenticated (dashboard). Never echoes the token back — only
|
|
/// `{ ok: true }`.
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/github-account",
|
|
request_body(content = GithubAccountForm, content_type = "application/x-www-form-urlencoded"),
|
|
responses(
|
|
(status = 200, description = "PAT provisioned", body = GithubAccountResult),
|
|
(status = 500, description = "invalid agent name, empty token, or the write failed"),
|
|
),
|
|
tag = "matrix_accounts"
|
|
)]
|
|
pub(super) async fn post_github_account(Form(f): Form<GithubAccountForm>) -> Response {
|
|
let agent = f.agent.trim();
|
|
let token = f.token.trim();
|
|
let Ok(agent) = Ident::parse(agent) else {
|
|
return error_response(&format!("github-account: invalid agent {agent:?}"));
|
|
};
|
|
if token.is_empty() {
|
|
return error_response("github-account: token is required");
|
|
}
|
|
if let Err(e) = crate::priv_client::write_agent_github_token(agent.as_str(), token).await {
|
|
return error_response(&format!("github-account: write token failed: {e:#}"));
|
|
}
|
|
tracing::info!(%agent, "github-account: provisioned github PAT");
|
|
axum::Json(GithubAccountResult { ok: true }).into_response()
|
|
}
|
|
|
|
#[derive(Deserialize, IntoParams)]
|
|
pub(super) struct GithubAccountQuery {
|
|
agent: String,
|
|
}
|
|
|
|
#[derive(Serialize, ToSchema)]
|
|
struct GithubAccountStatus {
|
|
/// A `github-token` file exists in the agent's state dir (a PAT has been
|
|
/// provisioned). A static PAT has no live/heartbeat concept, so this is
|
|
/// the only status the credentials tab needs.
|
|
present: bool,
|
|
}
|
|
|
|
/// Whether the agent has a GitHub
|
|
/// PAT provisioned (its `github-token` file exists).
|
|
///
|
|
/// Lets the credentials tab show "token stored" vs "not set" instead of a
|
|
/// black-hole paste field. Never returns the token itself.
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/github-account",
|
|
params(GithubAccountQuery),
|
|
responses(
|
|
(status = 200, description = "whether a github PAT is provisioned", body = GithubAccountStatus),
|
|
(status = 500, description = "invalid agent name"),
|
|
),
|
|
tag = "matrix_accounts"
|
|
)]
|
|
pub(super) async fn get_github_account(Query(q): Query<GithubAccountQuery>) -> Response {
|
|
let agent = q.agent.trim();
|
|
let Ok(agent) = Ident::parse(agent) else {
|
|
return error_response(&format!("github-account: invalid agent {agent:?}"));
|
|
};
|
|
let present = Coordinator::agent_notes_dir(&agent)
|
|
.join("github-token")
|
|
.exists();
|
|
axum::Json(GithubAccountStatus { present }).into_response()
|
|
}
|