diff --git a/backend/src/security/account_lockout.rs b/backend/src/security/account_lockout.rs index 3822a63..8194e4b 100644 --- a/backend/src/security/account_lockout.rs +++ b/backend/src/security/account_lockout.rs @@ -1,119 +1,122 @@ -use anyhow::Result; -use mongodb::bson::{doc, DateTime}; -use mongodb::Collection; -use std::sync::Arc; -use tokio::sync::RwLock; - -#[derive(Clone)] -pub struct AccountLockout { - user_collection: Arc>>, - max_attempts: u32, - base_duration_minutes: u32, - max_duration_minutes: u32, -} - -impl AccountLockout { - pub fn new( - user_collection: Collection, - max_attempts: u32, - base_duration_minutes: u32, - max_duration_minutes: u32, - ) -> Self { - Self { - user_collection: Arc::new(RwLock::new(user_collection)), - max_attempts, - base_duration_minutes, - max_duration_minutes, - } - } - - pub async fn check_lockout(&self, email: &str) -> Result { - let collection = self.user_collection.read().await; - let user = collection.find_one(doc! { "email": email }, None).await?; - - if let Some(user_doc) = user { - if let Some(locked_until_val) = user_doc.get("locked_until") { - if let Some(dt) = locked_until_val.as_datetime() { - let now = DateTime::now(); - if dt.timestamp_millis() > now.timestamp_millis() { - return Ok(true); // Account is locked - } - } - } - } - - Ok(false) // Account is not locked - } - - pub async fn record_failed_attempt(&self, email: &str) -> Result { - let collection = self.user_collection.write().await; - - // Get current failed attempts - let user = collection.find_one(doc! { "email": email }, None).await?; - - let current_attempts = if let Some(user_doc) = user { - user_doc - .get("failed_login_attempts") - .and_then(|v| v.as_i64()) - .unwrap_or(0) as u32 - } else { - 0 - }; - - let new_attempts = current_attempts + 1; - let should_lock = new_attempts >= self.max_attempts; - - // Calculate lockout duration - let lock_duration = if should_lock { - let multiplier = (new_attempts as u32).saturating_sub(self.max_attempts) + 1; - let duration = self.base_duration_minutes * multiplier; - std::cmp::min(duration, self.max_duration_minutes) - } else { - 0 - }; - - let locked_until = if lock_duration > 0 { - let now = DateTime::now(); - let duration_millis = lock_duration as u64 * 60 * 1000; - DateTime::from_millis(now.timestamp_millis() + duration_millis as i64) - } else { - DateTime::now() - }; - - // Update user - collection - .update_one( - doc! { "email": email }, - doc! { - "$set": { - "failed_login_attempts": new_attempts as i32, - "last_failed_login": DateTime::now(), - "locked_until": locked_until, - } - }, - None, - ) - .await?; - - Ok(should_lock) - } - - pub async fn reset_attempts(&self, email: &str) -> Result<()> { - let collection = self.user_collection.write().await; - - collection - .update_one( - doc! { "email": email }, - doc! { - "$set": { - "failed_login_attempts": 0, - "locked_until": null, - } - }, - None, - ) - .await?; - - Ok(()) - } -} +### /home/asoliver/desarrollo/normogen/backend/src/security/account_lockout.rs +```rust +1: use anyhow::Result; +2: use mongodb::bson::{doc, DateTime}; +3: use mongodb::Collection; +4: use std::sync::Arc; +5: use tokio::sync::RwLock; +6: +7: #[derive(Clone)] +8: pub struct AccountLockout { +9: user_collection: Arc>>, +10: max_attempts: u32, +11: base_duration_minutes: u32, +12: max_duration_minutes: u32, +13: } +14: +15: impl AccountLockout { +16: pub fn new( +17: user_collection: Collection, +18: max_attempts: u32, +19: base_duration_minutes: u32, +20: max_duration_minutes: u32, +21: ) -> Self { +22: Self { +23: user_collection: Arc::new(RwLock::new(user_collection)), +24: max_attempts, +25: base_duration_minutes, +26: max_duration_minutes, +27: } +28: } +29: +30: pub async fn check_lockout(&self, email: &str) -> Result { +31: let collection = self.user_collection.read().await; +32: let user = collection.find_one(doc! { "email": email }, None).await?; +33: +34: if let Some(user_doc) = user { +35: if let Some(locked_until_val) = user_doc.get("locked_until") { +36: if let Some(dt) = locked_until_val.as_datetime() { +37: let now = DateTime::now(); +38: if dt.timestamp_millis() > now.timestamp_millis() { +39: return Ok(true); // Account is locked +40: } +41: } +42: } +43: } +44: +45: Ok(false) // Account is not locked +46: } +47: +48: pub async fn record_failed_attempt(&self, email: &str) -> Result { +49: let collection = self.user_collection.write().await; +50: +51: // Get current failed attempts +52: let user = collection.find_one(doc! { "email": email }, None).await?; +53: +54: let current_attempts = if let Some(user_doc) = user { +55: user_doc +56: .get("failed_login_attempts") +57: .and_then(|v| v.as_i64()) +58: .unwrap_or(0) as u32 +59: } else { +60: 0 +61: }; +62: +63: let new_attempts = current_attempts + 1; +64: let should_lock = new_attempts >= self.max_attempts; +65: +66: // Calculate lockout duration +67: let lock_duration = if should_lock { +68: let multiplier = new_attempts.saturating_sub(self.max_attempts).saturating_sub(self.max_attempts) + 1; +69: let duration = self.base_duration_minutes * multiplier; +70: std::cmp::min(duration, self.max_duration_minutes) +71: } else { +72: 0 +73: }; +74: +75: let locked_until = if lock_duration > 0 { +76: let now = DateTime::now(); +77: let duration_millis = lock_duration as u64 * 60 * 1000; +78: DateTime::from_millis(now.timestamp_millis() + duration_millis as i64) +79: } else { +80: DateTime::now() +81: }; +82: +83: // Update user +84: collection +85: .update_one( +86: doc! { "email": email }, +87: doc! { +88: "$set": { +89: "failed_login_attempts": new_attempts as i32, +90: "last_failed_login": DateTime::now(), +91: "locked_until": locked_until, +92: } +93: }, +94: None, +95: ) +96: .await?; +97: +98: Ok(should_lock) +99: } +100: +101: pub async fn reset_attempts(&self, email: &str) -> Result<()> { +102: let collection = self.user_collection.write().await; +103: +104: collection +105: .update_one( +106: doc! { "email": email }, +107: doc! { +108: "$set": { +109: "failed_login_attempts": 0, +110: "locked_until": null, +111: } +112: }, +113: None, +114: ) +115: .await?; +116: +117: Ok(()) +118: } +119: } +```