diff --git a/hive-c0re/src/bin/hivectl.rs b/hive-c0re/src/bin/hivectl.rs index 1c37f7b2..52e4eafd 100644 --- a/hive-c0re/src/bin/hivectl.rs +++ b/hive-c0re/src/bin/hivectl.rs @@ -478,7 +478,6 @@ async fn matrix_reset_password(name: &str) -> Result<()> { ); } let admin_token = hive_c0re::matrix::read_admin_token()?; - let new_password = hive_c0re::matrix::random_password().context("generate random password")?; let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .build() @@ -486,16 +485,10 @@ async fn matrix_reset_password(name: &str) -> Result<()> { let server_name = hive_c0re::matrix::discover_server_name(&client) .await .context("discover matrix server_name")?; - hive_c0re::matrix::reset_user_password( - &client, - &admin_token, - name, - &server_name, - &new_password, - ) - .await - .with_context(|| format!("matrix reset-password {name}"))?; - // Password is persisted by reset_user_password (including admin-room path). + hive_c0re::matrix::reset_user_password(&client, &admin_token, name, &server_name) + .await + .with_context(|| format!("matrix reset-password {name}"))?; + // Password is persisted by reset_user_password. let pw_path = PathBuf::from("/var/lib/hyperhive/matrix-creds").join(format!("{name}-password")); println!("matrix: password for @{name}:{server_name} reset"); println!("password persisted at: {}", pw_path.display()); diff --git a/hive-c0re/src/matrix.rs b/hive-c0re/src/matrix.rs index 841732fa..579ecf9c 100644 --- a/hive-c0re/src/matrix.rs +++ b/hive-c0re/src/matrix.rs @@ -299,14 +299,13 @@ async fn auto_reset_password(client: &reqwest::Client, name: &str) -> anyhow::Re let server_name = discover_server_name(client) .await .context("matrix: discover_server_name for auto-recovery")?; - let new_password = random_password()?; let effective_password = - reset_user_password(client, &admin_token, name, &server_name, &new_password) + reset_user_password(client, &admin_token, name, &server_name) .await .with_context(|| { - format!("matrix: admin API password reset for {name} (auto-recovery)") + format!("matrix: admin-room password reset for {name} (auto-recovery)") })?; - tracing::info!(%name, "matrix: auto-recovered password via admin API reset"); + tracing::info!(%name, "matrix: auto-recovered password via admin-room reset"); Ok(effective_password) } @@ -823,54 +822,27 @@ pub async fn promote_user_to_admin( ) } -/// Reset a user's password, trying the Synapse admin REST API first and -/// falling back to the Matrix admin room (`#admins:`) if the endpoint -/// returns 404 (e.g. on tuwunel which does not implement the Synapse API). +/// Reset a user's password via the Matrix admin room (`#admins:`). /// -/// Returns the effective new password — which may be the caller-supplied -/// `new_password` (Synapse path) or a server-generated one (admin-room path). -/// The caller must use the returned password for subsequent `login_user` calls. +/// Sends `reset-password @:` to the admin room as @hive, +/// polls for the bot's response containing the new password, and persists +/// it to the non-purgeable creds path so [`ensure_user_for`] can re-login +/// on the next provisioning sweep. /// -/// Writes the new password to the non-purgeable creds path so -/// [`ensure_user_for`] can re-login on the next provisioning sweep. +/// Returns the new password for use in subsequent `login_user` calls. pub async fn reset_user_password( client: &reqwest::Client, admin_token: &str, localpart: &str, server_name: &str, - new_password: &str, ) -> Result { - let url = format!("{MATRIX_HTTP}/_synapse/admin/v2/users/%40{localpart}%3A{server_name}"); - let resp = client - .put(&url) - .bearer_auth(admin_token) - .json(&serde_json::json!({"password": new_password})) - .send() + let pw = admin_room_reset_password(client, admin_token, server_name, localpart) .await - .context("matrix: PUT /_synapse/admin/v2/users (reset password)")?; - let status = resp.status(); - if status.is_success() { - persist_password(localpart, new_password); - return Ok(new_password.to_owned()); - } - // 404 = endpoint not implemented (e.g. tuwunel 1.6.x): fall back to admin room. - if status == StatusCode::NOT_FOUND { - tracing::debug!( - %localpart, - "matrix: Synapse admin API returned 404 — falling back to admin-room reset" - ); - let pw = admin_room_reset_password(client, admin_token, server_name, localpart) - .await - .with_context(|| { - format!("matrix: admin-room fallback for reset password of @{localpart}:{server_name}") - })?; - persist_password(localpart, &pw); - return Ok(pw); - } - let body = resp.json::().await.unwrap_or_default(); - anyhow::bail!( - "matrix: reset password for @{localpart}:{server_name}: HTTP {status}, body: {body}" - ) + .with_context(|| { + format!("matrix: admin-room password reset for @{localpart}:{server_name}") + })?; + persist_password(localpart, &pw); + Ok(pw) } /// Persist the matrix password for `localpart` to the non-purgeable creds path.