Start to implement brute force protection
This commit is contained in:
parent
b965fa6b4f
commit
05e911bfc5
@ -1,3 +1,5 @@
|
||||
TODO list
|
||||
# Basic OIDC
|
||||
Basic OpenID provider. Still under early development.
|
||||
|
||||
TODO :
|
||||
- [ ] Bruteforce protection
|
||||
- [ ] CRSF protection
|
86
src/actors/bruteforce_actor.rs
Normal file
86
src/actors/bruteforce_actor.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
pub mod users_actor;
|
||||
pub mod bruteforce_actor;
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user