use crate::error::AccessError;
use ring::hmac;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct MFAConfig {
secret_key: Vec<u8>,
token_validity: u64, max_attempts: u32,
attempt_window: u64, }
pub struct MFAManager {
config: MFAConfig,
failed_attempts: std::collections::HashMap<String, Vec<u64>>,
}
impl MFAManager {
pub fn new(config: MFAConfig) -> Self {
Self {
config,
failed_attempts: std::collections::HashMap::new(),
}
}
pub fn generate_totp(&self, user: &str) -> Result<String, AccessError> {
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() / 30;
let key = hmac::Key::new(hmac::HMAC_SHA256, &self.config.secret_key);
let tag = hmac::sign(&key, format!("{}{}", user, current_time).as_bytes());
let code = format!("{:06}", tag.as_ref()[0..4].iter()
.fold(0u32, |acc, &x| acc * 256 + x as u32) % 1_000_000);
Ok(code)
}
pub fn verify_totp(&mut self, user: &str, token: &str) -> Result<bool, AccessError> {
if self.is_rate_limited(user) {
return Err(AccessError::TooManyAttempts);
}
let expected = self.generate_totp(user)?;
let valid = token == expected;
if !valid {
self.record_failed_attempt(user);
}
Ok(valid)
}
fn is_rate_limited(&self, user: &str) -> bool {
if let Some(attempts) = self.failed_attempts.get(user) {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let recent_attempts = attempts.iter()
.filter(|&×tamp| now - timestamp < self.config.attempt_window)
.count();
recent_attempts >= self.config.max_attempts as usize
} else {
false
}
}
fn record_failed_attempt(&mut self, user: &str) {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
self.failed_attempts
.entry(user.to_string())
.or_insert_with(Vec::new)
.push(now);
}
}