diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index 20fa1652..446a22da 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -197,6 +197,8 @@ admin_api_enabled = false mass_mentions_send_notifications = true # Can role/everyone pings be used at all mass_mentions_enabled = true +# Show the admin api in the OpenAPI spec +admin_api_show_spec = false [features.limits] diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 84ac77b5..0084abed 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -355,6 +355,7 @@ pub struct Features { pub mass_mentions_send_notifications: bool, pub mass_mentions_enabled: bool, pub admin_api_enabled: bool, + pub admin_api_show_spec: bool, #[serde(default)] pub advanced: FeaturesAdvanced, diff --git a/crates/core/database/src/models/admin_tokens/axum.rs b/crates/core/database/src/models/admin_tokens/axum.rs deleted file mode 100644 index 1233ae8f..00000000 --- a/crates/core/database/src/models/admin_tokens/axum.rs +++ /dev/null @@ -1,36 +0,0 @@ -use axum::{extract::FromRequestParts, http::request::Parts}; - -use revolt_result::{create_error, Error, Result}; - -use crate::{AdminMachineToken, Database}; - -#[async_trait::async_trait] -impl FromRequestParts for AdminMachineToken { - type Rejection = Error; - - async fn from_request_parts(parts: &mut Parts, db: &Database) -> Result { - if let Some(Ok(on_behalf_of)) = parts - .headers - .get("x-admin-on-behalf-of") - .map(|v| v.to_str()) - { - if let Some(Ok(token)) = parts.headers.get("x-admin-machine").map(|v| v.to_str()) { - let config = revolt_config::config().await; - let token = token.to_string(); - if config.api.security.admin_keys.contains(&token) { - let resp: AdminMachineToken; - // shitty email check - if on_behalf_of.contains("@") { - resp = AdminMachineToken::new_from_email(on_behalf_of, db).await? - } else { - resp = AdminMachineToken::new_from_id(on_behalf_of, db).await? - } - return Ok(resp); - } else { - return Err(create_error!(InvalidCredentials)); - } - } - } - Err(create_error!(NotAuthenticated)) - } -} diff --git a/crates/core/database/src/models/admin_tokens/mod.rs b/crates/core/database/src/models/admin_tokens/mod.rs index 7b1a1988..8a22f64e 100644 --- a/crates/core/database/src/models/admin_tokens/mod.rs +++ b/crates/core/database/src/models/admin_tokens/mod.rs @@ -1,13 +1,12 @@ -#[cfg(feature = "axum-impl")] -mod axum; mod models; mod ops; #[cfg(feature = "rocket-impl")] mod rocket; +mod schema; -#[cfg(feature = "axum-impl")] -pub use self::axum::*; #[cfg(feature = "rocket-impl")] pub use self::rocket::*; +#[cfg(feature = "rocket-impl")] +pub use self::schema::*; pub use models::*; pub use ops::*; diff --git a/crates/core/database/src/models/admin_tokens/models.rs b/crates/core/database/src/models/admin_tokens/models.rs index 0ffe3dd1..bb87e142 100644 --- a/crates/core/database/src/models/admin_tokens/models.rs +++ b/crates/core/database/src/models/admin_tokens/models.rs @@ -1,3 +1,4 @@ +use iso8601_timestamp::Timestamp; use revolt_result::Result; use crate::{AdminUser, Database}; @@ -20,10 +21,45 @@ auto_derived! { /// Placeholder field. pub on_behalf_of: AdminUser } + + pub enum AdminAuthorization { + AdminUser(AdminUser), + AdminMachine(AdminMachineToken), + } +} + +impl AdminToken { + pub fn new(user_id: &str, expiry: Timestamp) -> AdminToken { + let id = ulid::Ulid::new().to_string(); + let token = nanoid::nanoid!(64); + AdminToken { + id, + user_id: user_id.to_string(), + token, + expiry: expiry.format_short().to_string(), + } + } } impl AdminMachineToken { pub async fn new_from_email(email: &str, db: &Database) -> Result { + println!("{}", email); + if email == "example@example.com" { + // This is basically just a workaround to make the first user. + let user_count = db.admin_user_count().await?; + println!("{}", user_count); + if user_count == 0 { + return Ok(AdminMachineToken { + on_behalf_of: AdminUser { + id: "00".to_string(), + platform_user_id: "".to_string(), + email: "example@example.com".to_string(), + active: true, + permissions: u64::MAX, + }, + }); + } + } let user = db.admin_user_fetch_email(email).await?; Ok(AdminMachineToken { on_behalf_of: user }) } diff --git a/crates/core/database/src/models/admin_tokens/ops.rs b/crates/core/database/src/models/admin_tokens/ops.rs index a42e73a3..f4d0f0e7 100644 --- a/crates/core/database/src/models/admin_tokens/ops.rs +++ b/crates/core/database/src/models/admin_tokens/ops.rs @@ -11,4 +11,6 @@ pub trait AbstractAdminTokens: Sync + Send { async fn admin_token_revoke(&self, token_id: &str) -> Result<()>; async fn admin_token_authenticate(&self, token: &str) -> Result; + + async fn admin_token_fetch(&self, id: &str) -> Result; } diff --git a/crates/core/database/src/models/admin_tokens/ops/mongodb.rs b/crates/core/database/src/models/admin_tokens/ops/mongodb.rs index ee47bb3a..58b5d2c9 100644 --- a/crates/core/database/src/models/admin_tokens/ops/mongodb.rs +++ b/crates/core/database/src/models/admin_tokens/ops/mongodb.rs @@ -27,4 +27,8 @@ impl AbstractAdminTokens for MongoDb { query!(self, find_one, COL, doc! {"token": token})? .ok_or_else(|| create_error!(InvalidCredentials)) } + + async fn admin_token_fetch(&self, id: &str) -> Result { + query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound)) + } } diff --git a/crates/core/database/src/models/admin_tokens/ops/reference.rs b/crates/core/database/src/models/admin_tokens/ops/reference.rs index fd1697ee..4d28d09b 100644 --- a/crates/core/database/src/models/admin_tokens/ops/reference.rs +++ b/crates/core/database/src/models/admin_tokens/ops/reference.rs @@ -41,4 +41,13 @@ impl AbstractAdminTokens for ReferenceDb { Ok(result.ok_or_else(|| create_error!(NotFound))?) } + + async fn admin_token_fetch(&self, id: &str) -> Result { + let admin_tokens = self.admin_tokens.lock().await; + let result = admin_tokens.iter().find(|tok| tok.0 == id); + + Ok(result + .map(|t| t.1.clone()) + .ok_or_else(|| create_error!(NotFound))?) + } } diff --git a/crates/core/database/src/models/admin_tokens/rocket.rs b/crates/core/database/src/models/admin_tokens/rocket.rs index ed5b771b..34a2d7aa 100644 --- a/crates/core/database/src/models/admin_tokens/rocket.rs +++ b/crates/core/database/src/models/admin_tokens/rocket.rs @@ -1,8 +1,10 @@ +use iso8601_timestamp::Timestamp; +use revolt_config::{capture_internal_error, report_error}; use revolt_result::Result; use rocket::http::Status; use rocket::request::{self, FromRequest, Outcome, Request}; -use crate::{AdminMachineToken, Database}; +use crate::{AdminAuthorization, AdminMachineToken, AdminUser, Database}; #[rocket::async_trait] impl<'r> FromRequest<'r> for AdminMachineToken { @@ -50,3 +52,95 @@ impl<'r> FromRequest<'r> for AdminMachineToken { } } } + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for AdminAuthorization { + type Error = authifier::Error; + + async fn from_request(request: &'r Request<'_>) -> request::Outcome { + let machine: &Option = request + .local_cache_async(async { + let db = request.rocket().state::().expect("`Database`"); + + if let Some(token) = request + .headers() + .get("x-admin-machine") + .next() + .map(|x| x.to_string()) + { + if let Some(on_behalf_of) = request + .headers() + .get("x-admin-on-behalf-of") + .next() + .map(|x| x.to_string()) + { + let config = revolt_config::config().await; + let token = token.to_string(); + if config.api.security.admin_keys.contains(&token) { + let resp: Result = if on_behalf_of.contains("@") { + AdminMachineToken::new_from_email(&on_behalf_of, db).await + } else { + AdminMachineToken::new_from_id(&on_behalf_of, db).await + }; + if let Ok(resp) = resp { + return Some(resp); + } + } + } + } + None + }) + .await; + + if let Some(machine) = machine { + if !machine.on_behalf_of.active { + Outcome::Error((Status::Unauthorized, authifier::Error::LockedOut)) + } else { + Outcome::Success(AdminAuthorization::AdminMachine(machine.clone())) + } + } else { + let user: &Option = request + .local_cache_async(async { + let db = request.rocket().state::().expect("`Database`"); + + let token = request + .headers() + .get("x-admin-user") + .next() + .map(|x| x.to_string()); + + if let Some(token) = token { + if let Ok(admin_token) = db.admin_token_authenticate(&token).await { + if let Some(expiry) = Timestamp::parse(&admin_token.expiry) { + if expiry < Timestamp::now_utc() { + if let Err(e) = db.admin_token_revoke(&admin_token.id).await { + capture_internal_error!(e); + } + return None; + } else if let Ok(user) = + db.admin_user_fetch(&admin_token.user_id).await + { + if !user.active { + return None; + } + return Some(user); + } + } + } + } + None + }) + .await; + + if let Some(user) = user { + if !user.active { + Outcome::Error((Status::Unauthorized, authifier::Error::LockedOut)) + } else { + Outcome::Success(AdminAuthorization::AdminUser(user.clone())) + } + } else { + Outcome::Error((Status::Unauthorized, authifier::Error::InvalidCredentials)) + } + } + } +} diff --git a/crates/core/database/src/models/admin_tokens/schema.rs b/crates/core/database/src/models/admin_tokens/schema.rs new file mode 100644 index 00000000..abfb7089 --- /dev/null +++ b/crates/core/database/src/models/admin_tokens/schema.rs @@ -0,0 +1,59 @@ +use revolt_okapi::openapi3::{SecurityScheme, SecuritySchemeData}; +use revolt_rocket_okapi::{ + gen::OpenApiGenerator, + request::{OpenApiFromRequest, RequestHeaderInput}, +}; + +use crate::{AdminAuthorization, AdminMachineToken}; + +impl OpenApiFromRequest<'_> for AdminAuthorization { + fn from_request_input( + _gen: &mut OpenApiGenerator, + _name: String, + _required: bool, + ) -> revolt_rocket_okapi::Result { + let mut requirements = schemars::Map::new(); + requirements.insert("Admin Token".to_owned(), vec![]); + + Ok(RequestHeaderInput::Security( + "Admin Token".to_owned(), + SecurityScheme { + data: SecuritySchemeData::ApiKey { + name: "x-admin-user".to_owned(), + location: "header".to_owned(), + }, + description: Some("Used to authenticate as an admin user. + Can instead use an x-admin-machine token with an x-on-behalf-of containing the userid or email of + the user the machine is performing the action for.".to_owned()), + extensions: schemars::Map::new(), + }, + requirements, + )) + } +} + +impl OpenApiFromRequest<'_> for AdminMachineToken { + fn from_request_input( + _gen: &mut OpenApiGenerator, + _name: String, + _required: bool, + ) -> revolt_rocket_okapi::Result { + let mut requirements = schemars::Map::new(); + requirements.insert("Admin Machine Token".to_owned(), vec![]); + + Ok(RequestHeaderInput::Security( + "Admin Machine Token".to_owned(), + SecurityScheme { + data: SecuritySchemeData::ApiKey { + name: "x-admin-machine".to_owned(), + location: "header".to_owned(), + }, + description: Some("Used by machines to authenticate on behalf of a user. + Machines are trusted devices that authenticate as other users via providing the x-on-behalf-of header + with an admin user's ID or email.".to_owned()), + extensions: schemars::Map::new(), + }, + requirements, + )) + } +} diff --git a/crates/core/database/src/models/admin_users/axum.rs b/crates/core/database/src/models/admin_users/axum.rs deleted file mode 100644 index f3dc8676..00000000 --- a/crates/core/database/src/models/admin_users/axum.rs +++ /dev/null @@ -1,19 +0,0 @@ -use axum::{extract::FromRequestParts, http::request::Parts}; - -use revolt_result::{create_error, Error, Result}; - -use crate::{AdminUser, Database}; - -#[async_trait::async_trait] -impl FromRequestParts for AdminUser { - type Rejection = Error; - - async fn from_request_parts(parts: &mut Parts, db: &Database) -> Result { - if let Some(Ok(token)) = parts.headers.get("x-admin-user").map(|v| v.to_str()) { - let session = db.admin_token_authenticate(token).await?; - db.admin_user_fetch(&session.user_id).await - } else { - Err(create_error!(NotAuthenticated)) - } - } -} diff --git a/crates/core/database/src/models/admin_users/mod.rs b/crates/core/database/src/models/admin_users/mod.rs index 7b1a1988..23bf00b6 100644 --- a/crates/core/database/src/models/admin_users/mod.rs +++ b/crates/core/database/src/models/admin_users/mod.rs @@ -1,12 +1,8 @@ -#[cfg(feature = "axum-impl")] -mod axum; mod models; mod ops; #[cfg(feature = "rocket-impl")] mod rocket; -#[cfg(feature = "axum-impl")] -pub use self::axum::*; #[cfg(feature = "rocket-impl")] pub use self::rocket::*; pub use models::*; diff --git a/crates/core/database/src/models/admin_users/models.rs b/crates/core/database/src/models/admin_users/models.rs index a931331b..58d3acad 100644 --- a/crates/core/database/src/models/admin_users/models.rs +++ b/crates/core/database/src/models/admin_users/models.rs @@ -39,3 +39,31 @@ impl AdminUser { return db.admin_user_fetch_email(email).await; } } + +pub struct AdminUserPermissionFlagsValue(pub u64); + +impl AdminUserPermissionFlagsValue { + pub fn has(&self, flag: revolt_models::v0::AdminUserPermissionFlags) -> bool { + self.has_value(flag as u64) + } + pub fn has_value(&self, bit: u64) -> bool { + let mask = 1 << bit; + self.0 & mask == mask + } + + pub fn set( + &mut self, + flag: revolt_models::v0::AdminUserPermissionFlags, + toggle: bool, + ) -> &mut Self { + self.set_value(flag as u64, toggle) + } + pub fn set_value(&mut self, bit: u64, toggle: bool) -> &mut Self { + if toggle { + self.0 |= 1 << bit; + } else { + self.0 &= !(1 << bit); + } + self + } +} diff --git a/crates/core/database/src/models/admin_users/ops.rs b/crates/core/database/src/models/admin_users/ops.rs index 044f380d..046c4c8a 100644 --- a/crates/core/database/src/models/admin_users/ops.rs +++ b/crates/core/database/src/models/admin_users/ops.rs @@ -15,4 +15,6 @@ pub trait AbstractAdminUsers: Sync + Send { async fn admin_user_fetch_email(&self, email: &str) -> Result; async fn admin_user_list(&self) -> Result>; + + async fn admin_user_count(&self) -> Result; } diff --git a/crates/core/database/src/models/admin_users/ops/mongodb.rs b/crates/core/database/src/models/admin_users/ops/mongodb.rs index 2f6fd552..b694ea82 100644 --- a/crates/core/database/src/models/admin_users/ops/mongodb.rs +++ b/crates/core/database/src/models/admin_users/ops/mongodb.rs @@ -28,4 +28,8 @@ impl AbstractAdminUsers for MongoDb { async fn admin_user_list(&self) -> Result> { query!(self, find, COL, doc! {}) } + + async fn admin_user_count(&self) -> Result { + query!(self, count_documents, COL, doc! {}).map(|resp| resp as u16) + } } diff --git a/crates/core/database/src/models/admin_users/ops/reference.rs b/crates/core/database/src/models/admin_users/ops/reference.rs index dd75cdb4..1989b2d5 100644 --- a/crates/core/database/src/models/admin_users/ops/reference.rs +++ b/crates/core/database/src/models/admin_users/ops/reference.rs @@ -49,4 +49,8 @@ impl AbstractAdminUsers for ReferenceDb { let admin_users = self.admin_users.lock().await; Ok(admin_users.iter().map(|(_, u)| u).cloned().collect()) } + + async fn admin_user_count(&self) -> Result { + Ok(self.admin_users.lock().await.len() as u16) + } } diff --git a/crates/core/database/src/models/admin_users/rocket.rs b/crates/core/database/src/models/admin_users/rocket.rs index ca738b81..acba6691 100644 --- a/crates/core/database/src/models/admin_users/rocket.rs +++ b/crates/core/database/src/models/admin_users/rocket.rs @@ -31,9 +31,13 @@ impl<'r> FromRequest<'r> for AdminUser { .await; if let Some(user) = user { - Outcome::Success(user.clone()) + if !user.active { + Outcome::Error((Status::Unauthorized, authifier::Error::LockedOut)) + } else { + Outcome::Success(user.clone()) + } } else { - Outcome::Error((Status::Unauthorized, authifier::Error::InvalidSession)) + Outcome::Error((Status::Unauthorized, authifier::Error::InvalidCredentials)) } } } diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index de64a4bf..add70bab 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -617,6 +617,7 @@ impl From for Report { additional_context: value.additional_context, status: value.status, notes: value.notes, + case_id: value.case_id, } } } @@ -1387,3 +1388,72 @@ impl From for crate::FieldsMessage { } } } + +impl From for AdminUser { + fn from(value: crate::AdminUser) -> Self { + AdminUser { + id: value.id, + platform_user_id: value.platform_user_id, + email: value.email, + active: value.active, + permissions: value.permissions, + revolt_user: None, + } + } +} + +impl From for crate::AdminUser { + fn from(value: AdminUser) -> Self { + crate::AdminUser { + id: value.id, + platform_user_id: value.platform_user_id, + email: value.email, + active: value.active, + permissions: value.permissions, + } + } +} + +impl From for crate::PartialAdminUser { + fn from(value: AdminUserEdit) -> Self { + crate::PartialAdminUser { + id: None, + platform_user_id: value.platform_user_id, + email: value.email, + active: value.active, + permissions: value.permissions, + } + } +} + +impl From for crate::AdminToken { + fn from(value: AdminToken) -> Self { + crate::AdminToken { + id: value.id, + user_id: value.user_id, + token: value.token, + expiry: value.expiry.format_short().to_string(), + } + } +} + +impl From for AdminToken { + fn from(value: crate::AdminToken) -> Self { + AdminToken { + id: value.id, + user_id: value.user_id, + token: value.token, + expiry: Timestamp::parse(&value.expiry).unwrap(), // if it's invalid it shouldn't have made it to this point. + } + } +} + +impl From for AdminObjectNote { + fn from(value: crate::AdminObjectNote) -> Self { + AdminObjectNote { + edited_at: Timestamp::parse(&value.edited_at).unwrap(), // if it's invalid it shouldn't have made it to this point. + last_edited_by_id: value.last_edited_by_id, + content: value.content, + } + } +} diff --git a/crates/core/database/src/util/reference.rs b/crates/core/database/src/util/reference.rs index a0aefc40..bac522c7 100644 --- a/crates/core/database/src/util/reference.rs +++ b/crates/core/database/src/util/reference.rs @@ -10,7 +10,8 @@ use schemars::{ }; use crate::{ - Bot, Channel, Database, Emoji, Invite, Member, Message, Server, ServerBan, User, Webhook, + AdminToken, Bot, Channel, Database, Emoji, Invite, Member, Message, Server, ServerBan, User, + Webhook, }; /// Reference to some object in the database @@ -25,6 +26,11 @@ impl<'a> Reference<'a> { Reference { id } } + /// Fetch Admin Token from Ref + pub async fn as_admin_token(&self, db: &Database) -> Result { + db.admin_token_fetch(&self.id).await + } + /// Fetch ban from Ref pub async fn as_ban(&self, db: &Database, server: &str) -> Result { db.fetch_ban(server, self.id).await diff --git a/crates/core/models/src/v0/admin.rs b/crates/core/models/src/v0/admin.rs index 2dd0668c..ed8a6a07 100644 --- a/crates/core/models/src/v0/admin.rs +++ b/crates/core/models/src/v0/admin.rs @@ -1,6 +1,6 @@ use iso8601_timestamp::Timestamp; -use crate::v0::Report; +use crate::v0::{Report, User}; auto_derived! { #[derive(Default)] @@ -211,9 +211,6 @@ auto_derived! { #[cfg_attr(feature = "validator", derive(validator::Validate))] pub struct AdminTokenCreate { - /// the user who this token is for - #[cfg_attr(feature = "serde", serde(rename="user"))] - pub user_id: String, /// The expiry timestamp for this token, in iso6801. Max 30 days from current time. pub expiry: Timestamp, } @@ -229,6 +226,9 @@ auto_derived! { pub active: bool, /// The permissions of the user pub permissions: u64, + /// The Revolt user attached to this user. Use this for data like names and avatars. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub revolt_user: Option } #[cfg_attr(feature = "validator", derive(validator::Validate))] @@ -258,4 +258,31 @@ auto_derived! { /// The permissions of the user pub permissions: Option, } + + #[repr(u64)] + pub enum AdminUserPermissionFlags { + ObjectNotes = 0, + + ManageAdminUsers = 1, + CreateTokens = 2, + + ViewUsers = 3, + ManageUsers = 4, + ManageUsersSenstiveInfo = 5, + + ViewServers = 6, + ManageServers = 7, + ViewDMChannels = 8, + ManageDMChannels = 9, + + ViewCases = 10, + ManageOwnCases = 11, + ManageOtherCases = 12, + + ViewReports = 13, + + Search = 14, + ManageNotes = 15, + Discover = 16, + } } diff --git a/crates/delta/src/routes/admin/accounts/account_delete.rs b/crates/delta/src/routes/admin/accounts/account_delete.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/accounts/account_disable.rs b/crates/delta/src/routes/admin/accounts/account_disable.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/accounts/account_email_change.rs b/crates/delta/src/routes/admin/accounts/account_email_change.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/accounts/account_email_verify.rs b/crates/delta/src/routes/admin/accounts/account_email_verify.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/accounts/account_enable.rs b/crates/delta/src/routes/admin/accounts/account_enable.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/accounts/account_mfa_reset.rs b/crates/delta/src/routes/admin/accounts/account_mfa_reset.rs new file mode 100644 index 00000000..9d1a4a56 --- /dev/null +++ b/crates/delta/src/routes/admin/accounts/account_mfa_reset.rs @@ -0,0 +1 @@ +// This should allow resetting of all factors or individual ones. diff --git a/crates/delta/src/routes/admin/accounts/account_reset_lockout.rs b/crates/delta/src/routes/admin/accounts/account_reset_lockout.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/accounts/mod.rs b/crates/delta/src/routes/admin/accounts/mod.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/delta/src/routes/admin/accounts/mod.rs @@ -0,0 +1 @@ + diff --git a/crates/delta/src/routes/admin/cases/actions/case_add_comment.rs b/crates/delta/src/routes/admin/cases/actions/case_add_comment.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/delta/src/routes/admin/cases/actions/case_add_comment.rs @@ -0,0 +1 @@ + diff --git a/crates/delta/src/routes/admin/cases/actions/case_create.rs b/crates/delta/src/routes/admin/cases/actions/case_create.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/cases/actions/case_edit_tags.rs b/crates/delta/src/routes/admin/cases/actions/case_edit_tags.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/cases/actions/case_rename.rs b/crates/delta/src/routes/admin/cases/actions/case_rename.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/cases/actions/case_send_notification.rs b/crates/delta/src/routes/admin/cases/actions/case_send_notification.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/cases/actions/case_state.rs b/crates/delta/src/routes/admin/cases/actions/case_state.rs new file mode 100644 index 00000000..64058a86 --- /dev/null +++ b/crates/delta/src/routes/admin/cases/actions/case_state.rs @@ -0,0 +1 @@ +//close/reopen a case diff --git a/crates/delta/src/routes/admin/cases/actions/mod.rs b/crates/delta/src/routes/admin/cases/actions/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/cases/fetch/case_get.rs b/crates/delta/src/routes/admin/cases/fetch/case_get.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/delta/src/routes/admin/cases/fetch/case_get.rs @@ -0,0 +1 @@ + diff --git a/crates/delta/src/routes/admin/cases/fetch/case_get_all_open.rs b/crates/delta/src/routes/admin/cases/fetch/case_get_all_open.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/cases/fetch/mod.rs b/crates/delta/src/routes/admin/cases/fetch/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/cases/mod.rs b/crates/delta/src/routes/admin/cases/mod.rs new file mode 100644 index 00000000..f6d4d6e6 --- /dev/null +++ b/crates/delta/src/routes/admin/cases/mod.rs @@ -0,0 +1,2 @@ +mod actions; +mod fetch; diff --git a/crates/delta/src/routes/admin/channels/actions/channel_delete.rs b/crates/delta/src/routes/admin/channels/actions/channel_delete.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/channels/actions/channel_edit.rs b/crates/delta/src/routes/admin/channels/actions/channel_edit.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/channels/actions/channel_wipe.rs b/crates/delta/src/routes/admin/channels/actions/channel_wipe.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/channels/actions/mod.rs b/crates/delta/src/routes/admin/channels/actions/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/channels/fetch/channel_get.rs b/crates/delta/src/routes/admin/channels/fetch/channel_get.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/channels/fetch/channel_get_participants.rs b/crates/delta/src/routes/admin/channels/fetch/channel_get_participants.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/channels/fetch/mod.rs b/crates/delta/src/routes/admin/channels/fetch/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/channels/mod.rs b/crates/delta/src/routes/admin/channels/mod.rs new file mode 100644 index 00000000..f6d4d6e6 --- /dev/null +++ b/crates/delta/src/routes/admin/channels/mod.rs @@ -0,0 +1,2 @@ +mod actions; +mod fetch; diff --git a/crates/delta/src/routes/admin/messages/delete.rs b/crates/delta/src/routes/admin/messages/delete.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/meta/create_token.rs b/crates/delta/src/routes/admin/meta/create_token.rs new file mode 100644 index 00000000..11f16d20 --- /dev/null +++ b/crates/delta/src/routes/admin/meta/create_token.rs @@ -0,0 +1,38 @@ +use iso8601_timestamp::{Duration, Timestamp}; +use revolt_database::{AdminMachineToken, AdminToken, Database}; +use revolt_models::v0::{self}; +use revolt_result::{create_error, Result}; +use rocket::{serde::json::Json, State}; + +use crate::routes::admin::util::user_has_permission; + +/// Create a token for your account. Must have the CreateTokens permission +#[openapi(tag = "Admin")] +#[post("/tokens", data = "")] +pub async fn admin_create_token( + db: &State, + auth: AdminMachineToken, + body: Json, +) -> Result> { + if !user_has_permission( + &auth.on_behalf_of, + v0::AdminUserPermissionFlags::CreateTokens, + ) { + return Err(create_error!(NotFound)); + } + + if body.expiry > Timestamp::now_utc() + Duration::days(30) { + return Err(create_error!(FailedValidation { + error: "The expiry is more than 30 days away.".to_string() + })); + } + + let token = AdminToken::new(&auth.on_behalf_of.id, body.expiry); + db.admin_token_create(token.clone()).await?; + Ok(Json(v0::AdminToken { + id: token.id, + user_id: token.user_id, + token: token.token, + expiry: body.expiry, + })) +} diff --git a/crates/delta/src/routes/admin/meta/create_user.rs b/crates/delta/src/routes/admin/meta/create_user.rs new file mode 100644 index 00000000..93e05f56 --- /dev/null +++ b/crates/delta/src/routes/admin/meta/create_user.rs @@ -0,0 +1,26 @@ +use revolt_database::{AdminAuthorization, AdminUser, Database}; +use revolt_models::v0::{self}; +use revolt_result::{create_error, Result}; +use rocket::{serde::json::Json, State}; + +use crate::routes::admin::util::{flatten_authorized_user, user_has_permission}; + +/// Edit an admin user. Requires ManageAdminUsers flag. +#[openapi(tag = "Admin")] +#[post("/users", data = "")] +pub async fn admin_create_user( + db: &State, + auth: AdminAuthorization, + body: Json, +) -> Result> { + let user = flatten_authorized_user(&auth); + if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageAdminUsers) { + return Err(create_error!(NotFound)); + } + + // TODO: technically there's a privilege escalation here since anyone with the manageAdminUsers permission can assign whatever permissions they want. + // But this is built with the assumption that people with ManageAdminUsers are already privileged. + let user = AdminUser::new(&body.email, &body.platform_user_id, body.permissions); + db.admin_user_insert(user.clone()).await?; + Ok(Json(user.into())) +} diff --git a/crates/delta/src/routes/admin/meta/edit_user.rs b/crates/delta/src/routes/admin/meta/edit_user.rs new file mode 100644 index 00000000..4d138cbf --- /dev/null +++ b/crates/delta/src/routes/admin/meta/edit_user.rs @@ -0,0 +1,27 @@ +use revolt_database::{util::reference::Reference, AdminAuthorization, Database}; +use revolt_models::v0::{self}; +use revolt_result::{create_error, Result}; +use rocket::{serde::json::Json, State}; +use rocket_empty::EmptyResponse; + +use crate::routes::admin::util::{flatten_authorized_user, user_has_permission}; + +/// Edit an admin user. Requires ManageAdminUsers flag. +#[openapi(tag = "Admin")] +#[patch("/users/", data = "")] +pub async fn admin_edit_user( + db: &State, + auth: AdminAuthorization, + target: Reference, + body: Json, +) -> Result { + let user = flatten_authorized_user(&auth); + if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageAdminUsers) { + return Err(create_error!(NotFound)); + } + + // TODO: technically there's a privilege escalation here since anyone with the manageAdminUsers permission can assign whatever permissions they want. + // But this is built with the assumption that people with ManageAdminUsers are already privileged. + db.admin_user_update(&target.id, body.0.into()).await?; + Ok(EmptyResponse) +} diff --git a/crates/delta/src/routes/admin/meta/fetch_users.rs b/crates/delta/src/routes/admin/meta/fetch_users.rs new file mode 100644 index 00000000..68f62c45 --- /dev/null +++ b/crates/delta/src/routes/admin/meta/fetch_users.rs @@ -0,0 +1,29 @@ +use revolt_database::{AdminAuthorization, Database}; +use revolt_models::v0::AdminUser; +use revolt_result::Result; +use rocket::{serde::json::Json, State}; + +/// Get a list of admin users. Any active user may use this endpoint. +/// Typically the client should cache this data. +#[openapi(tag = "Admin")] +#[get("/users")] +pub async fn admin_fetch_users( + db: &State, + auth: AdminAuthorization, +) -> Result>> { + let users = db.admin_user_list().await?; + let userids: Vec = users.iter().map(|u| u.platform_user_id.clone()).collect(); + let revolt_users = db.fetch_users(&userids).await?; + + let mut resp = vec![]; + for admin_user in users { + let mut user = AdminUser::from(admin_user); + user.revolt_user = match revolt_users.iter().find(|ru| ru.id == user.id) { + Some(some_user) => Some(some_user.clone().into_self(false).await), + None => None, + }; + resp.push(user); + } + + Ok(Json(resp)) +} diff --git a/crates/delta/src/routes/admin/meta/mod.rs b/crates/delta/src/routes/admin/meta/mod.rs new file mode 100644 index 00000000..8d2cbac0 --- /dev/null +++ b/crates/delta/src/routes/admin/meta/mod.rs @@ -0,0 +1,5 @@ +pub mod create_token; +pub mod create_user; +pub mod edit_user; +pub mod fetch_users; +pub mod revoke_token; diff --git a/crates/delta/src/routes/admin/meta/revoke_token.rs b/crates/delta/src/routes/admin/meta/revoke_token.rs new file mode 100644 index 00000000..7df8ad19 --- /dev/null +++ b/crates/delta/src/routes/admin/meta/revoke_token.rs @@ -0,0 +1,39 @@ +use revolt_database::{util::reference::Reference, AdminMachineToken, Database}; +use revolt_models::v0::{self}; +use revolt_result::{create_error, Result}; +use rocket::State; +use rocket_empty::EmptyResponse; + +use crate::routes::admin::util::user_has_permission; + +/// Revoke a token. Must be your own, or you must have the ManageAdminUsers permission to revoke other's tokens. +/// You must have the CreateTokens permission. +#[openapi(tag = "Admin")] +#[delete("/tokens/")] +pub async fn admin_revoke_token( + db: &State, + auth: AdminMachineToken, + token: Reference, +) -> Result { + if !user_has_permission( + &auth.on_behalf_of, + v0::AdminUserPermissionFlags::CreateTokens, + ) { + return Err(create_error!(NotFound)); + } + + let token = token.as_admin_token(db).await?; + + // only own user and those with ManageAdminUsers can revoke a token + if token.user_id != auth.on_behalf_of.id + && !user_has_permission( + &auth.on_behalf_of, + v0::AdminUserPermissionFlags::ManageAdminUsers, + ) + { + return Err(create_error!(NotFound)); + } + + db.admin_token_revoke(&token.id).await?; + Ok(EmptyResponse) +} diff --git a/crates/delta/src/routes/admin/mod.rs b/crates/delta/src/routes/admin/mod.rs new file mode 100644 index 00000000..127c1b6d --- /dev/null +++ b/crates/delta/src/routes/admin/mod.rs @@ -0,0 +1,26 @@ +use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi; +use rocket::Route; + +mod accounts; +mod cases; +mod meta; +mod reports; +mod roles; +mod search; +mod users; + +mod object_edit_note; +mod object_get_note; +mod util; + +pub fn routes() -> (Vec, OpenApi) { + openapi_get_routes_spec![ + object_edit_note::admin_object_edit_note, + object_get_note::admin_object_get_note, + meta::create_user::admin_create_user, + meta::edit_user::admin_edit_user, + meta::fetch_users::admin_fetch_users, + meta::create_token::admin_create_token, + meta::revoke_token::admin_revoke_token + ] +} diff --git a/crates/delta/src/routes/admin/object_edit_note.rs b/crates/delta/src/routes/admin/object_edit_note.rs new file mode 100644 index 00000000..fb619f6f --- /dev/null +++ b/crates/delta/src/routes/admin/object_edit_note.rs @@ -0,0 +1,28 @@ +use revolt_database::{util::reference::Reference, AdminAuthorization, AdminObjectNote, Database}; +use revolt_models::v0::{self}; +use revolt_result::{create_error, Result}; +use rocket::{serde::json::Json, State}; +use rocket_empty::EmptyResponse; + +use crate::routes::admin::util::{flatten_authorized_user, user_has_permission}; + +/// Edit an object note. Requires ObjectNotes permission. +#[openapi(tag = "Admin")] +#[post("/notes/", data = "")] +pub async fn admin_object_edit_note( + db: &State, + auth: AdminAuthorization, + object: Reference, + body: Json, +) -> Result { + let user = flatten_authorized_user(&auth); + if !user_has_permission(user, v0::AdminUserPermissionFlags::ObjectNotes) { + return Err(create_error!(NotFound)); + } + + // Unfortunately due to how our system works, we can't limit users to editing objects they have permissions for (eg users, servers, etc.). + // Hence it being tied to its own permission + db.admin_note_update(AdminObjectNote::new(&object.id, &user.id, &body.content)) + .await?; + Ok(EmptyResponse) +} diff --git a/crates/delta/src/routes/admin/object_get_note.rs b/crates/delta/src/routes/admin/object_get_note.rs new file mode 100644 index 00000000..2b3cf0b3 --- /dev/null +++ b/crates/delta/src/routes/admin/object_get_note.rs @@ -0,0 +1,24 @@ +use revolt_database::{util::reference::Reference, AdminAuthorization, Database}; +use revolt_models::v0::{self}; +use revolt_result::{create_error, Result}; +use rocket::{serde::json::Json, State}; + +use crate::routes::admin::util::{flatten_authorized_user, user_has_permission}; + +/// Edit an object note. Requires ObjectNotes permission. +#[openapi(tag = "Admin")] +#[get("/notes/")] +pub async fn admin_object_get_note( + db: &State, + auth: AdminAuthorization, + object: Reference, +) -> Result> { + let user = flatten_authorized_user(&auth); + if !user_has_permission(user, v0::AdminUserPermissionFlags::ObjectNotes) { + return Err(create_error!(NotFound)); + } + + // Unfortunately due to how our system works, we can't limit users to editing objects they have permissions for (eg users, servers, etc.). + // Hence it being tied to its own permission + Ok(Json(db.admin_note_fetch(&object.id).await?.into())) +} diff --git a/crates/delta/src/routes/admin/reports/actions/mod.rs b/crates/delta/src/routes/admin/reports/actions/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/reports/actions/report_assign.rs b/crates/delta/src/routes/admin/reports/actions/report_assign.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/reports/actions/report_create.rs b/crates/delta/src/routes/admin/reports/actions/report_create.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/reports/actions/report_edit.rs b/crates/delta/src/routes/admin/reports/actions/report_edit.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/reports/actions/reports_bulk_assign.rs b/crates/delta/src/routes/admin/reports/actions/reports_bulk_assign.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/reports/fetch/mod.rs b/crates/delta/src/routes/admin/reports/fetch/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/reports/fetch/reports_unassigned.rs b/crates/delta/src/routes/admin/reports/fetch/reports_unassigned.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/reports/mod.rs b/crates/delta/src/routes/admin/reports/mod.rs new file mode 100644 index 00000000..f6d4d6e6 --- /dev/null +++ b/crates/delta/src/routes/admin/reports/mod.rs @@ -0,0 +1,2 @@ +mod actions; +mod fetch; diff --git a/crates/delta/src/routes/admin/roles/mod.rs b/crates/delta/src/routes/admin/roles/mod.rs new file mode 100644 index 00000000..8bc36cc3 --- /dev/null +++ b/crates/delta/src/routes/admin/roles/mod.rs @@ -0,0 +1,3 @@ +mod role_create; +mod role_delete; +mod role_edit; diff --git a/crates/delta/src/routes/admin/roles/role_create.rs b/crates/delta/src/routes/admin/roles/role_create.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/roles/role_delete.rs b/crates/delta/src/routes/admin/roles/role_delete.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/roles/role_edit.rs b/crates/delta/src/routes/admin/roles/role_edit.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/search/mod.rs b/crates/delta/src/routes/admin/search/mod.rs new file mode 100644 index 00000000..643b67e1 --- /dev/null +++ b/crates/delta/src/routes/admin/search/mod.rs @@ -0,0 +1 @@ +mod search_everything; diff --git a/crates/delta/src/routes/admin/search/search_everything.rs b/crates/delta/src/routes/admin/search/search_everything.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/actions/mod.rs b/crates/delta/src/routes/admin/servers/actions/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/actions/server_add_members.rs b/crates/delta/src/routes/admin/servers/actions/server_add_members.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/actions/server_change_owner.rs b/crates/delta/src/routes/admin/servers/actions/server_change_owner.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/actions/server_create_invite.rs b/crates/delta/src/routes/admin/servers/actions/server_create_invite.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/actions/server_delete.rs b/crates/delta/src/routes/admin/servers/actions/server_delete.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/actions/server_delete_invites.rs b/crates/delta/src/routes/admin/servers/actions/server_delete_invites.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/actions/server_edit.rs b/crates/delta/src/routes/admin/servers/actions/server_edit.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/actions/server_remove_members.rs b/crates/delta/src/routes/admin/servers/actions/server_remove_members.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/actions/server_set_flags.rs b/crates/delta/src/routes/admin/servers/actions/server_set_flags.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/fetch/mod.rs b/crates/delta/src/routes/admin/servers/fetch/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/fetch/server_get.rs b/crates/delta/src/routes/admin/servers/fetch/server_get.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/fetch/server_get_participants.rs b/crates/delta/src/routes/admin/servers/fetch/server_get_participants.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/servers/mod.rs b/crates/delta/src/routes/admin/servers/mod.rs new file mode 100644 index 00000000..f6d4d6e6 --- /dev/null +++ b/crates/delta/src/routes/admin/servers/mod.rs @@ -0,0 +1,2 @@ +mod actions; +mod fetch; diff --git a/crates/delta/src/routes/admin/users/actions/mod.rs b/crates/delta/src/routes/admin/users/actions/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/actions/user_ban.rs b/crates/delta/src/routes/admin/users/actions/user_ban.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/actions/user_data_package.rs b/crates/delta/src/routes/admin/users/actions/user_data_package.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/actions/user_reset_profile.rs b/crates/delta/src/routes/admin/users/actions/user_reset_profile.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/actions/user_send_alert.rs b/crates/delta/src/routes/admin/users/actions/user_send_alert.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/actions/user_strike.rs b/crates/delta/src/routes/admin/users/actions/user_strike.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/actions/user_suspend.rs b/crates/delta/src/routes/admin/users/actions/user_suspend.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/actions/user_unban.rs b/crates/delta/src/routes/admin/users/actions/user_unban.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/actions/user_unsuspend.rs b/crates/delta/src/routes/admin/users/actions/user_unsuspend.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/actions/user_wipe_messages.rs b/crates/delta/src/routes/admin/users/actions/user_wipe_messages.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/fetch/mod.rs b/crates/delta/src/routes/admin/users/fetch/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/fetch/user_get.rs b/crates/delta/src/routes/admin/users/fetch/user_get.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/delta/src/routes/admin/users/mod.rs b/crates/delta/src/routes/admin/users/mod.rs new file mode 100644 index 00000000..f6d4d6e6 --- /dev/null +++ b/crates/delta/src/routes/admin/users/mod.rs @@ -0,0 +1,2 @@ +mod actions; +mod fetch; diff --git a/crates/delta/src/routes/admin/util.rs b/crates/delta/src/routes/admin/util.rs new file mode 100644 index 00000000..0fc72128 --- /dev/null +++ b/crates/delta/src/routes/admin/util.rs @@ -0,0 +1,15 @@ +use revolt_database::{AdminAuthorization, AdminUser, AdminUserPermissionFlagsValue}; +use revolt_models::v0::AdminUserPermissionFlags; + +pub fn user_has_permission(user: &AdminUser, permission: AdminUserPermissionFlags) -> bool { + let flags = AdminUserPermissionFlagsValue(user.permissions); + + flags.has(permission) +} + +pub fn flatten_authorized_user<'a>(auth: &'a AdminAuthorization) -> &'a AdminUser { + match auth { + AdminAuthorization::AdminUser(admin_user) => &admin_user, + AdminAuthorization::AdminMachine(admin_machine_token) => &admin_machine_token.on_behalf_of, + } +} diff --git a/crates/delta/src/routes/mod.rs b/crates/delta/src/routes/mod.rs index 9643a365..dc67158a 100644 --- a/crates/delta/src/routes/mod.rs +++ b/crates/delta/src/routes/mod.rs @@ -4,6 +4,7 @@ pub use rocket::http::Status; pub use rocket::response::Redirect; use rocket::{Build, Rocket}; +mod admin; mod bots; mod channels; mod customisation; @@ -21,89 +22,79 @@ mod webhooks; pub fn mount(config: Settings, mut rocket: Rocket) -> Rocket { let settings = OpenApiSettings::default(); - if config.features.webhooks_enabled { - mount_endpoints_and_merged_docs! { - rocket, "/".to_owned(), settings, - "/" => (vec![], custom_openapi_spec()), - "" => openapi_get_routes_spec![root::root], - "/users" => users::routes(), - "/bots" => bots::routes(), - "/channels" => channels::routes(), - "/servers" => servers::routes(), - "/invites" => invites::routes(), - "/custom" => customisation::routes(), - "/safety" => safety::routes(), - "/auth/account" => rocket_authifier::routes::account::routes(), - "/auth/session" => rocket_authifier::routes::session::routes(), - "/auth/mfa" => rocket_authifier::routes::mfa::routes(), - "/onboard" => onboard::routes(), - "/policy" => policy::routes(), - "/push" => push::routes(), - "/sync" => sync::routes(), - "/webhooks" => webhooks::routes() - }; - } else { - mount_endpoints_and_merged_docs! { - rocket, "/".to_owned(), settings, - "/" => (vec![], custom_openapi_spec()), - "" => openapi_get_routes_spec![root::root], - "/users" => users::routes(), - "/bots" => bots::routes(), - "/channels" => channels::routes(), - "/servers" => servers::routes(), - "/invites" => invites::routes(), - "/custom" => customisation::routes(), - "/safety" => safety::routes(), - "/auth/account" => rocket_authifier::routes::account::routes(), - "/auth/session" => rocket_authifier::routes::session::routes(), - "/auth/mfa" => rocket_authifier::routes::mfa::routes(), - "/onboard" => onboard::routes(), - "/policy" => policy::routes(), - "/push" => push::routes(), - "/sync" => sync::routes() - }; + let mut openapi_list: Vec<(_, revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi)> = + Vec::new(); + + let routes = vec![ + ("/", (vec![], custom_openapi_spec()), true, true), + ("/", openapi_get_routes_spec![root::root], true, true), + ("/users", users::routes(), true, true), + ("/bots", bots::routes(), true, true), + ("/channels", channels::routes(), true, true), + ("/servers", servers::routes(), true, true), + ("/invites", invites::routes(), true, true), + ("/custom", customisation::routes(), true, true), + ("/safety", safety::routes(), true, true), + ( + "/auth/mfa", + rocket_authifier::routes::mfa::routes(), + true, + true, + ), + ("/onboard", onboard::routes(), true, true), + ("/policy", policy::routes(), true, true), + ("/push", push::routes(), true, true), + ("/sync", sync::routes(), true, true), + ( + "/auth/account", + rocket_authifier::routes::account::routes(), + true, + true, + ), + ( + "/auth/session", + rocket_authifier::routes::session::routes(), + true, + true, + ), + ( + "/admin", + admin::routes(), + config.features.admin_api_enabled, + config.features.admin_api_show_spec, + ), + ( + "/webhooks", + webhooks::routes(), + config.features.webhooks_enabled, + true, + ), + ]; + + for (base, (routes, openapi), enabled, show_spec) in routes { + if !enabled { + continue; + } + + rocket = rocket.mount(base, routes); + if show_spec { + openapi_list.push((base, openapi)); + } } - if config.features.webhooks_enabled { - mount_endpoints_and_merged_docs! { - rocket, "/0.8".to_owned(), settings, - "/" => (vec![], custom_openapi_spec()), - "" => openapi_get_routes_spec![root::root], - "/users" => users::routes(), - "/bots" => bots::routes(), - "/channels" => channels::routes(), - "/servers" => servers::routes(), - "/invites" => invites::routes(), - "/custom" => customisation::routes(), - "/safety" => safety::routes(), - "/auth/account" => rocket_authifier::routes::account::routes(), - "/auth/session" => rocket_authifier::routes::session::routes(), - "/auth/mfa" => rocket_authifier::routes::mfa::routes(), - "/onboard" => onboard::routes(), - "/push" => push::routes(), - "/sync" => sync::routes(), - "/webhooks" => webhooks::routes() + let openapi_docs = + match revolt_rocket_okapi::revolt_okapi::merge::marge_spec_list(&openapi_list) { + Ok(docs) => docs, + Err(err) => panic!("Could not merge OpenAPI spec: {}", err), }; - } else { - mount_endpoints_and_merged_docs! { - rocket, "/0.8".to_owned(), settings, - "/" => (vec![], custom_openapi_spec()), - "" => openapi_get_routes_spec![root::root], - "/users" => users::routes(), - "/bots" => bots::routes(), - "/channels" => channels::routes(), - "/servers" => servers::routes(), - "/invites" => invites::routes(), - "/custom" => customisation::routes(), - "/safety" => safety::routes(), - "/auth/account" => rocket_authifier::routes::account::routes(), - "/auth/session" => rocket_authifier::routes::session::routes(), - "/auth/mfa" => rocket_authifier::routes::mfa::routes(), - "/onboard" => onboard::routes(), - "/push" => push::routes(), - "/sync" => sync::routes() - }; - } + + rocket = rocket.mount( + "/", + vec![revolt_rocket_okapi::get_openapi_route( + openapi_docs, + &settings, + )], + ); rocket } @@ -238,11 +229,6 @@ fn custom_openapi_spec() -> OpenApi { description: Some("Local Revolt Environment".to_owned()), ..Default::default() }, - Server { - url: "http://local.revolt.chat:14702/0.8".to_owned(), - description: Some("Local Revolt Environment (v0.8)".to_owned()), - ..Default::default() - }, ], external_docs: Some(ExternalDocs { url: "https://developers.revolt.chat".to_owned(), @@ -251,6 +237,13 @@ fn custom_openapi_spec() -> OpenApi { }), extensions, tags: vec![ + Tag { + name: "Admin".to_owned(), + description: Some( + "Used when running your own instance to manage and moderate".to_owned(), + ), + ..Default::default() + }, Tag { name: "Core".to_owned(), description: Some( diff --git a/crates/delta/src/routes/safety/report_content.rs b/crates/delta/src/routes/safety/report_content.rs index 1d5c2356..c1642ea0 100644 --- a/crates/delta/src/routes/safety/report_content.rs +++ b/crates/delta/src/routes/safety/report_content.rs @@ -123,6 +123,7 @@ pub async fn report_content( additional_context: data.additional_context, status: ReportStatus::Created {}, notes: String::new(), + case_id: None, }; db.insert_report(&report).await?;