@@ -83,6 +83,8 @@ pub struct LockerState { |
| 83 | 83 | invalid_since: Option<Instant>, |
| 84 | 84 | /// Number of failed authentication attempts |
| 85 | 85 | pub failed_attempts: u32, |
| 86 | + /// Cooldown end time (if in cooldown after too many failures) |
| 87 | + cooldown_until: Option<Instant>, |
| 86 | 88 | } |
| 87 | 89 | |
| 88 | 90 | impl LockerState { |
@@ -94,6 +96,7 @@ impl LockerState { |
| 94 | 96 | last_input: None, |
| 95 | 97 | invalid_since: None, |
| 96 | 98 | failed_attempts: 0, |
| 99 | + cooldown_until: None, |
| 97 | 100 | } |
| 98 | 101 | } |
| 99 | 102 | |
@@ -125,6 +128,34 @@ impl LockerState { |
| 125 | 128 | tracing::warn!(attempts = self.failed_attempts, "Authentication failed"); |
| 126 | 129 | } |
| 127 | 130 | |
| 131 | + /// Check if currently in cooldown period |
| 132 | + pub fn is_in_cooldown(&self) -> bool { |
| 133 | + self.cooldown_until |
| 134 | + .map(|t| Instant::now() < t) |
| 135 | + .unwrap_or(false) |
| 136 | + } |
| 137 | + |
| 138 | + /// Start a cooldown period after too many failed attempts |
| 139 | + pub fn start_cooldown(&mut self, seconds: u64) { |
| 140 | + let duration = Duration::from_secs(seconds); |
| 141 | + self.cooldown_until = Some(Instant::now() + duration); |
| 142 | + tracing::warn!(seconds, "Cooldown started after {} failed attempts", self.failed_attempts); |
| 143 | + } |
| 144 | + |
| 145 | + /// Get remaining cooldown time in seconds (0 if not in cooldown) |
| 146 | + pub fn cooldown_remaining(&self) -> u64 { |
| 147 | + self.cooldown_until |
| 148 | + .map(|t| { |
| 149 | + let now = Instant::now(); |
| 150 | + if now < t { |
| 151 | + (t - now).as_secs() |
| 152 | + } else { |
| 153 | + 0 |
| 154 | + } |
| 155 | + }) |
| 156 | + .unwrap_or(0) |
| 157 | + } |
| 158 | + |
| 128 | 159 | /// Check and update timers, returning true if state changed |
| 129 | 160 | pub fn update_timers(&mut self) -> bool { |
| 130 | 161 | let mut changed = false; |
@@ -152,6 +183,16 @@ impl LockerState { |
| 152 | 183 | } |
| 153 | 184 | } |
| 154 | 185 | |
| 186 | + // Check cooldown expiry |
| 187 | + if let Some(until) = self.cooldown_until { |
| 188 | + if Instant::now() >= until { |
| 189 | + self.cooldown_until = None; |
| 190 | + self.failed_attempts = 0; // Reset attempts after cooldown |
| 191 | + tracing::info!("Cooldown expired, attempts reset"); |
| 192 | + changed = true; |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 155 | 196 | changed |
| 156 | 197 | } |
| 157 | 198 | |