Skip to main content

kipuka/config/
lockout.rs

1//! Authentication failure-lockout configuration (NIAP CA PP FIA_AFL.1).
2//!
3//! The `[auth_lockout]` section tunes the failure counter and lockout applied
4//! by [`crate::auth::failure_tracker`].  When absent, defaults apply; when
5//! `max_failures = 0`, the control is disabled and authentication behaves as
6//! it did before the control existed.
7//!
8//! ```toml
9//! [auth_lockout]
10//! max_failures = 5
11//! failure_window_secs = 300
12//! lockout_duration_secs = 900
13//! ```
14
15use serde::Deserialize;
16
17/// `[auth_lockout]` — FIA_AFL.1 authentication failure handling.
18#[derive(Debug, Clone, Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct LockoutConfig {
21    /// Consecutive failed authentication attempts (per claimed identity,
22    /// within `failure_window_secs`) that trigger a lockout.
23    ///
24    /// `0` disables the control.  Default: `5`.
25    #[serde(default = "default_max_failures")]
26    pub max_failures: u32,
27
28    /// Sliding window (seconds) over which failures are counted.  A gap longer
29    /// than this between failures resets the counter.  Default: `300` (5 min).
30    #[serde(default = "default_failure_window_secs")]
31    pub failure_window_secs: u64,
32
33    /// How long (seconds) an identity remains locked out once the threshold is
34    /// reached.  Default: `900` (15 min).
35    #[serde(default = "default_lockout_duration_secs")]
36    pub lockout_duration_secs: u64,
37}
38
39fn default_max_failures() -> u32 {
40    5
41}
42
43fn default_failure_window_secs() -> u64 {
44    300
45}
46
47fn default_lockout_duration_secs() -> u64 {
48    900
49}
50
51impl Default for LockoutConfig {
52    fn default() -> Self {
53        Self {
54            max_failures: default_max_failures(),
55            failure_window_secs: default_failure_window_secs(),
56            lockout_duration_secs: default_lockout_duration_secs(),
57        }
58    }
59}
60
61impl LockoutConfig {
62    /// Validate semantic constraints.
63    pub fn validate(&self) -> Result<(), String> {
64        // Disabled control: no further constraints.
65        if self.max_failures == 0 {
66            return Ok(());
67        }
68        if self.failure_window_secs == 0 {
69            return Err(
70                "[auth_lockout].failure_window_secs must be at least 1 (or set max_failures = 0 to disable)".into(),
71            );
72        }
73        if self.lockout_duration_secs == 0 {
74            return Err(
75                "[auth_lockout].lockout_duration_secs must be at least 1 (or set max_failures = 0 to disable)".into(),
76            );
77        }
78        Ok(())
79    }
80
81    /// Build the runtime [`crate::auth::failure_tracker::LockoutPolicy`].
82    pub fn to_policy(&self) -> crate::auth::failure_tracker::LockoutPolicy {
83        crate::auth::failure_tracker::LockoutPolicy {
84            max_failures: self.max_failures,
85            failure_window: std::time::Duration::from_secs(self.failure_window_secs),
86            lockout_duration: std::time::Duration::from_secs(self.lockout_duration_secs),
87        }
88    }
89}