From edfa97db108c9c81828547f98a1db5315cb5ba4a Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 23 Mar 2026 12:25:33 -0700 Subject: [PATCH] feat: Allow restricting server creation to specific users (#685) Signed-off-by: IAmTomahawkx --- crates/core/config/Revolt.toml | 4 +++ crates/core/config/src/lib.rs | 2 ++ crates/core/result/src/axum.rs | 1 + crates/core/result/src/lib.rs | 30 +++++++++---------- crates/core/result/src/rocket.rs | 1 + crates/delta/src/routes/root.rs | 18 +++++++++-- .../delta/src/routes/servers/server_create.rs | 19 ++++++++++++ 7 files changed, 56 insertions(+), 19 deletions(-) diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index a64778c4..4468517f 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -220,6 +220,10 @@ new_user_hours = 72 # (should be greater than any one file upload limit) body_limit_size = 20_000_000 +# If any userids are entered here, only those users will be able to create servers. +# Leave empty to allow all users to create servers +restrict_server_creation = [] + [features.limits.new_user] # Limits imposed on new users diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 76a6b2e1..23f7edf3 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -344,6 +344,8 @@ pub struct GlobalLimits { pub new_user_hours: usize, pub body_limit_size: usize, + + pub restrict_server_creation: Vec, } #[derive(Deserialize, Debug, Clone)] diff --git a/crates/core/result/src/axum.rs b/crates/core/result/src/axum.rs index 3cbf4359..1145009c 100644 --- a/crates/core/result/src/axum.rs +++ b/crates/core/result/src/axum.rs @@ -37,6 +37,7 @@ impl IntoResponse for Error { ErrorType::AlreadyPinned => StatusCode::BAD_REQUEST, ErrorType::NotPinned => StatusCode::BAD_REQUEST, + ErrorType::CantCreateServers => StatusCode::FORBIDDEN, ErrorType::UnknownServer => StatusCode::NOT_FOUND, ErrorType::InvalidRole => StatusCode::NOT_FOUND, ErrorType::Banned => StatusCode::FORBIDDEN, diff --git a/crates/core/result/src/lib.rs b/crates/core/result/src/lib.rs index a7661212..17093f96 100644 --- a/crates/core/result/src/lib.rs +++ b/crates/core/result/src/lib.rs @@ -1,5 +1,5 @@ -use std::panic::Location; use std::fmt::Display; +use std::panic::Location; #[cfg(feature = "serde")] #[macro_use] @@ -104,6 +104,7 @@ pub enum ErrorType { NotPinned, // ? Server related errors + CantCreateServers, UnknownServer, InvalidRole, Banned, @@ -232,17 +233,16 @@ impl ToRevoltError for Result Result { let loc = Location::caller(); - self - .map_err(|e| { - log::error!("{e:?}"); - #[cfg(feature = "sentry")] - sentry::capture_error(&e); + self.map_err(|e| { + log::error!("{e:?}"); + #[cfg(feature = "sentry")] + sentry::capture_error(&e); - Error { - error_type: ErrorType::InternalError, - location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column()) - } - }) + Error { + error_type: ErrorType::InternalError, + location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column()), + } + }) } } @@ -251,11 +251,9 @@ impl ToRevoltError for Option { fn to_internal_error(self) -> Result { let loc = Location::caller(); - self.ok_or_else(|| { - Error { - error_type: ErrorType::InternalError, - location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column()) - } + self.ok_or_else(|| Error { + error_type: ErrorType::InternalError, + location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column()), }) } } diff --git a/crates/core/result/src/rocket.rs b/crates/core/result/src/rocket.rs index 94bed4d2..744d09cf 100644 --- a/crates/core/result/src/rocket.rs +++ b/crates/core/result/src/rocket.rs @@ -44,6 +44,7 @@ impl<'r> Responder<'r, 'static> for Error { ErrorType::NotPinned => Status::BadRequest, ErrorType::InvalidFlagValue => Status::BadRequest, + ErrorType::CantCreateServers => Status::Forbidden, ErrorType::UnknownServer => Status::NotFound, ErrorType::InvalidRole => Status::NotFound, ErrorType::Banned => Status::Forbidden, diff --git a/crates/delta/src/routes/root.rs b/crates/delta/src/routes/root.rs index 44b697d4..6fd37a19 100644 --- a/crates/delta/src/routes/root.rs +++ b/crates/delta/src/routes/root.rs @@ -1,8 +1,8 @@ -use std::collections::HashMap; use revolt_config::config; use revolt_result::Result; use rocket::serde::json::Json; use serde::Serialize; +use std::collections::HashMap; /// # hCaptcha Configuration #[derive(Serialize, JsonSchema, Debug)] @@ -88,6 +88,10 @@ pub struct GlobalLimits { /// max server channels server_channels: i64, body_limit_size: i64, + + /// restrict server creation to these users. + /// if blank, all users can create servers + pub restrict_server_creation: Vec, } /// # User Limits @@ -125,7 +129,10 @@ impl UserLimits { voice_quality: fl.voice_quality as i64, video: fl.video, video_resolution: [fl.video_resolution[0] as i64, fl.video_resolution[1] as i64], - video_aspect_ratio: [fl.video_aspect_ratio[0] as f64, fl.video_aspect_ratio[1] as f64], + video_aspect_ratio: [ + fl.video_aspect_ratio[0] as f64, + fl.video_aspect_ratio[1] as f64, + ], file_upload_size_limits: fl.file_upload_size_limit, } } @@ -219,10 +226,15 @@ pub async fn root() -> Result> { server_roles: config.features.limits.global.server_roles as i64, server_channels: config.features.limits.global.server_channels as i64, body_limit_size: config.features.limits.global.body_limit_size as i64, + restrict_server_creation: config + .features + .limits + .global + .restrict_server_creation, }, new_user: UserLimits::from_feature_limits(config.features.limits.new_user), default: UserLimits::from_feature_limits(config.features.limits.default), - } + }, }, ws: config.hosts.events, app: config.hosts.app, diff --git a/crates/delta/src/routes/servers/server_create.rs b/crates/delta/src/routes/servers/server_create.rs index 82f0a8aa..5b9aa010 100644 --- a/crates/delta/src/routes/servers/server_create.rs +++ b/crates/delta/src/routes/servers/server_create.rs @@ -1,3 +1,4 @@ +use revolt_config::config; use revolt_database::{Database, Member, Server, User}; use revolt_models::v0; use revolt_result::{create_error, Result}; @@ -20,6 +21,24 @@ pub async fn create_server( return Err(create_error!(IsBot)); } + let config = config().await; + + if !config + .features + .limits + .global + .restrict_server_creation + .is_empty() + && !config + .features + .limits + .global + .restrict_server_creation + .contains(&user.id) + { + return Err(create_error!(CantCreateServers)); + } + let data = data.into_inner(); data.validate().map_err(|error| { create_error!(FailedValidation {