diff --git a/Cargo.lock b/Cargo.lock index 96387865..e5d06758 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2505,7 +2505,7 @@ dependencies = [ [[package]] name = "rauth" version = "1.0.0" -source = "git+https://github.com/insertish/rauth?rev=c2acaf6b31213e3969f8b48f9deed3daa125d394#c2acaf6b31213e3969f8b48f9deed3daa125d394" +source = "git+https://github.com/insertish/rauth?rev=9c3f9bb58a549a51546bd748274130ac4a667cb4#9c3f9bb58a549a51546bd748274130ac4a667cb4" dependencies = [ "async-std", "async-trait", @@ -2986,7 +2986,7 @@ dependencies = [ [[package]] name = "rocket_rauth" version = "1.0.0" -source = "git+https://github.com/insertish/rauth?rev=c2acaf6b31213e3969f8b48f9deed3daa125d394#c2acaf6b31213e3969f8b48f9deed3daa125d394" +source = "git+https://github.com/insertish/rauth?rev=9c3f9bb58a549a51546bd748274130ac4a667cb4#9c3f9bb58a549a51546bd748274130ac4a667cb4" dependencies = [ "okapi", "rauth", diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml index f2113799..4a7ef196 100644 --- a/crates/delta/Cargo.toml +++ b/crates/delta/Cargo.toml @@ -52,7 +52,7 @@ mobc-redis = { version = "0.7.0", default-features = false, features = ["async-s # web rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] } rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "master" } -rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "67a7fa8100c7db8411fb8fb149441418b55c19ef" } +rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "9c3f9bb58a549a51546bd748274130ac4a667cb4" } # spec generation schemars = "0.8.8" diff --git a/crates/delta/src/routes/channels/message_send.rs b/crates/delta/src/routes/channels/message_send.rs index 0cf373d2..554d64ea 100644 --- a/crates/delta/src/routes/channels/message_send.rs +++ b/crates/delta/src/routes/channels/message_send.rs @@ -100,10 +100,16 @@ pub async fn message_send( } // 2. Verify permissions for masquerade. - if message.masquerade.is_some() { + if let Some(masq) = &message.masquerade { permissions .throw_permission(db, Permission::Masquerade) .await?; + + if masq.colour.is_some() { + permissions + .throw_permission(db, Permission::ManageRole) + .await?; + } } // 3. Verify replies are valid. diff --git a/crates/delta/src/routes/servers/member_edit.rs b/crates/delta/src/routes/servers/member_edit.rs index f0797dd0..d27b9609 100644 --- a/crates/delta/src/routes/servers/member_edit.rs +++ b/crates/delta/src/routes/servers/member_edit.rs @@ -5,7 +5,7 @@ use revolt_quark::{ server_member::{FieldsMember, PartialMember}, File, Member, User, }, - perms, Db, Error, Permission, Ref, Result, + perms, Db, Error, Permission, Ref, Result, Timestamp, }; use rocket::serde::json::Json; @@ -22,6 +22,8 @@ pub struct DataMemberEdit { avatar: Option, /// Array of role ids roles: Option>, + /// Timestamp this member is timed out until + timeout: Option, /// Fields to remove from channel object #[validate(length(min = 1))] remove: Option>, @@ -105,13 +107,7 @@ pub async fn req( // Check permissions against roles in diff if let Some(roles) = &data.roles { - let fallback = vec![]; - let current_roles = member - .roles - .as_ref() - .unwrap_or(&fallback) - .iter() - .collect::>(); + let current_roles = member.roles.iter().collect::>(); let new_roles = roles.iter().collect::>(); let added_roles: Vec<&&String> = new_roles.difference(¤t_roles).collect(); @@ -132,12 +128,14 @@ pub async fn req( nickname, avatar, roles, + timeout, remove, } = data; let mut partial = PartialMember { nickname, roles, + timeout, ..Default::default() }; diff --git a/crates/quark/Cargo.toml b/crates/quark/Cargo.toml index 219b79ee..86b6fd8e 100644 --- a/crates/quark/Cargo.toml +++ b/crates/quark/Cargo.toml @@ -84,7 +84,7 @@ rocket_empty = { optional = true, git = "https://github.com/insertish/rocket_emp rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" } # rAuth -rauth = { git = "https://github.com/insertish/rauth", rev = "67a7fa8100c7db8411fb8fb149441418b55c19ef", features = [ "async-std-runtime" ] } +rauth = { git = "https://github.com/insertish/rauth", rev = "9c3f9bb58a549a51546bd748274130ac4a667cb4", features = [ "async-std-runtime" ] } # Sentry sentry = "0.25.0" diff --git a/crates/quark/src/events/impl.rs b/crates/quark/src/events/impl.rs index 31cf56e9..03acc508 100644 --- a/crates/quark/src/events/impl.rs +++ b/crates/quark/src/events/impl.rs @@ -484,10 +484,8 @@ impl State { if data.rank.is_some() || data.permissions.is_some() { if let Some(member) = self.cache.members.get(id) { - if let Some(roles) = &member.roles { - if roles.contains(role_id) { - queue_server = Some(id.clone()); - } + if member.roles.contains(role_id) { + queue_server = Some(id.clone()); } } } @@ -498,10 +496,8 @@ impl State { } if let Some(member) = self.cache.members.get(id) { - if let Some(roles) = &member.roles { - if roles.contains(role_id) { - queue_server = Some(id.clone()); - } + if member.roles.contains(role_id) { + queue_server = Some(id.clone()); } } } diff --git a/crates/quark/src/impl/dummy/servers/server_member.rs b/crates/quark/src/impl/dummy/servers/server_member.rs index 77db7406..f0b9eff4 100644 --- a/crates/quark/src/impl/dummy/servers/server_member.rs +++ b/crates/quark/src/impl/dummy/servers/server_member.rs @@ -3,6 +3,8 @@ use crate::{AbstractServerMember, Result}; use super::super::DummyDb; +use iso8601_timestamp::Timestamp; + #[async_trait] impl AbstractServerMember for DummyDb { async fn fetch_member(&self, server: &str, user: &str) -> Result { @@ -11,9 +13,11 @@ impl AbstractServerMember for DummyDb { server: server.into(), user: user.into(), }, + joined_at: Timestamp::now_utc(), nickname: None, avatar: None, - roles: None, + roles: vec![], + timeout: None, }) } diff --git a/crates/quark/src/impl/generic/servers/server.rs b/crates/quark/src/impl/generic/servers/server.rs index ce3b7f09..dfa3ca5a 100644 --- a/crates/quark/src/impl/generic/servers/server.rs +++ b/crates/quark/src/impl/generic/servers/server.rs @@ -1,3 +1,4 @@ +use iso8601_timestamp::Timestamp; use ulid::Ulid; use crate::{ @@ -188,7 +189,11 @@ impl Server { server: self.id.clone(), user: user.id.clone(), }, - ..Default::default() + joined_at: Timestamp::now_utc(), + nickname: None, + avatar: None, + roles: vec![], + timeout: None, }; db.insert_member(&member).await?; diff --git a/crates/quark/src/impl/generic/servers/server_member.rs b/crates/quark/src/impl/generic/servers/server_member.rs index 79c5d8fc..bb9154b9 100644 --- a/crates/quark/src/impl/generic/servers/server_member.rs +++ b/crates/quark/src/impl/generic/servers/server_member.rs @@ -36,27 +36,24 @@ impl Member { /// Get this user's current ranking pub fn get_ranking(&self, server: &Server) -> i64 { - if let Some(roles) = &self.roles { - let mut value = i64::MAX; - for role in roles { - if let Some(role) = server.roles.get(role) { - if role.rank < value { - value = role.rank; - } + let mut value = i64::MAX; + for role in &self.roles { + if let Some(role) = server.roles.get(role) { + if role.rank < value { + value = role.rank; } } - - value - } else { - i64::MAX } + + value } pub fn remove(&mut self, field: &FieldsMember) { match field { FieldsMember::Avatar => self.avatar = None, FieldsMember::Nickname => self.nickname = None, - FieldsMember::Roles => self.roles = None, + FieldsMember::Roles => self.roles.clear(), + FieldsMember::Timeout => self.timeout = None, } } } diff --git a/crates/quark/src/impl/mongo/admin/migrations/scripts.rs b/crates/quark/src/impl/mongo/admin/migrations/scripts.rs index 6f8c08d5..faaa9708 100644 --- a/crates/quark/src/impl/mongo/admin/migrations/scripts.rs +++ b/crates/quark/src/impl/mongo/admin/migrations/scripts.rs @@ -1,7 +1,8 @@ use std::time::Duration; -use bson::Bson; +use bson::{to_bson, Bson}; use futures::StreamExt; +use iso8601_timestamp::Timestamp; use mongodb::{ bson::{doc, from_bson, from_document, to_document, Document}, options::FindOptions, @@ -16,7 +17,7 @@ struct MigrationInfo { revision: i32, } -pub const LATEST_REVISION: i32 = 17; +pub const LATEST_REVISION: i32 = 18; pub async fn migrate_database(db: &MongoDb) { let migrations = db.col::("migrations"); @@ -644,6 +645,23 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 { .expect("Failed to create emoji parent index."); } + if revision <= 17 { + info!("Running migration [revision 17 / 15-07-2022]: Initialise `joined_at` property on server members."); + + db.col::("server_members") + .update_many( + doc! {}, + doc! { + "$set": { + "joined_at": to_bson(&Timestamp::now_utc()).expect("to_bson") + } + }, + None, + ) + .await + .expect("Failed to update server members."); + } + // Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`. // Reminder to update LATEST_REVISION when adding new migrations. diff --git a/crates/quark/src/impl/mongo/servers/server_member.rs b/crates/quark/src/impl/mongo/servers/server_member.rs index bebb7e65..500f36a2 100644 --- a/crates/quark/src/impl/mongo/servers/server_member.rs +++ b/crates/quark/src/impl/mongo/servers/server_member.rs @@ -129,6 +129,7 @@ impl IntoDocumentPath for FieldsMember { FieldsMember::Avatar => "avatar", FieldsMember::Nickname => "nickname", FieldsMember::Roles => "roles", + FieldsMember::Timeout => "timeout", }) } } diff --git a/crates/quark/src/models/channels/message.rs b/crates/quark/src/models/channels/message.rs index 91ea6e0e..93f0d007 100644 --- a/crates/quark/src/models/channels/message.rs +++ b/crates/quark/src/models/channels/message.rs @@ -74,6 +74,14 @@ pub struct Masquerade { #[serde(skip_serializing_if = "Option::is_none")] #[validate(length(min = 1, max = 128))] pub avatar: Option, + /// Replace the display role colour shown on this message + /// + /// Must have `ManageRole` permission to use + /// + /// This can be any valid CSS colour + #[serde(skip_serializing_if = "Option::is_none")] + #[validate(length(min = 1, max = 32))] + pub colour: Option, } /// Representation of a Message on Revolt diff --git a/crates/quark/src/models/servers/server_member.rs b/crates/quark/src/models/servers/server_member.rs index 7c911bf1..9f5d6441 100644 --- a/crates/quark/src/models/servers/server_member.rs +++ b/crates/quark/src/models/servers/server_member.rs @@ -1,3 +1,4 @@ +use iso8601_timestamp::Timestamp; use serde::{Deserialize, Serialize}; use crate::models::attachment::File; @@ -12,7 +13,7 @@ pub struct MemberCompositeKey { } /// Representation of a member of a server on Revolt -#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)] +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct)] #[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)] #[optional_name = "PartialMember"] #[opt_skip_serializing_none] @@ -22,6 +23,9 @@ pub struct Member { #[serde(rename = "_id")] pub id: MemberCompositeKey, + /// Time at which this user joined the server + pub joined_at: Timestamp, + /// Member's nickname #[serde(skip_serializing_if = "Option::is_none")] pub nickname: Option, @@ -30,8 +34,11 @@ pub struct Member { pub avatar: Option, /// Member's roles + #[serde(skip_serializing_if = "Vec::is_empty")] + pub roles: Vec, + /// Timestamp this member is timed out until #[serde(skip_serializing_if = "Option::is_none")] - pub roles: Option>, + pub timeout: Option, } /// Optional fields on server member object @@ -40,6 +47,7 @@ pub enum FieldsMember { Nickname, Avatar, Roles, + Timeout, } /// Member removal intention diff --git a/crates/quark/src/permissions/impl/permission.rs b/crates/quark/src/permissions/impl/permission.rs index 68bec970..d1107b34 100644 --- a/crates/quark/src/permissions/impl/permission.rs +++ b/crates/quark/src/permissions/impl/permission.rs @@ -55,11 +55,7 @@ async fn calculate_server_permission( let mut permissions: PermissionValue = server.default_permissions.into(); // 4. Resolve each role in order. - let member_roles: HashSet<&String> = if let Some(roles) = member.roles.as_ref() { - roles.iter().collect() - } else { - HashSet::new() - }; + let member_roles: HashSet<&String> = member.roles.iter().collect(); if !member_roles.is_empty() { let mut roles = server @@ -188,11 +184,7 @@ async fn calculate_channel_permission( } // 4. Resolve each role in order. - let member_roles: HashSet<&String> = if let Some(roles) = member.roles.as_ref() { - roles.iter().collect() - } else { - HashSet::new() - }; + let member_roles: HashSet<&String> = member.roles.iter().collect(); if !member_roles.is_empty() { let mut roles = role_permissions