2023-05-26 15:55:19 +00:00
|
|
|
use crate::connections::redis_connection;
|
|
|
|
use crate::utils::time_utils::time;
|
|
|
|
use std::net::IpAddr;
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
|
pub enum RatedAction {
|
|
|
|
CreateAccount,
|
2023-05-31 09:36:14 +00:00
|
|
|
CheckResetPasswordTokenFailed,
|
2023-05-31 11:56:18 +00:00
|
|
|
RequestNewPasswordResetLink,
|
2023-05-31 13:17:00 +00:00
|
|
|
FailedPasswordLogin,
|
2023-06-02 09:52:10 +00:00
|
|
|
StartOpenIDLogin,
|
2023-06-05 17:02:51 +00:00
|
|
|
RequestReplacePasswordSignedIn,
|
2023-06-06 07:47:52 +00:00
|
|
|
RequestDeleteAccount,
|
2023-06-17 16:55:07 +00:00
|
|
|
JoinFamily,
|
2023-05-26 15:55:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl RatedAction {
|
|
|
|
fn id(&self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
RatedAction::CreateAccount => "create-account",
|
2023-05-31 09:36:14 +00:00
|
|
|
RatedAction::CheckResetPasswordTokenFailed => "check-reset-password-token",
|
2023-05-31 11:56:18 +00:00
|
|
|
RatedAction::RequestNewPasswordResetLink => "req-pwd-reset-lnk",
|
2023-05-31 13:17:00 +00:00
|
|
|
RatedAction::FailedPasswordLogin => "failed-login",
|
2023-06-02 09:52:10 +00:00
|
|
|
RatedAction::StartOpenIDLogin => "start-oidc-login",
|
2023-06-06 07:47:52 +00:00
|
|
|
RatedAction::RequestReplacePasswordSignedIn => "req-pwd-signed-in",
|
|
|
|
RatedAction::RequestDeleteAccount => "req-del-acct",
|
2023-06-17 16:55:07 +00:00
|
|
|
RatedAction::JoinFamily => "join-family",
|
2023-05-26 15:55:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn limit(&self) -> usize {
|
|
|
|
match self {
|
|
|
|
RatedAction::CreateAccount => 5,
|
2023-05-31 09:36:14 +00:00
|
|
|
RatedAction::CheckResetPasswordTokenFailed => 100,
|
2023-05-31 11:56:18 +00:00
|
|
|
RatedAction::RequestNewPasswordResetLink => 5,
|
2023-05-31 13:17:00 +00:00
|
|
|
RatedAction::FailedPasswordLogin => 15,
|
2023-06-02 09:52:10 +00:00
|
|
|
RatedAction::StartOpenIDLogin => 30,
|
2023-06-05 17:02:51 +00:00
|
|
|
RatedAction::RequestReplacePasswordSignedIn => 5,
|
2023-06-06 07:47:52 +00:00
|
|
|
RatedAction::RequestDeleteAccount => 5,
|
2023-06-17 16:55:07 +00:00
|
|
|
RatedAction::JoinFamily => 10,
|
2023-05-26 15:55:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn keep_seconds(&self) -> u64 {
|
2023-05-31 11:56:18 +00:00
|
|
|
3600
|
2023-05-26 15:55:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn key(&self, ip: IpAddr) -> String {
|
|
|
|
format!("rate-{}-{}", self.id(), ip)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Keep track of the time the action was executed by the user
|
|
|
|
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
|
|
|
struct ActionRecord(Vec<u64>);
|
|
|
|
|
|
|
|
impl ActionRecord {
|
|
|
|
pub fn clean(&mut self, action: RatedAction) {
|
|
|
|
self.0.retain(|e| e + action.keep_seconds() > time());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Record a new action of the user
|
|
|
|
pub async fn record_action(ip: IpAddr, action: RatedAction) -> anyhow::Result<()> {
|
|
|
|
let key = action.key(ip);
|
|
|
|
|
|
|
|
let mut record = redis_connection::get_value::<ActionRecord>(&key)
|
|
|
|
.await?
|
|
|
|
.unwrap_or_default();
|
|
|
|
record.clean(action);
|
|
|
|
record.0.push(time());
|
|
|
|
redis_connection::set_value(&key, &record, Duration::from_secs(action.keep_seconds())).await?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Check whether an action should be blocked, due to too much attempts from the user
|
|
|
|
pub async fn should_block_action(ip: IpAddr, action: RatedAction) -> anyhow::Result<bool> {
|
|
|
|
let mut record = redis_connection::get_value::<ActionRecord>(&action.key(ip))
|
|
|
|
.await?
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
record.clean(action);
|
|
|
|
|
|
|
|
Ok(record.0.len() >= action.limit())
|
|
|
|
}
|