From 13f64a1c19ab722de68aa94377729f49ec79f849 Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Wed, 20 Jul 2022 15:29:07 -0400 Subject: [PATCH 1/8] server-side validation for asc min-chars & max-chars --- src/constants.rs | 5 +- src/database/models/autostar_channel.rs | 52 ++++++++++++++++++- .../commands/chat/autostar/edit.rs | 12 ++++- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 7b3aa389..b29edd73 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -11,10 +11,11 @@ pub const AUTOSTAR_COOLDOWN: (u32, Duration) = (5, Duration::from_secs(20)); // Common Validation pub const MAX_NAME_LENGTH: u32 = 32; +pub const MIN_NAME_LENGTH: u32 = 3; // AutoStar Validation -pub const MAX_MAX_CHARS: u16 = 5_000; -pub const MAX_MIN_CHARS: u16 = 5_000; +pub const MAX_MAX_CHARS: i16 = 5_000; +pub const MAX_MIN_CHARS: i16 = 5_000; // Starboard Validation pub const MIN_REQUIRED: i16 = -1; diff --git a/src/database/models/autostar_channel.rs b/src/database/models/autostar_channel.rs index 6207770a..2084ba2d 100644 --- a/src/database/models/autostar_channel.rs +++ b/src/database/models/autostar_channel.rs @@ -1,7 +1,10 @@ use sqlx::FromRow; -use crate::database::helpers::{ - query::build_update::build_update, settings::autostar::call_with_autostar_settings, +use crate::{ + constants, + database::helpers::{ + query::build_update::build_update, settings::autostar::call_with_autostar_settings, + }, }; #[derive(Debug, FromRow)] @@ -134,4 +137,49 @@ impl AutoStarChannel { .fetch_optional(pool) .await } + + // validation + pub fn set_min_chars(&mut self, val: i16) -> Result<(), String> { + if let Some(max_chars) = self.max_chars { + if val > max_chars { + return Err("`min-chars` cannot be greater than `max-chars`.".to_string()); + } + } + + if val > constants::MAX_MIN_CHARS { + Err(format!( + "`min-chars` cannot be greater than {}.", + constants::MAX_MIN_CHARS + )) + } else if val < 0 { + Err("`min-chars` cannot be less than 0.".to_string()) + } else { + self.min_chars = val; + Ok(()) + } + } + + pub fn set_max_chars(&mut self, val: Option) -> Result<(), String> { + match val { + None => { + self.max_chars = None; + Ok(()) + } + Some(val) => { + if val < self.min_chars { + Err("`max-chars` cannot be less than `min-chars.".to_string()) + } else if val < 0 { + Err("`max-chars` cannot be less than 0.".to_string()) + } else if val > constants::MAX_MAX_CHARS { + Err(format!( + "`max-chars` cannot be greater than {}.", + constants::MAX_MAX_CHARS + )) + } else { + self.max_chars = Some(val); + Ok(()) + } + } + } + } } diff --git a/src/interactions/commands/chat/autostar/edit.rs b/src/interactions/commands/chat/autostar/edit.rs index 5ad72d8e..4ad059b3 100644 --- a/src/interactions/commands/chat/autostar/edit.rs +++ b/src/interactions/commands/chat/autostar/edit.rs @@ -51,14 +51,22 @@ impl EditAutoStar { .into_stored(); } if let Some(val) = self.min_chars { - asc.min_chars = val.try_into().unwrap(); + let min_chars = val.try_into().unwrap(); + if let Err(why) = asc.set_min_chars(min_chars) { + ctx.respond_str(&why, true).await?; + return Ok(()); + } } if let Some(val) = self.max_chars { - asc.max_chars = if val == -1 { + let max_chars = if val == -1 { None } else { Some(val.try_into().unwrap()) }; + if let Err(why) = asc.set_max_chars(max_chars) { + ctx.respond_str(&why, true).await?; + return Ok(()); + } } if let Some(val) = self.require_image { asc.require_image = val; From 2304068ba6ee1f2c0b6198da88cb6a2248cb6646 Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Wed, 20 Jul 2022 15:54:37 -0400 Subject: [PATCH 2/8] strip variation-selector-16 from emoji input --- src/core/emoji.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/emoji.rs b/src/core/emoji.rs index 592b1a0d..3e68986b 100644 --- a/src/core/emoji.rs +++ b/src/core/emoji.rs @@ -83,6 +83,13 @@ impl EmojiCommon for SimpleEmoji { bot: &StarboardBot, guild_id: Id, ) -> Option { + // Get rid of the Variation-Selector-16 codepoint that is sometimes present in user + // input. https://emojipedia.org/variation-selector-16/ + let input = input + .strip_suffix("\u{fe0f}") + .map(|s| s.to_string()) + .unwrap_or(input); + if emojis::get(&input).is_some() { Some(Self { is_custom: false, From 5c922a7ad51c9179457523fde2534dfd54539eef Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Wed, 20 Jul 2022 22:03:58 -0400 Subject: [PATCH 3/8] validation for required & required-remove --- src/constants.rs | 2 +- src/database/validation/mod.rs | 1 + src/database/validation/starboard_settings.rs | 41 +++++++++++++++++++ .../chat/starboard/edit/requirements.rs | 25 +++++++++-- 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 src/database/validation/starboard_settings.rs diff --git a/src/constants.rs b/src/constants.rs index b29edd73..351b75dd 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -19,6 +19,6 @@ pub const MAX_MIN_CHARS: i16 = 5_000; // Starboard Validation pub const MIN_REQUIRED: i16 = -1; -pub const MAX_REQUIRED: u16 = 500; +pub const MAX_REQUIRED: i16 = 500; pub const MIN_REQUIRED_REMOVE: i16 = -500; pub const MAX_REQUIRED_REMOVE: i16 = 490; diff --git a/src/database/validation/mod.rs b/src/database/validation/mod.rs index e6621071..692b0b51 100644 --- a/src/database/validation/mod.rs +++ b/src/database/validation/mod.rs @@ -1,3 +1,4 @@ pub mod color; pub mod name; +pub mod starboard_settings; pub mod time_delta; diff --git a/src/database/validation/starboard_settings.rs b/src/database/validation/starboard_settings.rs new file mode 100644 index 00000000..0e89071b --- /dev/null +++ b/src/database/validation/starboard_settings.rs @@ -0,0 +1,41 @@ +//! Validation for certain starboard settings that are shared between +//! starboards and overrides, but not elsewhere and thus don't deserve +//! their own file. + +use crate::constants; + +pub fn validate_required(val: i16, required_remove: i16) -> Result<(), String> { + if val <= required_remove { + Err("`required` must be greater than `required-remove`.".to_string()) + } else if val < constants::MIN_REQUIRED { + Err(format!( + "`required` cannot be less than {}.", + constants::MIN_REQUIRED + )) + } else if val > constants::MAX_REQUIRED { + Err(format!( + "`required` cannot be greater than {}.", + constants::MAX_REQUIRED + )) + } else { + Ok(()) + } +} + +pub fn validate_required_remove(val: i16, required: i16) -> Result<(), String> { + if val >= required { + Err("`required-remove` must be less than `required`.".to_string()) + } else if val < constants::MIN_REQUIRED_REMOVE { + Err(format!( + "`required-remove` cannot be less than {}.", + constants::MIN_REQUIRED_REMOVE + )) + } else if val > constants::MAX_REQUIRED_REMOVE { + Err(format!( + "`required-remove` cannot be greater than {}.", + constants::MAX_REQUIRED_REMOVE + )) + } else { + Ok(()) + } +} diff --git a/src/interactions/commands/chat/starboard/edit/requirements.rs b/src/interactions/commands/chat/starboard/edit/requirements.rs index 17c7f393..97a26528 100644 --- a/src/interactions/commands/chat/starboard/edit/requirements.rs +++ b/src/interactions/commands/chat/starboard/edit/requirements.rs @@ -2,7 +2,10 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ core::emoji::{EmojiCommon, SimpleEmoji}, - database::{validation::time_delta::parse_time_delta, Starboard}, + database::{ + validation::{self, time_delta::parse_time_delta}, + Starboard, + }, get_guild_id, interactions::commands::context::CommandCtx, unwrap_id, @@ -67,10 +70,26 @@ impl EditRequirements { }; if let Some(val) = self.required { - starboard.settings.required = val.try_into().unwrap(); + let val = val.try_into().unwrap(); + if let Err(why) = validation::starboard_settings::validate_required( + val, + starboard.settings.required_remove, + ) { + ctx.respond_str(&why, true).await?; + return Ok(()); + } + starboard.settings.required = val; } if let Some(val) = self.required_remove { - starboard.settings.required_remove = val.try_into().unwrap(); + let val = val.try_into().unwrap(); + if let Err(why) = validation::starboard_settings::validate_required_remove( + val, + starboard.settings.required, + ) { + ctx.respond_str(&why, true).await?; + return Ok(()); + } + starboard.settings.required_remove = val; } if let Some(val) = self.upvote_emojis { starboard.settings.upvote_emojis = From 52f79bcc3ecd53a90f34b8e4a7f5c7413046bd32 Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Wed, 20 Jul 2022 22:15:00 -0400 Subject: [PATCH 4/8] validation for `xp-multiplier` --- src/constants.rs | 2 ++ src/database/validation/starboard_settings.rs | 16 ++++++++++++++++ .../commands/chat/starboard/edit/behavior.rs | 15 +++++++++++---- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 351b75dd..10896537 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -22,3 +22,5 @@ pub const MIN_REQUIRED: i16 = -1; pub const MAX_REQUIRED: i16 = 500; pub const MIN_REQUIRED_REMOVE: i16 = -500; pub const MAX_REQUIRED_REMOVE: i16 = 490; +pub const MIN_XP_MULTIPLIER: f32 = -10; +pub const MAX_XP_MULTIPLIER: f32 = 10; diff --git a/src/database/validation/starboard_settings.rs b/src/database/validation/starboard_settings.rs index 0e89071b..4bccd5d6 100644 --- a/src/database/validation/starboard_settings.rs +++ b/src/database/validation/starboard_settings.rs @@ -39,3 +39,19 @@ pub fn validate_required_remove(val: i16, required: i16) -> Result<(), String> { Ok(()) } } + +pub fn validate_xp_multiplier(val: f32) -> Result<(), String> { + if val > constants::MAX_XP_MULTIPLIER { + Err(format!( + "`xp-multiplier` cannot be greater than {}.", + constants::MAX_XP_MULTIPLIER + )) + } else if val < constants::MIN_XP_MULTIPLIER { + Err(format!( + "`xp-multiplier` cannot be less than {}.", + constants::MIN_XP_MULTIPLIER + )) + } else { + Ok(()) + } +} diff --git a/src/interactions/commands/chat/starboard/edit/behavior.rs b/src/interactions/commands/chat/starboard/edit/behavior.rs index cbd4535c..d4979251 100644 --- a/src/interactions/commands/chat/starboard/edit/behavior.rs +++ b/src/interactions/commands/chat/starboard/edit/behavior.rs @@ -2,7 +2,10 @@ use lazy_static::lazy_static; use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ - database::Starboard, get_guild_id, interactions::commands::context::CommandCtx, unwrap_id, + database::{validation, Starboard}, + get_guild_id, + interactions::commands::context::CommandCtx, + unwrap_id, }; #[derive(CommandModel, CreateCommand)] @@ -32,7 +35,7 @@ pub struct EditBehavior { /// If true, prevents /random and /moststarred from pulling from this starboard. private: Option, /// How much XP each upvote on this starboard counts for. - #[command(rename = "xp-multiplier")] + #[command(rename = "xp-multiplier", min_value = 10, max_value = 10)] xp_multiplier: Option, /// Whether to enable the per-user vote cooldown. #[command(rename = "cooldown-enabled")] @@ -76,8 +79,12 @@ impl EditBehavior { starboard.settings.private = val; } if let Some(val) = self.xp_multiplier { - // TODO: validation - starboard.settings.xp_multiplier = val.to_string().parse().unwrap(); + let val = val.to_string().parse().unwrap(); + if let Err(why) = validation::starboard_settings::validate_xp_multiplier(val) { + ctx.respond_str(&why, true).await?; + return Ok(()); + } + starboard.settings.xp_multiplier = val; } if let Some(val) = self.cooldown_enabled { starboard.settings.cooldown_enabled = val; From 694724e03f1347ae58de9dc692e845e0fab7b465 Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Wed, 20 Jul 2022 22:22:12 -0400 Subject: [PATCH 5/8] fix bug --- src/constants.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 10896537..43cf1164 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -22,5 +22,5 @@ pub const MIN_REQUIRED: i16 = -1; pub const MAX_REQUIRED: i16 = 500; pub const MIN_REQUIRED_REMOVE: i16 = -500; pub const MAX_REQUIRED_REMOVE: i16 = 490; -pub const MIN_XP_MULTIPLIER: f32 = -10; -pub const MAX_XP_MULTIPLIER: f32 = 10; +pub const MIN_XP_MULTIPLIER: f32 = -10.0; +pub const MAX_XP_MULTIPLIER: f32 = 10.0; From 1d5b4169cd30e81e734efb6c7f674d16b31a75c1 Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Wed, 20 Jul 2022 22:39:40 -0400 Subject: [PATCH 6/8] validation for cooldown --- src/constants.rs | 2 ++ src/database/validation/starboard_settings.rs | 18 ++++++++++++++++++ .../commands/chat/starboard/edit/behavior.rs | 1 + 3 files changed, 21 insertions(+) diff --git a/src/constants.rs b/src/constants.rs index 43cf1164..14c25d80 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -24,3 +24,5 @@ pub const MIN_REQUIRED_REMOVE: i16 = -500; pub const MAX_REQUIRED_REMOVE: i16 = 490; pub const MIN_XP_MULTIPLIER: f32 = -10.0; pub const MAX_XP_MULTIPLIER: f32 = 10.0; +pub const MAX_COOLDOWN_CAPACITY: i16 = 3600; // seconds in an hour +pub const MAX_COOLDOWN_PERIOD: i16 = MAX_COOLDOWN_CAPACITY; diff --git a/src/database/validation/starboard_settings.rs b/src/database/validation/starboard_settings.rs index 4bccd5d6..00e9f7ad 100644 --- a/src/database/validation/starboard_settings.rs +++ b/src/database/validation/starboard_settings.rs @@ -55,3 +55,21 @@ pub fn validate_xp_multiplier(val: f32) -> Result<(), String> { Ok(()) } } + +pub fn validate_cooldown(capacity: i16, period: i16) -> Result<(), String> { + if capacity <= 0 || period <= 0 { + Err("The capacity and period for the cooldown must be greater than 0.".to_string()) + } else if capacity > constants::MAX_COOLDOWN_CAPACITY { + Err(format!( + "The capacity cannot be greater than {}.", + constants::MAX_COOLDOWN_CAPACITY + )) + } else if period > constants::MAX_COOLDOWN_PERIOD { + Err(format!( + "The period cannot be greater than {}.", + constants::MAX_COOLDOWN_PERIOD + )) + } else { + Ok(()) + } +} diff --git a/src/interactions/commands/chat/starboard/edit/behavior.rs b/src/interactions/commands/chat/starboard/edit/behavior.rs index d4979251..1b00b9e8 100644 --- a/src/interactions/commands/chat/starboard/edit/behavior.rs +++ b/src/interactions/commands/chat/starboard/edit/behavior.rs @@ -141,5 +141,6 @@ fn parse_cooldown(inp: &str) -> Result<(i16, i16), String> { Ok(period) => period, }; + validation::starboard_settings::validate_cooldown(capacity, period)?; Ok((capacity, period)) } From a25dcafeb63cb609c6c09ffc408e6011c2b6f1c4 Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Thu, 21 Jul 2022 10:23:19 -0400 Subject: [PATCH 7/8] intersect validation for vote emojis --- src/database/validation/starboard_settings.rs | 21 +++++++++++++ .../chat/starboard/edit/requirements.rs | 30 ++++++++++++++----- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/database/validation/starboard_settings.rs b/src/database/validation/starboard_settings.rs index 00e9f7ad..a26359b9 100644 --- a/src/database/validation/starboard_settings.rs +++ b/src/database/validation/starboard_settings.rs @@ -2,6 +2,8 @@ //! starboards and overrides, but not elsewhere and thus don't deserve //! their own file. +use std::collections::HashSet; + use crate::constants; pub fn validate_required(val: i16, required_remove: i16) -> Result<(), String> { @@ -73,3 +75,22 @@ pub fn validate_cooldown(capacity: i16, period: i16) -> Result<(), String> { Ok(()) } } + +pub fn validate_vote_emojis( + upvote: &Vec, + downvote: &Vec, +) -> Result<(), &'static str> { + let unique_upvote: HashSet<_> = upvote.iter().collect(); + let unique_downvote: HashSet<_> = downvote.iter().collect(); + + if unique_upvote + .intersection(&unique_downvote) + .collect::>() + .len() + > 0 + { + Err("Upvote emojis and downvote emojis cannot share the same emojis.") + } else { + Ok(()) + } +} diff --git a/src/interactions/commands/chat/starboard/edit/requirements.rs b/src/interactions/commands/chat/starboard/edit/requirements.rs index 97a26528..7d1f67a8 100644 --- a/src/interactions/commands/chat/starboard/edit/requirements.rs +++ b/src/interactions/commands/chat/starboard/edit/requirements.rs @@ -92,16 +92,30 @@ impl EditRequirements { starboard.settings.required_remove = val; } if let Some(val) = self.upvote_emojis { - starboard.settings.upvote_emojis = - Vec::::from_user_input(val, &ctx.bot, guild_id) - .await - .into_stored(); + let emojis = Vec::::from_user_input(val, &ctx.bot, guild_id) + .await + .into_stored(); + if let Err(why) = validation::starboard_settings::validate_vote_emojis( + &emojis, + &starboard.settings.downvote_emojis, + ) { + ctx.respond_str(why, true).await?; + return Ok(()); + } + starboard.settings.upvote_emojis = emojis; } if let Some(val) = self.downvote_emojis { - starboard.settings.downvote_emojis = - Vec::::from_user_input(val, &ctx.bot, guild_id) - .await - .into_stored(); + let emojis = Vec::::from_user_input(val, &ctx.bot, guild_id) + .await + .into_stored(); + if let Err(why) = validation::starboard_settings::validate_vote_emojis( + &starboard.settings.upvote_emojis, + &emojis, + ) { + ctx.respond_str(why, true).await?; + return Ok(()); + } + starboard.settings.downvote_emojis = emojis; } if let Some(val) = self.self_vote { starboard.settings.self_vote = val; From fe5cbaca6652ac2687afca398c06b916b5059f45 Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Thu, 21 Jul 2022 10:24:35 -0400 Subject: [PATCH 8/8] remove todo comment --- src/interactions/commands/chat/starboard/edit/behavior.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/interactions/commands/chat/starboard/edit/behavior.rs b/src/interactions/commands/chat/starboard/edit/behavior.rs index 1b00b9e8..47c9757c 100644 --- a/src/interactions/commands/chat/starboard/edit/behavior.rs +++ b/src/interactions/commands/chat/starboard/edit/behavior.rs @@ -90,7 +90,6 @@ impl EditBehavior { starboard.settings.cooldown_enabled = val; } if let Some(val) = self.cooldown { - // TODO: validation let (capacity, period) = match parse_cooldown(&val) { Err(why) => { ctx.respond_str(&why, true).await?;