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(&self, privileged_user_ids: &[Uuid]) -> Result<Vec<Uuid>, crate::Error>;
59}
60
61/// In-memory MFA storage (for development/testing)
62pub struct InMemoryMfaStorage {
63    mfa_statuses: Arc<RwLock<HashMap<Uuid, MfaStatus>>>,
64}
65
66impl InMemoryMfaStorage {
67    /// Create a new in-memory MFA storage
68    pub fn new() -> Self {
69        Self {
70            mfa_statuses: Arc::new(RwLock::new(HashMap::new())),
71        }
72    }
73}
74
75impl Default for InMemoryMfaStorage {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80
81#[async_trait::async_trait]
82impl MfaStorage for InMemoryMfaStorage {
83    async fn get_mfa_status(&self, user_id: Uuid) -> Result<Option<MfaStatus>, crate::Error> {
84        let statuses = self.mfa_statuses.read().await;
85        Ok(statuses.get(&user_id).cloned())
86    }
87
88    async fn set_mfa_status(&self, status: MfaStatus) -> Result<(), crate::Error> {
89        let mut statuses = self.mfa_statuses.write().await;
90        statuses.insert(status.user_id, status);
91        Ok(())
92    }
93
94    async fn get_users_with_mfa(&self) -> Result<Vec<Uuid>, crate::Error> {
95        let statuses = self.mfa_statuses.read().await;
96        Ok(statuses
97            .iter()
98            .filter(|(_, status)| status.enabled)
99            .map(|(user_id, _)| *user_id)
100            .collect())
101    }
102
103    async fn get_privileged_users_without_mfa(&self, privileged_user_ids: &[Uuid]) -> Result<Vec<Uuid>, crate::Error> {
104        let statuses = self.mfa_statuses.read().await;
105        Ok(privileged_user_ids
106            .iter()
107            .filter(|user_id| {
108                statuses
109                    .get(user_id)
110                    .map(|s| !s.enabled)
111                    .unwrap_or(true) // If no status, assume MFA not enabled
112            })
113            .copied()
114            .collect())
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[tokio::test]
123    async fn test_mfa_storage() {
124        let storage = InMemoryMfaStorage::new();
125        let user_id = Uuid::new_v4();
126        let status = MfaStatus {
127            user_id,
128            enabled: true,
129            methods: vec![MfaMethod::Totp],
130            enabled_at: Some(Utc::now()),
131            last_verification: Some(Utc::now()),
132            backup_codes_remaining: 5,
133        };
134
135        storage.set_mfa_status(status).await.unwrap();
136        let retrieved = storage.get_mfa_status(user_id).await.unwrap();
137        assert!(retrieved.is_some());
138        assert!(retrieved.unwrap().enabled);
139    }
140}