diff --git a/crates/core/database/src/drivers/mod.rs b/crates/core/database/src/drivers/mod.rs index df9a56ef..1fefcde8 100644 --- a/crates/core/database/src/drivers/mod.rs +++ b/crates/core/database/src/drivers/mod.rs @@ -3,6 +3,7 @@ mod mongodb; mod reference; use authifier::config::Captcha; +use authifier::config::EmailExpiryConfig; use authifier::config::EmailVerificationConfig; use authifier::config::PasswordScanning; use authifier::config::ResolveIp; @@ -10,10 +11,10 @@ use authifier::config::SMTPSettings; use authifier::config::Shield; use authifier::config::Template; use authifier::config::Templates; -use authifier::config::EmailExpiryConfig; use authifier::Authifier; use rand::Rng; use revolt_config::config; +use revolt_result::Result; #[cfg(feature = "mongodb")] pub use self::mongodb::*; @@ -251,4 +252,22 @@ impl Database { event_channel: None, } } + pub async fn disable_authifier_account(&self, user_id: &str) -> Result<()> { + let auth = self.clone().to_authifier().await; + + let mut account = auth + .database + .find_account(user_id) + .await + .map_err(|_| create_database_error!("find_account", "accounts"))?; + + account.disabled = true; + + auth.database + .save_account(&account) + .await + .map_err(|_| create_database_error!("save_account", "accounts"))?; + + Ok(()) + } } diff --git a/crates/core/models/src/v0/admin.rs b/crates/core/models/src/v0/admin.rs index c9c9c98a..12648206 100644 --- a/crates/core/models/src/v0/admin.rs +++ b/crates/core/models/src/v0/admin.rs @@ -288,6 +288,8 @@ auto_derived! { Search = 14, ManageNotes = 15, Discover = 16, + + AccountDisable = 17, } pub enum AdminAuditItemActions { @@ -317,7 +319,10 @@ auto_derived! { ServerSetFlags, ServerInstanceBanAllMembers, ServerBanMember, - ServerUnbanMember + ServerUnbanMember, + + // Accounts + AccountDisable } // Joiner payloads @@ -365,6 +370,7 @@ impl AdminAuditItemActions { AdminAuditItemActions::ServerBanMember => true, AdminAuditItemActions::ServerUnbanMember => true, AdminAuditItemActions::ServerFetchMembers => false, + AdminAuditItemActions::AccountDisable => true, } } } diff --git a/crates/core/result/src/axum.rs b/crates/core/result/src/axum.rs index dea75cf1..bb09621e 100644 --- a/crates/core/result/src/axum.rs +++ b/crates/core/result/src/axum.rs @@ -37,9 +37,7 @@ impl IntoResponse for Error { ErrorType::NotAMember => StatusCode::BAD_REQUEST, ErrorType::AlreadyPinned => StatusCode::BAD_REQUEST, ErrorType::NotPinned => StatusCode::BAD_REQUEST, - ErrorType::InSlowmode { - retry_after: _, - } => StatusCode::TOO_MANY_REQUESTS, + ErrorType::InSlowmode { retry_after: _ } => StatusCode::TOO_MANY_REQUESTS, ErrorType::InviteExists => StatusCode::BAD_REQUEST, ErrorType::CantCreateServers => StatusCode::FORBIDDEN, @@ -98,6 +96,8 @@ impl IntoResponse for Error { ErrorType::NoEmbedData => StatusCode::BAD_REQUEST, ErrorType::ImATeaPot => StatusCode::IM_A_TEAPOT, + + ErrorType::PrivilegedAccount => StatusCode::FORBIDDEN, }; (status, Json(&self)).into_response() diff --git a/crates/core/result/src/lib.rs b/crates/core/result/src/lib.rs index 994aec4b..6e4ecea2 100644 --- a/crates/core/result/src/lib.rs +++ b/crates/core/result/src/lib.rs @@ -190,6 +190,9 @@ pub enum ErrorType { feature: String, }, + // ? Admin API relate errors + PrivilegedAccount, + // :) ImATeaPot, } diff --git a/crates/core/result/src/rocket.rs b/crates/core/result/src/rocket.rs index df7d0075..d965d768 100644 --- a/crates/core/result/src/rocket.rs +++ b/crates/core/result/src/rocket.rs @@ -43,9 +43,7 @@ impl<'r> Responder<'r, 'static> for Error { ErrorType::NotAMember => Status::BadRequest, ErrorType::AlreadyPinned => Status::BadRequest, ErrorType::NotPinned => Status::BadRequest, - ErrorType::InSlowmode { - retry_after: _, - } => Status::TooManyRequests, + ErrorType::InSlowmode { retry_after: _ } => Status::TooManyRequests, ErrorType::InvalidFlagValue => Status::BadRequest, ErrorType::InviteExists => Status::BadRequest, @@ -104,6 +102,8 @@ impl<'r> Responder<'r, 'static> for Error { ErrorType::VosoUnavailable => Status::BadRequest, ErrorType::ImATeaPot => Status::ImATeapot, + + ErrorType::PrivilegedAccount => Status::Forbidden, }; // Serialize the error data structure into JSON. diff --git a/crates/delta/src/routes/admin/accounts/account_disable.rs b/crates/delta/src/routes/admin/accounts/account_disable.rs index e69de29b..7de0f6e6 100644 --- a/crates/delta/src/routes/admin/accounts/account_disable.rs +++ b/crates/delta/src/routes/admin/accounts/account_disable.rs @@ -0,0 +1,53 @@ +use crate::routes::admin::util::{ + create_audit_action, flatten_authorized_user, user_has_permission, +}; +use revolt_database::util::reference::Reference; +use revolt_database::{AdminAuthorization, Database}; +use revolt_models::v0::{self}; +use revolt_result::{create_error, Result}; +use rocket::serde::json::Json; +use rocket::State; + +/// Disable an account. Requires AccountDisable permissions +#[openapi(tag = "Admin")] +#[post("/accounts/disable/")] +pub async fn admin_account_disable( + db: &State, + auth: AdminAuthorization, + id: Reference<'_>, +) -> Result<()> { + let user = flatten_authorized_user(&auth); + if !user_has_permission(user, v0::AdminUserPermissionFlags::AccountDisable) { + return Err(create_error!(MissingPermission { + permission: "AccountDisable".to_string() + })); + } + + let target = id.as_user(db).await?; + + if target.privileged { + return Err(create_error!(PrivilegedAccount)); + } + + let admin = db.admin_user_fetch(&target.id).await.ok(); + + if let Some(admin) = admin { + if user_has_permission(&admin, v0::AdminUserPermissionFlags::AccountDisable) { + return Err(create_error!(PrivilegedAccount)); + } + } + + create_audit_action( + db, + &user.id, + v0::AdminAuditItemActions::AccountDisable, + None, + Some(id.id), + None, + ) + .await?; + + db.disable_authifier_account(&target.id).await?; + + Ok(()) +}