From 815ce17cc53f20df0b0ecce9c4ae71c1d1e4e935 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 16 Sep 2025 12:42:32 +0200 Subject: [PATCH] Simple CLI commands to manage server admins --- crates/cli/src/commands/manage.rs | 92 ++++++++++++++++++++++++++++++- docs/reference/cli/manage.md | 26 +++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/crates/cli/src/commands/manage.rs b/crates/cli/src/commands/manage.rs index a6a8e67a0..0b0121078 100644 --- a/crates/cli/src/commands/manage.rs +++ b/crates/cli/src/commands/manage.rs @@ -19,14 +19,17 @@ use mas_data_model::{Clock, Device, SystemClock, TokenType, Ulid, UpstreamOAuthP use mas_email::Address; use mas_matrix::HomeserverConnection; use mas_storage::{ - RepositoryAccess, + Pagination, RepositoryAccess, compat::{CompatAccessTokenRepository, CompatSessionFilter, CompatSessionRepository}, oauth2::OAuth2SessionFilter, queue::{ DeactivateUserJob, ProvisionUserJob, QueueJobRepositoryExt as _, ReactivateUserJob, SyncDevicesJob, }, - user::{BrowserSessionFilter, UserEmailRepository, UserPasswordRepository, UserRepository}, + user::{ + BrowserSessionFilter, UserEmailRepository, UserFilter, UserPasswordRepository, + UserRepository, + }, }; use mas_storage_pg::{DatabaseError, PgRepository}; use rand::{ @@ -85,6 +88,15 @@ enum Subcommand { ignore_complexity: bool, }, + /// Make a user admin + PromoteAdmin { username: String }, + + /// Make a user non-admin + DemoteAdmin { username: String }, + + /// List all users with admin privileges + ListAdminUsers, + /// Issue a compatibility token IssueCompatibilityToken { /// User for which to issue the token @@ -315,6 +327,82 @@ impl Options { Ok(ExitCode::SUCCESS) } + SC::PromoteAdmin { username } => { + let _span = + info_span!("cli.manage.promote_admin", user.username = username,).entered(); + + let database_config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let mut conn = database_connection_from_config(&database_config).await?; + let txn = conn.begin().await?; + let mut repo = PgRepository::from_conn(txn); + + let user = repo + .user() + .find_by_username(&username) + .await? + .context("User not found")?; + + let user = repo.user().set_can_request_admin(user, true).await?; + + repo.into_inner().commit().await?; + info!(%user.id, %user.username, "User promoted to admin"); + + Ok(ExitCode::SUCCESS) + } + + SC::DemoteAdmin { username } => { + let _span = + info_span!("cli.manage.demote_admin", user.username = username,).entered(); + + let database_config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let mut conn = database_connection_from_config(&database_config).await?; + let txn = conn.begin().await?; + let mut repo = PgRepository::from_conn(txn); + + let user = repo + .user() + .find_by_username(&username) + .await? + .context("User not found")?; + + let user = repo.user().set_can_request_admin(user, false).await?; + + repo.into_inner().commit().await?; + info!(%user.id, %user.username, "User is no longer admin"); + + Ok(ExitCode::SUCCESS) + } + + SC::ListAdminUsers => { + let _span = info_span!("cli.manage.list_admins").entered(); + let database_config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let mut conn = database_connection_from_config(&database_config).await?; + let txn = conn.begin().await?; + let mut repo = PgRepository::from_conn(txn); + + let mut cursor = Pagination::first(1000); + let filter = UserFilter::new().can_request_admin_only(); + let total = repo.user().count(filter).await?; + + info!("The following users can request admin privileges ({total} total):"); + loop { + let page = repo.user().list(filter, cursor).await?; + for user in page.edges { + info!(%user.id, username = %user.username); + cursor = cursor.after(user.id); + } + + if !page.has_next_page { + break; + } + } + + Ok(ExitCode::SUCCESS) + } + SC::IssueCompatibilityToken { username, admin, diff --git a/docs/reference/cli/manage.md b/docs/reference/cli/manage.md index 0f14f1773..5b107cd52 100644 --- a/docs/reference/cli/manage.md +++ b/docs/reference/cli/manage.md @@ -23,6 +23,32 @@ $ mas-cli manage add-email $ mas-cli manage verify-email ``` +## `manage promote-admin` + +Make a user admin. + +``` +$ mas-cli manage promote-admin +``` + +**This doesn't make all the users sessions admin, but rather lets the user request admin access in administration tools.** + +## `manage demote-admin` + +Make a user non-admin. + +``` +$ mas-cli manage demote-admin +``` + +## `manage list-admin-users` + +List all users with admin privileges. + +``` +$ mas-cli manage list-admins +``` + ## `manage set-password` Set a user password.