auth_framework/auth_modular/mfa/
backup_codes.rs

1//! Backup codes manager for MFA
2
3use crate::errors::Result;
4use crate::storage::AuthStorage;
5use std::sync::Arc;
6use tracing::{debug, info};
7
8/// Backup codes manager for handling backup codes
9pub struct BackupCodesManager {
10    storage: Arc<dyn AuthStorage>,
11}
12
13impl BackupCodesManager {
14    /// Create a new backup codes manager
15    pub fn new(storage: Arc<dyn AuthStorage>) -> Self {
16        Self { storage }
17    }
18
19    /// Generate backup codes for a user
20    pub async fn generate_codes(&self, user_id: &str, count: usize) -> Result<Vec<String>> {
21        debug!("Generating {} backup codes for user '{}'", count, user_id);
22
23        let codes: Vec<String> = (0..count)
24            .map(|_| format!("{:08}", rand::random::<u32>() % 100000000))
25            .collect();
26
27        // Store backup codes for the user
28        let backup_key = format!("user:{}:backup_codes", user_id);
29        let codes_json = serde_json::to_string(&codes).unwrap_or("[]".to_string());
30        self.storage
31            .store_kv(&backup_key, codes_json.as_bytes(), None)
32            .await?;
33
34        info!("Generated {} backup codes for user '{}'", count, user_id);
35        Ok(codes)
36    }
37
38    /// Verify backup code and mark it as used
39    pub async fn verify_code(&self, user_id: &str, code: &str) -> Result<bool> {
40        debug!("Verifying backup code for user '{}'", user_id);
41
42        // Validate code format
43        if code.len() != 8 || !code.chars().all(|c| c.is_ascii_digit()) {
44            return Ok(false);
45        }
46
47        // Get user's backup codes
48        let backup_key = format!("user:{}:backup_codes", user_id);
49        if let Some(codes_data) = self.storage.get_kv(&backup_key).await? {
50            let codes_str = std::str::from_utf8(&codes_data).unwrap_or("[]");
51            let mut backup_codes: Vec<String> = serde_json::from_str(codes_str).unwrap_or_default();
52
53            if let Some(index) = backup_codes.iter().position(|c| c == code) {
54                // Mark code as used by removing it
55                backup_codes.remove(index);
56                let updated_codes =
57                    serde_json::to_string(&backup_codes).unwrap_or("[]".to_string());
58                self.storage
59                    .store_kv(&backup_key, updated_codes.as_bytes(), None)
60                    .await?;
61
62                info!("Backup code verified and consumed for user '{}'", user_id);
63                Ok(true)
64            } else {
65                Ok(false)
66            }
67        } else {
68            Ok(false)
69        }
70    }
71
72    /// Get remaining backup codes count
73    pub async fn get_remaining_count(&self, user_id: &str) -> Result<usize> {
74        debug!("Getting remaining backup codes for user '{}'", user_id);
75
76        let backup_key = format!("user:{}:backup_codes", user_id);
77        if let Some(codes_data) = self.storage.get_kv(&backup_key).await? {
78            let codes_str = std::str::from_utf8(&codes_data).unwrap_or("[]");
79            let backup_codes: Vec<String> = serde_json::from_str(codes_str).unwrap_or_default();
80            Ok(backup_codes.len())
81        } else {
82            Ok(0)
83        }
84    }
85
86    /// Check if user has backup codes
87    pub async fn has_backup_codes(&self, user_id: &str) -> Result<bool> {
88        let count = self.get_remaining_count(user_id).await?;
89        Ok(count > 0)
90    }
91
92    /// Regenerate backup codes (invalidating old ones)
93    pub async fn regenerate_codes(&self, user_id: &str, count: usize) -> Result<Vec<String>> {
94        info!("Regenerating backup codes for user '{}'", user_id);
95
96        // This will overwrite existing codes
97        self.generate_codes(user_id, count).await
98    }
99}
100
101