Skip to main content

alibi_api/plugins/two_factor/
config.rs

1use super::*;
2/// Callback used by the two-factor plugin to deliver a one-time password.
3#[async_trait]
4pub trait SendTwoFactorOtp: Send + Sync {
5    /// Send a one-time password to the given user.
6    async fn send(&self, user: &UserView, otp: &str) -> AuthResult<()>;
7}
8
9/// Consecutive failed sign-in verifications across factors and challenges.
10#[derive(Debug, Clone)]
11pub struct AccountLockoutConfig {
12    pub enabled: bool,
13    pub max_failed_attempts: f64,
14    pub duration_seconds: f64,
15}
16
17impl Default for AccountLockoutConfig {
18    fn default() -> Self {
19        Self {
20            enabled: true,
21            max_failed_attempts: 10.0,
22            duration_seconds: 900.0,
23        }
24    }
25}
26
27/// Public configuration for the two-factor plugin.
28#[derive(Clone, alibi_core::PluginConfig)]
29#[plugin(name = "TwoFactorPlugin")]
30pub struct TwoFactorConfig {
31    /// Allow omission of passwords for users without a stored credential hash.
32    #[config(default = false)]
33    pub allow_passwordless: bool,
34    /// Override the global passwordless policy for existing TOTP URI retrieval.
35    #[config(default = None)]
36    pub totp_allow_passwordless: Option<bool>,
37    /// Override the global passwordless policy for backup regeneration.
38    #[config(default = None)]
39    pub backup_allow_passwordless: Option<bool>,
40    /// Number of generated backup codes, using JS array-length coercion.
41    #[config(default = 10.0)]
42    pub backup_code_amount: f64,
43    /// Generated characters per code, before the separator after character five.
44    #[config(default = 10.0)]
45    pub backup_code_length: f64,
46    /// Optional synchronous generator. Its strings are persisted unchanged.
47    #[config(default = None, skip)]
48    pub custom_backup_codes_generate:
49        Option<Arc<dyn Fn() -> AuthResult<Vec<String>> + Send + Sync>>,
50    #[config(default = TwoFactorBackupStorage::default(), skip)]
51    pub backup_storage: TwoFactorBackupStorage,
52    #[config(default = AccountLockoutConfig::default())]
53    pub account_lockout: AccountLockoutConfig,
54    /// Override the issuer embedded in enrollment TOTP URIs.
55    #[config(default = None)]
56    pub issuer: Option<String>,
57    /// Skip the enrollment verification step and enable 2FA immediately.
58    #[config(default = false)]
59    pub skip_verification_on_enable: bool,
60    /// Pending challenge lifetime in seconds, including fractions and explicit zero.
61    #[config(default = DEFAULT_TWO_FACTOR_COOKIE_MAX_AGE_SECS)]
62    pub two_factor_cookie_max_age: f64,
63    /// Trusted proof lifetime in seconds; negative values omit cookie Max-Age.
64    #[config(default = DEFAULT_TRUST_DEVICE_MAX_AGE_SECS)]
65    pub trust_device_max_age: f64,
66    /// TOTP period in seconds, retaining Source fractional and signed counters.
67    #[config(default = DEFAULT_TOTP_PERIOD_SECS)]
68    pub totp_period: f64,
69    /// Raw TOTP digits. Fractional values retain Source decimal-remainder output.
70    #[config(default = DEFAULT_TOTP_DIGITS)]
71    pub totp_digits: f64,
72    /// Issuer used when retrieving an existing authenticator URI.
73    #[config(default = None)]
74    pub totp_issuer: Option<String>,
75    /// Reject TOTP enrollment, URI retrieval, verification and server generation.
76    #[config(default = false)]
77    pub totp_disabled: bool,
78    /// Optional OTP sender callback. When absent, `/two-factor/send-otp` is disabled.
79    #[config(default = None, skip)]
80    pub send_otp: Option<Arc<dyn SendTwoFactorOtp>>,
81    /// Number of decimal digits in delivered OTPs, following JS numeric length.
82    #[config(default = 6.0)]
83    pub otp_digits: f64,
84    /// OTP lifetime in minutes; zero selects the pinned three-minute default.
85    #[config(default = 3.0)]
86    pub otp_period_minutes: f64,
87    /// Failed OTP budget; zero selects the pinned five-attempt default.
88    #[config(default = 5.0)]
89    pub otp_allowed_attempts: f64,
90    #[config(default = TwoFactorOtpStorage::default(), skip)]
91    pub otp_storage: TwoFactorOtpStorage,
92}
93
94impl std::fmt::Debug for TwoFactorConfig {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        f.debug_struct("TwoFactorConfig")
97            .field("allow_passwordless", &self.allow_passwordless)
98            .field("totp_allow_passwordless", &self.totp_allow_passwordless)
99            .field("backup_allow_passwordless", &self.backup_allow_passwordless)
100            .field("backup_code_amount", &self.backup_code_amount)
101            .field("backup_code_length", &self.backup_code_length)
102            .field(
103                "custom_backup_codes_generate",
104                &self.custom_backup_codes_generate.is_some(),
105            )
106            .field("backup_storage", &self.backup_storage)
107            .field("account_lockout", &self.account_lockout)
108            .field("issuer", &self.issuer)
109            .field(
110                "skip_verification_on_enable",
111                &self.skip_verification_on_enable,
112            )
113            .field("two_factor_cookie_max_age", &self.two_factor_cookie_max_age)
114            .field("trust_device_max_age", &self.trust_device_max_age)
115            .field("totp_period", &self.totp_period)
116            .field("totp_digits", &self.totp_digits)
117            .field("totp_issuer", &self.totp_issuer)
118            .field("totp_disabled", &self.totp_disabled)
119            .field("send_otp", &self.send_otp.as_ref().map(|_| "custom"))
120            .field("otp_digits", &self.otp_digits)
121            .field("otp_period_minutes", &self.otp_period_minutes)
122            .field("otp_allowed_attempts", &self.otp_allowed_attempts)
123            .field("otp_storage", &self.otp_storage)
124            .finish()
125    }
126}