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_thread_file: bool,
|
||||||
pub require_reply_content: bool,
|
pub require_reply_content: bool,
|
||||||
pub require_reply_file: 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,
|
AlreadyAppealedError,
|
||||||
#[error("Tento ban nelze odvolat.")]
|
#[error("Tento ban nelze odvolat.")]
|
||||||
UnappealableError,
|
UnappealableError,
|
||||||
|
#[error("Tvůj příspěvek vypadá jako spam.")]
|
||||||
|
FloodError,
|
||||||
// 500
|
// 500
|
||||||
#[error("Nadnástěnka nebyla inicializována.")]
|
#[error("Nadnástěnka nebyla inicializována.")]
|
||||||
OverboardError,
|
OverboardError,
|
||||||
@ -235,6 +237,7 @@ impl ResponseError for NekrochanError {
|
|||||||
| NekrochanError::IncorrectCaptchaError
|
| NekrochanError::IncorrectCaptchaError
|
||||||
| NekrochanError::IncorrectPasswordError(_) => StatusCode::UNAUTHORIZED,
|
| NekrochanError::IncorrectPasswordError(_) => StatusCode::UNAUTHORIZED,
|
||||||
NekrochanError::HeaderError(_) => StatusCode::BAD_GATEWAY,
|
NekrochanError::HeaderError(_) => StatusCode::BAD_GATEWAY,
|
||||||
|
NekrochanError::FloodError => StatusCode::TOO_MANY_REQUESTS,
|
||||||
NekrochanError::OverboardError | NekrochanError::InternalError => {
|
NekrochanError::OverboardError | NekrochanError::InternalError => {
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,11 @@ use actix_web::{
|
|||||||
cookie::Cookie, http::StatusCode, post, web::Data, HttpRequest, HttpResponse,
|
cookie::Cookie, http::StatusCode, post, web::Data, HttpRequest, HttpResponse,
|
||||||
HttpResponseBuilder,
|
HttpResponseBuilder,
|
||||||
};
|
};
|
||||||
|
use chrono::{Duration, Utc};
|
||||||
use pwhash::bcrypt::hash;
|
use pwhash::bcrypt::hash;
|
||||||
|
use redis::AsyncCommands;
|
||||||
use sha256::digest;
|
use sha256::digest;
|
||||||
|
use std::{collections::HashSet, net::IpAddr};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ctx::Ctx,
|
ctx::Ctx,
|
||||||
@ -171,6 +174,10 @@ pub async fn create_post(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if board.config.antispam {
|
||||||
|
check_spam(&ctx, &board, ip, content_nomarkup.clone()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
if form.files.len() > board.config.0.file_limit {
|
if form.files.len() > board.config.0.file_limit {
|
||||||
return Err(NekrochanError::FileLimitError(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)
|
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 actix_web::{post, web::Data, HttpRequest, HttpResponse};
|
||||||
use pwhash::bcrypt::verify;
|
use pwhash::bcrypt::verify;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ctx::Ctx,
|
ctx::Ctx,
|
||||||
|
@ -24,6 +24,10 @@ pub struct UpdateBoardConfigForm {
|
|||||||
require_thread_file: Option<String>,
|
require_thread_file: Option<String>,
|
||||||
require_reply_content: Option<String>,
|
require_reply_content: Option<String>,
|
||||||
require_reply_file: 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")]
|
#[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_thread_file = form.require_thread_file.is_some();
|
||||||
let require_reply_content = form.require_reply_content.is_some();
|
let require_reply_content = form.require_reply_content.is_some();
|
||||||
let require_reply_file = form.require_reply_file.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 {
|
let config = BoardCfg {
|
||||||
anon_name,
|
anon_name,
|
||||||
@ -75,6 +83,10 @@ pub async fn update_board_config(
|
|||||||
require_thread_file,
|
require_thread_file,
|
||||||
require_reply_content,
|
require_reply_content,
|
||||||
require_reply_file,
|
require_reply_file,
|
||||||
|
antispam,
|
||||||
|
antispam_ip,
|
||||||
|
antispam_content,
|
||||||
|
antispam_both
|
||||||
};
|
};
|
||||||
|
|
||||||
board.update_config(&ctx, config).await?;
|
board.update_config(&ctx, config).await?;
|
||||||
|
@ -145,6 +145,30 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Uložit"></td>
|
<td class="submit" colspan="2"><input class="button" type="submit" value="Uložit"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
Načítá se…
Odkázat v novém úkolu
Zablokovat Uživatele