mockforge_core/security/
mfa_tracking.rs

1//! Multi-factor authentication (MFA) tracking for privileged users
2//!
3//! This module provides MFA status tracking and enforcement for privileged access management.
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10use uuid::Uuid;
11
12/// MFA method types
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum MfaMethod {
16    /// Time-based one-time password (TOTP)
17    Totp,
18    /// SMS-based verification
19    Sms,
20    /// Email-based verification
21    Email,
22    /// Hardware security key (FIDO2/WebAuthn)
23    HardwareKey,
24    /// Push notification
25    Push,
26}
27
28/// MFA status for a user
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct MfaStatus {
31    /// User ID
32    pub user_id: Uuid,
33    /// Whether MFA is enabled
34    pub enabled: bool,
35    /// MFA methods configured
36    pub methods: Vec<MfaMethod>,
37    /// When MFA was enabled
38    pub enabled_at: Option<DateTime<Utc>>,
39    /// Last MFA verification
40    pub last_verification: Option<DateTime<Utc>>,
41    /// Backup codes remaining
42    pub backup_codes_remaining: u32,
43}
44
45/// MFA storage trait
46#[async_trait::async_trait]
47pub trait MfaStorage: Send + Sync {
48    /// Get MFA status for a user
49    async fn get_mfa_status(&self, user_id: Uuid) -> Result<Option<MfaStatus>, crate::Error>;
50
51    /// Set MFA status for a user
52    async fn set_mfa_status(&self, status: MfaStatus) -> Result<(), crate::Error>;
53
54    /// Get all users with MFA enabled
55    async fn get_users_with_mfa(&self) -> Result<Vec<Uuid>, crate::Error>;
56
57    /// Get all privileged users without MFA
58    async fn get_privileged_users_without_mfa(
59        &self,
60        privileged_user_ids: &[Uuid],
61    ) -> Result<Vec<Uuid>, crate::Error>;
62}
63
64/// In-memory MFA storage (for development/testing)
65pub struct InMemoryMfaStorage {
66    mfa_statuses: Arc<RwLock<HashMap<Uuid, MfaStatus>>>,
67}
68
69impl InMemoryMfaStorage {
70    /// Create a new in-memory MFA storage
71    pub fn new() -> Self {
72        Self {
73            mfa_statuses: Arc::new(RwLock::new(HashMap::new())),
74        }
75    }
76}
77
78impl Default for InMemoryMfaStorage {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84#[async_trait::async_trait]
85impl MfaStorage for InMemoryMfaStorage {
86    async fn get_mfa_status(&self, user_id: Uuid) -> Result<Option<MfaStatus>, crate::Error> {
87        let statuses = self.mfa_statuses.read().await;
88        Ok(statuses.get(&user_id).cloned())
89    }
90
91    async fn set_mfa_status(&self, status: MfaStatus) -> Result<(), crate::Error> {
92        let mut statuses = self.mfa_statuses.write().await;
93        statuses.insert(status.user_id, status);
94        Ok(())
95    }
96
97    async fn get_users_with_mfa(&self) -> Result<Vec<Uuid>, crate::Error> {
98        let statuses = self.mfa_statuses.read().await;
99        Ok(statuses
100            .iter()
101            .filter(|(_, status)| status.enabled)
102            .map(|(user_id, _)| *user_id)
103            .collect())
104    }
105
106    async fn get_privileged_users_without_mfa(
107        &self,
108        privileged_user_ids: &[Uuid],
109    ) -> Result<Vec<Uuid>, crate::Error> {
110        let statuses = self.mfa_statuses.read().await;
111        Ok(privileged_user_ids
112            .iter()
113            .filter(|user_id| {
114                statuses.get(user_id).map(|s| !s.enabled).unwrap_or(true) // If no status, assume MFA not enabled
115            })
116            .copied()
117            .collect())
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[tokio::test]
126    async fn test_mfa_storage() {
127        let storage = InMemoryMfaStorage::new();
128        let user_id = Uuid::new_v4();
129        let status = MfaStatus {
130            user_id,
131            enabled: true,
132            methods: vec![MfaMethod::Totp],
133            enabled_at: Some(Utc::now()),
134            last_verification: Some(Utc::now()),
135            backup_codes_remaining: 5,
136        };
137
138        storage.set_mfa_status(status).await.unwrap();
139        let retrieved = storage.get_mfa_status(user_id).await.unwrap();
140        assert!(retrieved.is_some());
141        assert!(retrieved.unwrap().enabled);
142    }
143}