Antispam
Tento commit je obsažen v:
rodič
8a73452cc0
revize
eb33ad9a19
@ -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,
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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<String> = ctx
|
||||
.cache()
|
||||
.zrangebyscore(&ip_key, antispam_ip, "+inf")
|
||||
.await?;
|
||||
let content_posts: HashSet<String> = ctx
|
||||
.cache()
|
||||
.zrangebyscore(&content_key, antispam_content, "+inf")
|
||||
.await?;
|
||||
|
||||
let ip_posts2: HashSet<String> = ctx
|
||||
.cache()
|
||||
.zrangebyscore(&ip_key, antispam_both, "+inf")
|
||||
.await?;
|
||||
let content_posts2: HashSet<String> = 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(())
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -24,6 +24,10 @@ pub struct UpdateBoardConfigForm {
|
||||
require_thread_file: Option<String>,
|
||||
require_reply_content: Option<String>,
|
||||
require_reply_file: Option<String>,
|
||||
antispam: Option<String>,
|
||||
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?;
|
||||
|
@ -145,6 +145,30 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Antispam</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="antispam" type="checkbox" {% if board.config.0.antispam %}checked="checked"{% endif %}>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Interval antispamu (IP)</td>
|
||||
<td><input name="antispam_ip" type="number" min="0" value="{{ board.config.0.antispam_ip }}" required="required"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Interval antispamu (Obsah)</td>
|
||||
<td><input name="antispam_content" type="number" min="0" value="{{ board.config.0.antispam_content }}" required="required"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Interval antispamu (IP+Obsah)</td>
|
||||
<td><input name="antispam_both" type="number" min="0" value="{{ board.config.0.antispam_both }}" required="required"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Uložit"></td>
|
||||
</tr>
|
||||
|
Načítá se…
Odkázat v novém úkolu
Zablokovat Uživatele