fix: remove Synapse admin API path — use admin-room reset exclusively (tuwunel only)

This commit is contained in:
atlas 2026-06-04 15:07:48 +02:00 committed by mara
commit a56badd004
2 changed files with 19 additions and 54 deletions

View file

@ -478,7 +478,6 @@ async fn matrix_reset_password(name: &str) -> Result<()> {
); );
} }
let admin_token = hive_c0re::matrix::read_admin_token()?; 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() let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30)) .timeout(std::time::Duration::from_secs(30))
.build() .build()
@ -486,16 +485,10 @@ async fn matrix_reset_password(name: &str) -> Result<()> {
let server_name = hive_c0re::matrix::discover_server_name(&client) let server_name = hive_c0re::matrix::discover_server_name(&client)
.await .await
.context("discover matrix server_name")?; .context("discover matrix server_name")?;
hive_c0re::matrix::reset_user_password( hive_c0re::matrix::reset_user_password(&client, &admin_token, name, &server_name)
&client, .await
&admin_token, .with_context(|| format!("matrix reset-password {name}"))?;
name, // Password is persisted by reset_user_password.
&server_name,
&new_password,
)
.await
.with_context(|| format!("matrix reset-password {name}"))?;
// Password is persisted by reset_user_password (including admin-room path).
let pw_path = PathBuf::from("/var/lib/hyperhive/matrix-creds").join(format!("{name}-password")); let pw_path = PathBuf::from("/var/lib/hyperhive/matrix-creds").join(format!("{name}-password"));
println!("matrix: password for @{name}:{server_name} reset"); println!("matrix: password for @{name}:{server_name} reset");
println!("password persisted at: {}", pw_path.display()); println!("password persisted at: {}", pw_path.display());

View file

@ -299,14 +299,13 @@ async fn auto_reset_password(client: &reqwest::Client, name: &str) -> anyhow::Re
let server_name = discover_server_name(client) let server_name = discover_server_name(client)
.await .await
.context("matrix: discover_server_name for auto-recovery")?; .context("matrix: discover_server_name for auto-recovery")?;
let new_password = random_password()?;
let effective_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 .await
.with_context(|| { .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) 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 /// Reset a user's password via the Matrix admin room (`#admins:<server>`).
/// falling back to the Matrix admin room (`#admins:<server>`) if the endpoint
/// returns 404 (e.g. on tuwunel which does not implement the Synapse API).
/// ///
/// Returns the effective new password — which may be the caller-supplied /// Sends `reset-password @<localpart>:<server>` to the admin room as @hive,
/// `new_password` (Synapse path) or a server-generated one (admin-room path). /// polls for the bot's response containing the new password, and persists
/// The caller must use the returned password for subsequent `login_user` calls. /// 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 /// Returns the new password for use in subsequent `login_user` calls.
/// [`ensure_user_for`] can re-login on the next provisioning sweep.
pub async fn reset_user_password( pub async fn reset_user_password(
client: &reqwest::Client, client: &reqwest::Client,
admin_token: &str, admin_token: &str,
localpart: &str, localpart: &str,
server_name: &str, server_name: &str,
new_password: &str,
) -> Result<String> { ) -> Result<String> {
let url = format!("{MATRIX_HTTP}/_synapse/admin/v2/users/%40{localpart}%3A{server_name}"); let pw = admin_room_reset_password(client, admin_token, server_name, localpart)
let resp = client
.put(&url)
.bearer_auth(admin_token)
.json(&serde_json::json!({"password": new_password}))
.send()
.await .await
.context("matrix: PUT /_synapse/admin/v2/users (reset password)")?; .with_context(|| {
let status = resp.status(); format!("matrix: admin-room password reset for @{localpart}:{server_name}")
if status.is_success() { })?;
persist_password(localpart, new_password); persist_password(localpart, &pw);
return Ok(new_password.to_owned()); Ok(pw)
}
// 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::<serde_json::Value>().await.unwrap_or_default();
anyhow::bail!(
"matrix: reset password for @{localpart}:{server_name}: HTTP {status}, body: {body}"
)
} }
/// Persist the matrix password for `localpart` to the non-purgeable creds path. /// Persist the matrix password for `localpart` to the non-purgeable creds path.