From 05e911bfc5246af88e5b8757f4fea70547d79fc3 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Sun, 3 Apr 2022 16:21:09 +0200 Subject: [PATCH] Start to implement brute force protection --- README.md | 8 ++-- src/actors/bruteforce_actor.rs | 86 ++++++++++++++++++++++++++++++++++ src/actors/mod.rs | 1 + src/constants.rs | 4 ++ 4 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/actors/bruteforce_actor.rs diff --git a/README.md b/README.md index 7836af9..f854c72 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ -TODO list -- [ ] Bruteforce protection -- [ ] CRSF protection \ No newline at end of file +# Basic OIDC +Basic OpenID provider. Still under early development. + +TODO : +- [ ] Bruteforce protection \ No newline at end of file diff --git a/src/actors/bruteforce_actor.rs b/src/actors/bruteforce_actor.rs new file mode 100644 index 0000000..3ef594d --- /dev/null +++ b/src/actors/bruteforce_actor.rs @@ -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>, +} + +impl BruteForceActor { + pub fn clean_attempts(&mut self) { + let keys = self.failed_attempts + .keys() + .map(|i| i.clone()) + .collect::>(); + + 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::>(); + 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); + } +} diff --git a/src/actors/mod.rs b/src/actors/mod.rs index 33c1250..fc4d21e 100644 --- a/src/actors/mod.rs +++ b/src/actors/mod.rs @@ -1 +1,2 @@ pub mod users_actor; +pub mod bruteforce_actor; \ No newline at end of file diff --git a/src/constants.rs b/src/constants.rs index 303bdbb..352eb65 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -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;