diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index 7b8626a2..dd63bf0e 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -52,6 +52,7 @@ message_length = 2000 message_embeds = 5 message_replies = 5 message_attachments = 5 +message_reactions = 20 servers = 100 server_emoji = 100 server_roles = 200 diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 934114e1..30622370 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -101,6 +101,7 @@ pub struct FeaturesLimits { pub message_replies: usize, pub message_attachments: usize, pub message_embeds: usize, + pub message_reactions: usize, pub servers: usize, pub server_emoji: usize, pub server_roles: usize, diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index c0d39402..0cc4f069 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -7,6 +7,7 @@ use revolt_models::v0::{ self, DataMessageSend, Embed, MessageAuthor, MessageSort, MessageWebhook, PushNotification, ReplyIntent, SendableEmbed, RE_MENTION, }; +use revolt_permissions::{ChannelPermission, PermissionValue}; use revolt_result::Result; use ulid::Ulid; @@ -14,7 +15,7 @@ use crate::{ events::client::EventV1, tasks::{self, ack::AckEvent}, util::idempotency::IdempotencyKey, - Channel, Database, File, + Channel, Database, Emoji, File, }; auto_derived_partial!( @@ -353,7 +354,9 @@ impl Message { // Pass-through nonce value for clients message.nonce = Some(idempotency.into_key()); + // Send the message message.send(db, author, &channel, generate_embeds).await?; + Ok(message) } @@ -528,6 +531,27 @@ impl SystemMessage { } impl Interactions { + /// Validate interactions info is correct + pub async fn validate(&self, db: &Database, permissions: &PermissionValue) -> Result<()> { + let config = config().await; + + if let Some(reactions) = &self.reactions { + permissions.throw_if_lacking_channel_permission(ChannelPermission::React)?; + + if reactions.len() > config.features.limits.default.message_reactions { + return Err(create_error!(InvalidOperation)); + } + + for reaction in reactions { + if !Emoji::can_use(db, reaction).await? { + return Err(create_error!(InvalidOperation)); + } + } + } + + Ok(()) + } + /// Check if we can use a given emoji to react pub fn can_use(&self, emoji: &str) -> bool { if self.restrict_reactions { diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index 88cf7bdb..f508ec29 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -626,18 +626,26 @@ impl From for FieldsRole { } impl crate::User { - pub async fn into

(self, perspective: P) -> User + pub async fn into<'a, P>(self, perspective: P) -> User where - P: Into>, + P: Into>, { let relationship = if let Some(perspective) = perspective.into() { - perspective - .relations - .unwrap_or_default() - .into_iter() - .find(|relationship| relationship.id == self.id) - .map(|relationship| relationship.status.into()) - .unwrap_or_default() + if perspective.id == self.id { + RelationshipStatus::User + } else { + perspective + .relations + .as_ref() + .map(|relations| { + relations + .iter() + .find(|relationship| relationship.id == self.id) + .map(|relationship| relationship.status.clone().into()) + .unwrap_or_default() + }) + .unwrap_or_default() + } } else { RelationshipStatus::None }; diff --git a/crates/core/permissions/src/models/mod.rs b/crates/core/permissions/src/models/mod.rs index 657b9927..aefd8454 100644 --- a/crates/core/permissions/src/models/mod.rs +++ b/crates/core/permissions/src/models/mod.rs @@ -39,20 +39,17 @@ impl PermissionValue { } /// Check whether certain a permission has been granted - pub fn has(&mut self, v: u64) -> bool { + pub fn has(&self, v: u64) -> bool { (self.0 & v) == v } /// Check whether certain a channel permission has been granted - pub fn has_channel_permission(&mut self, permission: ChannelPermission) -> bool { + pub fn has_channel_permission(&self, permission: ChannelPermission) -> bool { self.has(permission as u64) } /// Throw if missing channel permission - pub fn throw_if_lacking_channel_permission( - &mut self, - permission: ChannelPermission, - ) -> Result<()> { + pub fn throw_if_lacking_channel_permission(&self, permission: ChannelPermission) -> Result<()> { if self.has_channel_permission(permission) { Ok(()) } else { diff --git a/crates/delta/src/routes/channels/message_send.rs b/crates/delta/src/routes/channels/message_send.rs index 04034599..c522c0a6 100644 --- a/crates/delta/src/routes/channels/message_send.rs +++ b/crates/delta/src/routes/channels/message_send.rs @@ -1,12 +1,13 @@ -use revolt_database::util::idempotency::IdempotencyKey; -use revolt_quark::{ - models::{message::DataMessageSend, Message, User}, - perms, - types::push::MessageAuthor, - Db, Error, Permission, Ref, Result, +use revolt_database::util::permissions::DatabasePermissionQuery; +use revolt_database::{ + util::idempotency::IdempotencyKey, util::reference::Reference, Database, User, }; - +use revolt_database::{Interactions, Message}; +use revolt_models::v0; +use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; +use revolt_result::{create_error, Result}; use rocket::serde::json::Json; +use rocket::State; use validator::Validate; /// # Send Message @@ -15,68 +16,63 @@ use validator::Validate; #[openapi(tag = "Messaging")] #[post("//messages", data = "")] pub async fn message_send( - db: &Db, + db: &State, user: User, - target: Ref, - data: Json, + target: Reference, + data: Json, idempotency: IdempotencyKey, -) -> Result> { +) -> Result> { let data = data.into_inner(); - data.validate() - .map_err(|error| Error::FailedValidation { error })?; + data.validate().map_err(|error| { + create_error!(FailedValidation { + error: error.to_string() + }) + })?; // Ensure we have permissions to send a message let channel = target.as_channel(db).await?; - let mut permissions = perms(&user).channel(&channel); - permissions - .throw_permission_and_view_channel(db, Permission::SendMessage) - .await?; + let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel); + let permissions = calculate_channel_permissions(&mut query).await; + permissions.throw_if_lacking_channel_permission(ChannelPermission::SendMessage)?; // Verify permissions for masquerade if let Some(masq) = &data.masquerade { - permissions - .throw_permission(db, Permission::Masquerade) - .await?; + permissions.throw_if_lacking_channel_permission(ChannelPermission::Masquerade)?; if masq.colour.is_some() { - permissions - .throw_permission(db, Permission::ManageRole) - .await?; + permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageRole)?; } } // Check permissions for embeds if data.embeds.as_ref().is_some_and(|v| !v.is_empty()) { - permissions - .throw_permission(db, Permission::SendEmbeds) - .await?; + permissions.throw_if_lacking_channel_permission(ChannelPermission::SendEmbeds)?; } // Check permissions for files if data.attachments.as_ref().is_some_and(|v| !v.is_empty()) { - permissions - .throw_permission(db, Permission::UploadFiles) - .await?; + permissions.throw_if_lacking_channel_permission(ChannelPermission::UploadFiles)?; } // Ensure interactions information is correct if let Some(interactions) = &data.interactions { - interactions.validate(db, &mut permissions).await?; + let interactions: Interactions = interactions.clone().into(); + interactions.validate(db, &permissions).await?; } // Create the message - let message = channel - .send_message( + let author: v0::User = user.clone().into(Some(&user)).await; + Ok(Json( + Message::create_from_api( db, + channel, data, - MessageAuthor::User(&user), + v0::MessageAuthor::User(&author), idempotency, - permissions - .has_permission(db, Permission::SendEmbeds) - .await?, + true, ) - .await?; - - Ok(Json(message)) + .await? + .into(), + )) } diff --git a/crates/delta/src/routes/webhooks/webhook_execute.rs b/crates/delta/src/routes/webhooks/webhook_execute.rs index 506ee099..0b8e1b07 100644 --- a/crates/delta/src/routes/webhooks/webhook_execute.rs +++ b/crates/delta/src/routes/webhooks/webhook_execute.rs @@ -31,35 +31,37 @@ pub async fn webhook_execute( let webhook = webhook_id.as_webhook(db).await?; webhook.assert_token(&token)?; - let mut value: PermissionValue = webhook.permissions.into(); - value.throw_if_lacking_channel_permission(ChannelPermission::SendMessage)?; + let permissions: PermissionValue = webhook.permissions.into(); + permissions.throw_if_lacking_channel_permission(ChannelPermission::SendMessage)?; if data.attachments.as_ref().map_or(false, |v| !v.is_empty()) { - value.throw_if_lacking_channel_permission(ChannelPermission::UploadFiles)?; + permissions.throw_if_lacking_channel_permission(ChannelPermission::UploadFiles)?; } if data.embeds.as_ref().map_or(false, |v| !v.is_empty()) { - value.throw_if_lacking_channel_permission(ChannelPermission::SendEmbeds)?; + permissions.throw_if_lacking_channel_permission(ChannelPermission::SendEmbeds)?; } if data.masquerade.is_some() { - value.throw_if_lacking_channel_permission(ChannelPermission::Masquerade)?; + permissions.throw_if_lacking_channel_permission(ChannelPermission::Masquerade)?; } if data.interactions.is_some() { - value.throw_if_lacking_channel_permission(ChannelPermission::React)?; + permissions.throw_if_lacking_channel_permission(ChannelPermission::React)?; } let channel = db.fetch_channel(&webhook.channel_id).await?; - let message = Message::create_from_api( - db, - channel, - data, - v0::MessageAuthor::Webhook(&webhook.into()), - idempotency, - true, - ) - .await?; - Ok(Json(message.into())) + Ok(Json( + Message::create_from_api( + db, + channel, + data, + v0::MessageAuthor::Webhook(&webhook.into()), + idempotency, + true, + ) + .await? + .into(), + )) }