diff --git a/src/database/entities/server.rs b/src/database/entities/server.rs index 4fad9551f0437ea6a4eac3951b630929500bc1e3..069a4ec4e201628bac2f86aaf786f6ccb67dec23 100644 --- a/src/database/entities/server.rs +++ b/src/database/entities/server.rs @@ -30,7 +30,7 @@ pub struct Member { pub struct Ban { #[serde(rename = "_id")] pub id: MemberCompositeKey, - pub reason: String, + pub reason: Option<String>, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -234,6 +234,23 @@ impl Server { } pub async fn join_member(&self, id: &str) -> Result<()> { + if get_collection("server_bans") + .find_one( + doc! { + "_id.server": &self.id, + "_id.user": &id + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "find_one", + with: "server_bans", + })? + .is_some() { + return Err(Error::Banned) + } + get_collection("server_members") .insert_one( doc! { @@ -260,7 +277,7 @@ impl Server { } pub async fn remove_member(&self, id: &str) -> Result<()> { - get_collection("server_members") + let result = get_collection("server_members") .delete_one( doc! { "_id": { @@ -276,11 +293,13 @@ impl Server { with: "server_members", })?; - ClientboundNotification::ServerMemberLeave { - id: self.id.clone(), - user: id.to_string() + if result.deleted_count > 0 { + ClientboundNotification::ServerMemberLeave { + id: self.id.clone(), + user: id.to_string() + } + .publish(self.id.clone()); } - .publish(self.id.clone()); Ok(()) } diff --git a/src/database/guards/reference.rs b/src/database/guards/reference.rs index 38a2be05123a2944b21ffe32532b6c5b450424e9..3fe6e06f135f16ae74bfb6373850dbda3cd8fd22 100644 --- a/src/database/guards/reference.rs +++ b/src/database/guards/reference.rs @@ -25,7 +25,7 @@ impl Ref { Ok(r) } - pub async fn fetch<T: DeserializeOwned>(&self, collection: &'static str) -> Result<T> { + async fn fetch<T: DeserializeOwned>(&self, collection: &'static str) -> Result<T> { let doc = get_collection(&collection) .find_one( doc! { @@ -84,6 +84,28 @@ impl Ref { })?) } + pub async fn fetch_ban(&self, server: &str) -> Result<Ban> { + let doc = get_collection("server_bans") + .find_one( + doc! { + "_id.user": &self.id, + "_id.server": server + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "find_one", + with: "server_ban", + })? + .ok_or_else(|| Error::NotFound)?; + + Ok(from_document::<Ban>(doc).map_err(|_| Error::DatabaseError { + operation: "from_document", + with: "server_ban", + })?) + } + pub async fn fetch_message(&self, channel: &Channel) -> Result<Message> { let message: Message = self.fetch("messages").await?; if &message.channel != channel.id() { diff --git a/src/database/permissions/server.rs b/src/database/permissions/server.rs index 276e2483a3c3c0df09d0f1ad3345cbd3b2429464..c4af2dda57ac28a115e95f3c12ace61c4fc2af10 100644 --- a/src/database/permissions/server.rs +++ b/src/database/permissions/server.rs @@ -12,7 +12,9 @@ pub enum ServerPermission { ManageMembers = 0b00000000000000000000000000000010, // 2 ManageChannels = 0b00000000000000000000000000000100, // 4 ManageServer = 0b00000000000000000000000000001000, // 8 - // 8 bits of space + KickMembers = 0b00000000000000000000000000010000, // 16 + BanMembers = 0b00000000000000000000000000100000, // 32 + // 6 bits of space ChangeNickname = 0b00000000000000000001000000000000, // 4096 ManageNicknames = 0b00000000000000000010000000000000, // 8192 ChangeAvatar = 0b00000000000000000100000000000000, // 16392 @@ -30,6 +32,8 @@ bitfield! { pub get_manage_members, _: 30; pub get_manage_channels, _: 29; pub get_manage_server, _: 28; + pub get_kick_members, _: 27; + pub get_ban_members, _: 26; pub get_change_nickname, _: 19; pub get_manage_nicknames, _: 18; diff --git a/src/routes/servers/ban_create.rs b/src/routes/servers/ban_create.rs new file mode 100644 index 0000000000000000000000000000000000000000..76a7428fc41a44c333e5d77b6296dbdab2b19c7f --- /dev/null +++ b/src/routes/servers/ban_create.rs @@ -0,0 +1,56 @@ +use crate::database::*; +use crate::util::result::{Error, Result}; + +use mongodb::bson::doc; +use validator::Validate; +use rocket_contrib::json::Json; +use serde::{Serialize, Deserialize}; + +#[derive(Validate, Serialize, Deserialize)] +pub struct Data { + #[validate(length(min = 1, max = 1024))] + reason: Option<String>, +} + +#[put("/<server>/bans/<target>", data = "<data>")] +pub async fn req(user: User, server: Ref, target: Ref, data: Json<Data>) -> Result<()> { + let data = data.into_inner(); + data.validate() + .map_err(|error| Error::FailedValidation { error })?; + + let server = server.fetch_server().await?; + + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&server) + .for_server() + .await?; + + if !perm.get_ban_members() { + Err(Error::MissingPermission)? + } + + let target = target.fetch_user().await?; + let mut document = doc! { + "_id": { + "server": &server.id, + "user": &target.id + } + }; + + if let Some(reason) = data.reason { + document.insert("reason", reason); + } + + get_collection("server_bans") + .insert_one( + document, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "insert_one", + with: "server_ban" + })?; + + server.remove_member(&target.id).await +} diff --git a/src/routes/servers/ban_list.rs b/src/routes/servers/ban_list.rs new file mode 100644 index 0000000000000000000000000000000000000000..56899aa40919af7037d9403db6a4426b15ec450d --- /dev/null +++ b/src/routes/servers/ban_list.rs @@ -0,0 +1,44 @@ +use crate::database::*; +use crate::util::result::{Error, Result}; + +use futures::StreamExt; +use rocket_contrib::json::JsonValue; +use mongodb::bson::{doc, from_document}; + +#[get("/<target>/bans")] +pub async fn req(user: User, target: Ref) -> Result<JsonValue> { + let target = target.fetch_server().await?; + + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&target) + .for_server() + .await?; + + if !perm.get_manage_members() { + Err(Error::MissingPermission)? + } + + let mut cursor = get_collection("server_bans") + .find( + doc! { + "_id.server": target.id + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "find", + with: "server_bans", + })?; + + let mut bans = vec![]; + while let Some(result) = cursor.next().await { + if let Ok(doc) = result { + if let Ok(ban) = from_document::<Ban>(doc) { + bans.push(ban); + } + } + } + + Ok(json!(bans)) +} diff --git a/src/routes/servers/ban_remove.rs b/src/routes/servers/ban_remove.rs new file mode 100644 index 0000000000000000000000000000000000000000..ef27a50af3a223935b6c76c4eefde8cb505bc833 --- /dev/null +++ b/src/routes/servers/ban_remove.rs @@ -0,0 +1,35 @@ +use crate::database::*; +use crate::util::result::{Error, Result}; + +use mongodb::bson::doc; + +#[delete("/<server>/bans/<target>")] +pub async fn req(user: User, server: Ref, target: Ref) -> Result<()> { + let server = server.fetch_server().await?; + + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&server) + .for_server() + .await?; + + if !perm.get_ban_members() { + Err(Error::MissingPermission)? + } + + let target = target.fetch_ban(&server.id).await?; + get_collection("server_bans") + .delete_one( + doc! { + "_id.server": &server.id, + "_id.user": &target.id.user + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "delete_one", + with: "server_ban" + })?; + + Ok(()) +} diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs index cf867c56fa86709347da5946fcf3f3c630a39e92..26638b49f0a3a39db1bafff5478c0b2ccfb7b128 100644 --- a/src/routes/servers/mod.rs +++ b/src/routes/servers/mod.rs @@ -12,6 +12,10 @@ mod member_remove; mod member_fetch; mod member_edit; +mod ban_create; +mod ban_remove; +mod ban_list; + mod invites_fetch; pub fn routes() -> Vec<Route> { @@ -25,6 +29,9 @@ pub fn routes() -> Vec<Route> { member_remove::req, member_fetch::req, member_edit::req, + ban_create::req, + ban_remove::req, + ban_list::req, invites_fetch::req ] } diff --git a/src/util/result.rs b/src/util/result.rs index 44d4bad66ec8ceebdb7cf61aa011c1e0c92d147d..b3225a0df819cc888cc1aa56bd5b87d1cb7690c8 100644 --- a/src/util/result.rs +++ b/src/util/result.rs @@ -38,6 +38,7 @@ pub enum Error { // ? Server related errors. UnknownServer, + Banned, // ? General errors. TooManyIds, @@ -87,6 +88,7 @@ impl<'r> Responder<'r, 'static> for Error { Error::NotInGroup => Status::NotFound, Error::UnknownServer => Status::NotFound, + Error::Banned => Status::Forbidden, Error::FailedValidation { .. } => Status::UnprocessableEntity, Error::DatabaseError { .. } => Status::InternalServerError,