diff --git a/src/cfg.rs b/src/cfg.rs index 5e90339..e420f30 100755 --- a/src/cfg.rs +++ b/src/cfg.rs @@ -68,4 +68,8 @@ pub struct BoardCfg { pub require_thread_file: bool, pub require_reply_content: bool, pub require_reply_file: bool, + pub antispam: bool, + pub antispam_ip: i64, + pub antispam_content: i64, + pub antispam_both: i64, } diff --git a/src/error.rs b/src/error.rs index bac192a..878c4c5 100755 --- a/src/error.rs +++ b/src/error.rs @@ -76,6 +76,8 @@ pub enum NekrochanError { AlreadyAppealedError, #[error("Tento ban nelze odvolat.")] UnappealableError, + #[error("Tvůj příspěvek vypadá jako spam.")] + FloodError, // 500 #[error("Nadnástěnka nebyla inicializována.")] OverboardError, @@ -235,6 +237,7 @@ impl ResponseError for NekrochanError { | NekrochanError::IncorrectCaptchaError | NekrochanError::IncorrectPasswordError(_) => StatusCode::UNAUTHORIZED, NekrochanError::HeaderError(_) => StatusCode::BAD_GATEWAY, + NekrochanError::FloodError => StatusCode::TOO_MANY_REQUESTS, NekrochanError::OverboardError | NekrochanError::InternalError => { StatusCode::INTERNAL_SERVER_ERROR } diff --git a/src/web/actions/create_post.rs b/src/web/actions/create_post.rs index 2cdbf05..d14f12a 100644 --- a/src/web/actions/create_post.rs +++ b/src/web/actions/create_post.rs @@ -3,8 +3,11 @@ use actix_web::{ cookie::Cookie, http::StatusCode, post, web::Data, HttpRequest, HttpResponse, HttpResponseBuilder, }; +use chrono::{Duration, Utc}; use pwhash::bcrypt::hash; +use redis::AsyncCommands; use sha256::digest; +use std::{collections::HashSet, net::IpAddr}; use crate::{ ctx::Ctx, @@ -171,6 +174,10 @@ pub async fn create_post( ) .await?; + if board.config.antispam { + check_spam(&ctx, &board, ip, content_nomarkup.clone()).await?; + } + if form.files.len() > board.config.0.file_limit { return Err(NekrochanError::FileLimitError(board.config.0.file_limit)); } @@ -248,3 +255,53 @@ pub async fn create_post( Ok(res) } + +pub async fn check_spam( + ctx: &Ctx, + board: &Board, + ip: IpAddr, + content_nomarkup: String, +) -> Result<(), NekrochanError> { + let ip_key = format!("by_ip:{ip}"); + let content_key = format!("by_content:{}", digest(content_nomarkup)); + + let antispam_ip = (Utc::now() - Duration::seconds(board.config.antispam_ip)).timestamp_micros(); + let antispam_content = + (Utc::now() - Duration::seconds(board.config.antispam_content)).timestamp_micros(); + let antispam_both = + (Utc::now() - Duration::seconds(board.config.antispam_both)).timestamp_micros(); + + let ip_posts: HashSet = ctx + .cache() + .zrangebyscore(&ip_key, antispam_ip, "+inf") + .await?; + let content_posts: HashSet = ctx + .cache() + .zrangebyscore(&content_key, antispam_content, "+inf") + .await?; + + let ip_posts2: HashSet = ctx + .cache() + .zrangebyscore(&ip_key, antispam_both, "+inf") + .await?; + let content_posts2: HashSet = ctx + .cache() + .zrangebyscore(&content_key, antispam_both, "+inf") + .await?; + + let both_posts = ip_posts2.intersection(&content_posts2); + + if !ip_posts.is_empty() { + return Err(NekrochanError::FloodError); + } + + if !content_posts.is_empty() { + return Err(NekrochanError::FloodError); + } + + if both_posts.count() != 0 { + return Err(NekrochanError::FloodError); + } + + Ok(()) +} diff --git a/src/web/actions/user_post_actions.rs b/src/web/actions/user_post_actions.rs index f2d92a6..4e82e3e 100644 --- a/src/web/actions/user_post_actions.rs +++ b/src/web/actions/user_post_actions.rs @@ -1,8 +1,7 @@ -use std::fmt::Write; - use actix_web::{post, web::Data, HttpRequest, HttpResponse}; use pwhash::bcrypt::verify; use serde::Deserialize; +use std::fmt::Write; use crate::{ ctx::Ctx, diff --git a/src/web/staff/actions/update_board_config.rs b/src/web/staff/actions/update_board_config.rs index d4af5fd..e8ad34f 100755 --- a/src/web/staff/actions/update_board_config.rs +++ b/src/web/staff/actions/update_board_config.rs @@ -24,6 +24,10 @@ pub struct UpdateBoardConfigForm { require_thread_file: Option, require_reply_content: Option, require_reply_file: Option, + antispam: Option, + antispam_ip: i64, + antispam_content: i64, + antispam_both: i64, } #[post("/staff/actions/update-board-config")] @@ -58,6 +62,10 @@ pub async fn update_board_config( let require_thread_file = form.require_thread_file.is_some(); let require_reply_content = form.require_reply_content.is_some(); let require_reply_file = form.require_reply_file.is_some(); + let antispam = form.antispam.is_some(); + let antispam_ip = form.antispam_ip; + let antispam_content = form.antispam_content; + let antispam_both = form.antispam_both; let config = BoardCfg { anon_name, @@ -75,6 +83,10 @@ pub async fn update_board_config( require_thread_file, require_reply_content, require_reply_file, + antispam, + antispam_ip, + antispam_content, + antispam_both }; board.update_config(&ctx, config).await?; diff --git a/templates/staff/board-config.html b/templates/staff/board-config.html index 39c42e4..8e6e76b 100755 --- a/templates/staff/board-config.html +++ b/templates/staff/board-config.html @@ -145,6 +145,30 @@ + + Antispam + +
+ +
+ + + + + Interval antispamu (IP) + + + + + Interval antispamu (Obsah) + + + + + Interval antispamu (IP+Obsah) + + +