Start to implement brute force protection

This commit is contained in:
Pierre HUBERT 2022-04-03 16:21:09 +02:00
parent b965fa6b4f
commit 05e911bfc5
4 changed files with 96 additions and 3 deletions

View File

@ -1,3 +1,5 @@
TODO list
# Basic OIDC
Basic OpenID provider. Still under early development.
TODO :
- [ ] Bruteforce protection
- [ ] CRSF protection

View File

@ -0,0 +1,86 @@
use std::collections::HashMap;
use std::net::IpAddr;
use crate::constants::KEEP_FAILED_LOGIN_ATTEMPTS_FOR;
use crate::utils::time::time;
#[derive(Debug, Default)]
pub struct BruteForceActor {
failed_attempts: HashMap<IpAddr, Vec<u64>>,
}
impl BruteForceActor {
pub fn clean_attempts(&mut self) {
let keys = self.failed_attempts
.keys()
.map(|i| i.clone())
.collect::<Vec<_>>();
for ip in keys {
// Remove old attempts
let attempts = self.failed_attempts.get_mut(&ip).unwrap();
attempts.retain(|i| i + KEEP_FAILED_LOGIN_ATTEMPTS_FOR > time());
// Remove empty entry keys
if attempts.is_empty() {
self.failed_attempts.remove(&ip);
}
}
}
pub fn insert_failed_attempt(&mut self, ip: IpAddr) {
if !self.failed_attempts.contains_key(&ip) {
self.failed_attempts.insert(ip, vec![time()]);
} else {
self.failed_attempts.get_mut(&ip).unwrap().push(time());
}
}
pub fn count_failed_attempts(&mut self, ip: &IpAddr) -> usize {
self.failed_attempts.get(ip).map(Vec::len).unwrap_or(0)
}
}
#[cfg(test)]
mod test {
use std::net::{IpAddr, Ipv4Addr};
use crate::actors::bruteforce_actor::BruteForceActor;
use crate::utils::time::time;
const IP_1: IpAddr = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
const IP_2: IpAddr = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2));
const IP_3: IpAddr = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 5));
#[test]
fn test_clean() {
let mut actor = BruteForceActor::default();
actor.failed_attempts.insert(IP_1, vec![1, 10]);
actor.failed_attempts.insert(IP_2, vec![1, 10, time() + 10]);
actor.failed_attempts.insert(IP_3, vec![time() + 10, time() + 20]);
actor.clean_attempts();
let keys = actor.failed_attempts.keys().collect::<Vec<_>>();
assert_eq!(keys.len(), 2);
assert_eq!(actor.count_failed_attempts(&IP_1), 0);
assert_eq!(actor.count_failed_attempts(&IP_2), 1);
assert_eq!(actor.count_failed_attempts(&IP_3), 2);
}
#[test]
fn test_insert_and_count() {
let mut actor = BruteForceActor::default();
assert_eq!(actor.count_failed_attempts(&IP_1), 0);
assert_eq!(actor.count_failed_attempts(&IP_2), 0);
actor.insert_failed_attempt(IP_1);
assert_eq!(actor.count_failed_attempts(&IP_1), 1);
assert_eq!(actor.count_failed_attempts(&IP_2), 0);
actor.insert_failed_attempt(IP_1);
assert_eq!(actor.count_failed_attempts(&IP_1), 2);
assert_eq!(actor.count_failed_attempts(&IP_2), 0);
actor.insert_failed_attempt(IP_2);
assert_eq!(actor.count_failed_attempts(&IP_1), 2);
assert_eq!(actor.count_failed_attempts(&IP_2), 1);
}
}

View File

@ -1 +1,2 @@
pub mod users_actor;
pub mod bruteforce_actor;

View File

@ -28,3 +28,7 @@ pub const ADMIN_ROUTES: &str = "/admin";
/// Auth route
pub const LOGIN_ROUTE: &str = "/login";
/// Bruteforce protection
pub const KEEP_FAILED_LOGIN_ATTEMPTS_FOR: u64 = 3600;
pub const MAX_FAILED_LOGIN_ATTEMPTS: u64 = 15;