gardesk/garlock / af3cae8

Browse files

Add cooldown after repeated failed attempts

- Block authentication during cooldown period
- Start cooldown after max_attempts failures (default 3)
- Configurable cooldown duration (default 5 seconds)
- Reset attempt counter after cooldown expires
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
af3cae81012f8d00e846e9a8704ebeba010be80f
Parents
a9436ea
Tree
832591b

2 changed files

StatusFile+-
M garlock/src/main.rs 15 2
M garlock/src/state.rs 41 0
garlock/src/main.rsmodified
@@ -286,7 +286,17 @@ fn run_lock(config: Config) -> Result<()> {
286286
                             }
287287
 
288288
                             KeyResult::Enter => {
289
-                                if !password.is_empty() && pending_auth.is_none() {
289
+                                // Block attempts during cooldown
290
+                                if locker_state.is_in_cooldown() {
291
+                                    let remaining = locker_state.cooldown_remaining();
292
+                                    tracing::warn!(
293
+                                        remaining_seconds = remaining,
294
+                                        "Authentication blocked: cooldown active"
295
+                                    );
296
+                                    password.clear();
297
+                                    ring.clear_highlight();
298
+                                    needs_redraw = true;
299
+                                } else if !password.is_empty() && pending_auth.is_none() {
290300
                                     locker_state.start_validation();
291301
                                     ring.set_state(locker_state.ring_state());
292302
                                     needs_redraw = true;
@@ -350,7 +360,10 @@ fn run_lock(config: Config) -> Result<()> {
350360
                                 ring.set_state(locker_state.ring_state());
351361
                                 needs_redraw = true;
352362
 
353
-                                // TODO: Check for cooldown after max_attempts
363
+                                // Start cooldown after too many failures
364
+                                if locker_state.failed_attempts >= config.general.max_attempts {
365
+                                    locker_state.start_cooldown(config.general.cooldown_seconds.into());
366
+                                }
354367
                             }
355368
                         }
356369
                         pending_auth = None;
garlock/src/state.rsmodified
@@ -83,6 +83,8 @@ pub struct LockerState {
8383
     invalid_since: Option<Instant>,
8484
     /// Number of failed authentication attempts
8585
     pub failed_attempts: u32,
86
+    /// Cooldown end time (if in cooldown after too many failures)
87
+    cooldown_until: Option<Instant>,
8688
 }
8789
 
8890
 impl LockerState {
@@ -94,6 +96,7 @@ impl LockerState {
9496
             last_input: None,
9597
             invalid_since: None,
9698
             failed_attempts: 0,
99
+            cooldown_until: None,
97100
         }
98101
     }
99102
 
@@ -125,6 +128,34 @@ impl LockerState {
125128
         tracing::warn!(attempts = self.failed_attempts, "Authentication failed");
126129
     }
127130
 
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
+
128159
     /// Check and update timers, returning true if state changed
129160
     pub fn update_timers(&mut self) -> bool {
130161
         let mut changed = false;
@@ -152,6 +183,16 @@ impl LockerState {
152183
             }
153184
         }
154185
 
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
+
155196
         changed
156197
     }
157198