use crate::{
audit::{AuditConfig, AuditEvent, AuditEventType, AuditLogger, AuditSeverity, events},
config::AuthConfig,
jwt::{JwtConfig, JwtManager, TokenPair},
models::*,
storage::{StorageBackend, create_storage_backend},
};
use chrono::{DateTime, Utc};
use pulseengine_mcp_protocol::{Request, Response};
use std::collections::HashMap;
use std::sync::Arc;
use thiserror::Error;
use tokio::sync::RwLock;
use tracing::{debug, error, info, warn};
#[derive(Debug, Clone)]
pub struct RequestContext {
pub user_id: Option<String>,
pub roles: Vec<Role>,
}
#[derive(Debug, Error, serde::Serialize)]
pub enum AuthError {
#[error("Authentication failed: {0}")]
Failed(String),
#[error("Configuration error: {0}")]
Config(String),
#[error("Storage error: {0}")]
Storage(String),
#[error("Validation error: {0}")]
Validation(String),
}
pub struct AuthenticationManager {
config: AuthConfig,
validation_config: ValidationConfig,
storage: Arc<dyn StorageBackend>,
api_keys_cache: Arc<RwLock<std::collections::HashMap<String, ApiKey>>>,
rate_limit_state: Arc<RwLock<HashMap<String, RateLimitState>>>,
role_rate_limit_state: Arc<RwLock<HashMap<String, HashMap<String, RoleRateLimitStats>>>>,
audit_logger: Arc<AuditLogger>,
jwt_manager: Arc<JwtManager>,
}
#[derive(Debug, Clone)]
pub struct RateLimitState {
pub failed_attempts: u32,
pub window_start: DateTime<Utc>,
pub blocked_until: Option<DateTime<Utc>>,
pub successful_requests: u32,
pub success_window_start: DateTime<Utc>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RoleRateLimitConfig {
pub max_requests_per_window: u32,
pub window_duration_minutes: u64,
pub burst_allowance: u32,
pub cooldown_duration_minutes: u64,
}
#[derive(Debug, Clone)]
pub struct ValidationConfig {
pub max_failed_attempts: u32,
pub failed_attempt_window_minutes: u64,
pub block_duration_minutes: u64,
pub session_timeout_minutes: u64,
pub strict_ip_validation: bool,
pub enable_role_based_rate_limiting: bool,
pub role_rate_limits: std::collections::HashMap<String, RoleRateLimitConfig>,
}
impl Default for ValidationConfig {
fn default() -> Self {
let mut role_rate_limits = std::collections::HashMap::new();
role_rate_limits.insert(
"admin".to_string(),
RoleRateLimitConfig {
max_requests_per_window: 1000,
window_duration_minutes: 60,
burst_allowance: 100,
cooldown_duration_minutes: 5,
},
);
role_rate_limits.insert(
"operator".to_string(),
RoleRateLimitConfig {
max_requests_per_window: 500,
window_duration_minutes: 60,
burst_allowance: 50,
cooldown_duration_minutes: 10,
},
);
role_rate_limits.insert(
"monitor".to_string(),
RoleRateLimitConfig {
max_requests_per_window: 200,
window_duration_minutes: 60,
burst_allowance: 20,
cooldown_duration_minutes: 15,
},
);
role_rate_limits.insert(
"device".to_string(),
RoleRateLimitConfig {
max_requests_per_window: 100,
window_duration_minutes: 60,
burst_allowance: 10,
cooldown_duration_minutes: 20,
},
);
role_rate_limits.insert(
"custom".to_string(),
RoleRateLimitConfig {
max_requests_per_window: 50,
window_duration_minutes: 60,
burst_allowance: 5,
cooldown_duration_minutes: 30,
},
);
Self {
max_failed_attempts: 4,
failed_attempt_window_minutes: 15,
block_duration_minutes: 30,
session_timeout_minutes: 480, strict_ip_validation: true,
enable_role_based_rate_limiting: true,
role_rate_limits,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RateLimitStats {
pub total_tracked_ips: usize,
pub currently_blocked_ips: u32,
pub total_failed_attempts: u64,
pub role_stats: std::collections::HashMap<String, RoleRateLimitStats>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RoleRateLimitStats {
pub current_requests: u32,
pub blocked_requests: u64,
pub total_requests: u64,
pub in_cooldown: bool,
pub cooldown_ends_at: Option<DateTime<Utc>>,
pub last_window_start: Option<DateTime<Utc>>,
}
impl AuthenticationManager {
pub async fn new(config: AuthConfig) -> Result<Self, AuthError> {
let storage = create_storage_backend(&config.storage)
.await
.map_err(|e| AuthError::Storage(e.to_string()))?;
let audit_config = AuditConfig::default();
let audit_logger =
Arc::new(AuditLogger::new(audit_config).await.map_err(|e| {
AuthError::Config(format!("Failed to initialize audit logger: {}", e))
})?);
let jwt_config = JwtConfig::default();
let jwt_manager =
Arc::new(JwtManager::new(jwt_config).map_err(|e| {
AuthError::Config(format!("Failed to initialize JWT manager: {}", e))
})?);
let manager = Self {
storage,
validation_config: ValidationConfig::default(),
api_keys_cache: Arc::new(RwLock::new(HashMap::new())),
rate_limit_state: Arc::new(RwLock::new(HashMap::new())),
role_rate_limit_state: Arc::new(RwLock::new(HashMap::new())),
audit_logger,
jwt_manager,
config,
};
manager.refresh_cache().await?;
let startup_event = AuditEvent::new(
AuditEventType::SystemStartup,
AuditSeverity::Info,
"auth_manager".to_string(),
"Authentication manager initialized successfully".to_string(),
);
let _ = manager.audit_logger.log(startup_event).await;
info!("Authentication manager initialized successfully");
Ok(manager)
}
pub fn new_disabled() -> Self {
use crate::storage::MemoryStorage;
let storage = Arc::new(MemoryStorage::new()) as Arc<dyn StorageBackend>;
let audit_logger = Arc::new(AuditLogger::new_disabled());
let jwt_manager =
Arc::new(JwtManager::new(JwtConfig::default()).expect("Failed to create JWT manager"));
let config = AuthConfig {
enabled: false,
..Default::default()
};
Self {
storage,
validation_config: ValidationConfig::default(),
api_keys_cache: Arc::new(RwLock::new(HashMap::new())),
rate_limit_state: Arc::new(RwLock::new(HashMap::new())),
role_rate_limit_state: Arc::new(RwLock::new(HashMap::new())),
audit_logger,
jwt_manager,
config,
}
}
pub async fn new_with_validation(
config: AuthConfig,
validation_config: ValidationConfig,
) -> Result<Self, AuthError> {
let storage = create_storage_backend(&config.storage)
.await
.map_err(|e| AuthError::Storage(e.to_string()))?;
let audit_config = AuditConfig::default();
let audit_logger =
Arc::new(AuditLogger::new(audit_config).await.map_err(|e| {
AuthError::Config(format!("Failed to initialize audit logger: {}", e))
})?);
let jwt_config = JwtConfig::default();
let jwt_manager =
Arc::new(JwtManager::new(jwt_config).map_err(|e| {
AuthError::Config(format!("Failed to initialize JWT manager: {}", e))
})?);
let manager = Self {
storage,
validation_config,
api_keys_cache: Arc::new(RwLock::new(HashMap::new())),
rate_limit_state: Arc::new(RwLock::new(HashMap::new())),
role_rate_limit_state: Arc::new(RwLock::new(HashMap::new())),
audit_logger,
jwt_manager,
config,
};
manager.refresh_cache().await?;
let startup_event = AuditEvent::new(
AuditEventType::SystemStartup,
AuditSeverity::Info,
"auth_manager".to_string(),
"Authentication manager initialized with custom validation config".to_string(),
);
let _ = manager.audit_logger.log(startup_event).await;
info!("Authentication manager initialized with custom validation config");
Ok(manager)
}
pub async fn create_api_key(
&self,
name: String,
role: Role,
expires_at: Option<DateTime<Utc>>,
ip_whitelist: Option<Vec<String>>,
) -> Result<ApiKey, AuthError> {
let key = ApiKey::new(name, role, expires_at, ip_whitelist.unwrap_or_default());
self.storage
.save_key(&key)
.await
.map_err(|e| AuthError::Storage(e.to_string()))?;
{
let mut cache = self.api_keys_cache.write().await;
cache.insert(key.id.clone(), key.clone());
}
let audit_event = events::key_created(&key.id, "system", &key.role.to_string());
let _ = self.audit_logger.log(audit_event).await;
info!("Created new API key: {} ({})", key.id, key.name);
Ok(key)
}
pub async fn validate_api_key(
&self,
key_secret: &str,
client_ip: Option<&str>,
) -> Result<Option<AuthContext>, AuthError> {
let client_ip = client_ip.unwrap_or("unknown");
if let Some(blocked_until) = self.check_rate_limit(client_ip).await {
let audit_event = AuditEvent::new(
AuditEventType::AuthRateLimited,
AuditSeverity::Warning,
"rate_limiter".to_string(),
format!(
"IP {} blocked due to rate limiting until {}",
client_ip,
blocked_until.format("%Y-%m-%d %H:%M:%S UTC")
),
)
.with_client_ip(client_ip.to_string());
let _ = self.audit_logger.log(audit_event).await;
return Err(AuthError::Failed(format!(
"IP {} is rate limited until {}",
client_ip,
blocked_until.format("%Y-%m-%d %H:%M:%S UTC")
)));
}
let key = {
let cache = self.api_keys_cache.read().await;
cache
.values()
.find(|key| {
key.verify_key(key_secret).unwrap_or_default()
})
.cloned()
};
let key = match key {
Some(key) => key,
None => {
self.record_failed_attempt(client_ip).await;
let audit_event = events::auth_failure(client_ip, "Invalid API key");
let _ = self.audit_logger.log(audit_event).await;
return Err(AuthError::Failed("Invalid API key".to_string()));
}
};
if let Err(reason) = self.validate_key_security(&key, client_ip) {
self.record_failed_attempt(client_ip).await;
let audit_event = events::auth_failure(client_ip, &reason);
let _ = self.audit_logger.log(audit_event).await;
return Err(AuthError::Failed(reason));
}
if let Ok(is_rate_limited) = self.check_role_rate_limit(&key.role, client_ip).await {
if is_rate_limited {
self.record_failed_attempt(client_ip).await;
let audit_event = events::auth_failure(
client_ip,
&format!(
"Role-based rate limit exceeded for role {}",
self.get_role_key(&key.role)
),
);
let _ = self.audit_logger.log(audit_event).await;
return Err(AuthError::Failed(format!(
"Rate limit exceeded for role {}",
self.get_role_key(&key.role)
)));
}
}
let mut updated_key = key.clone();
self.clear_failed_attempts(client_ip).await;
updated_key.mark_used();
if let Err(e) = self.storage.save_key(&updated_key).await {
warn!("Failed to update key usage statistics: {}", e);
} else {
let mut cache = self.api_keys_cache.write().await;
cache.insert(updated_key.id.clone(), updated_key.clone());
}
let auth_event = events::auth_success(&key.id, client_ip);
let _ = self.audit_logger.log(auth_event).await;
let key_usage_event = events::key_used(&key.id, client_ip);
let _ = self.audit_logger.log(key_usage_event).await;
Ok(Some(AuthContext {
user_id: Some(key.id.clone()),
roles: vec![key.role.clone()],
api_key_id: Some(key.id.clone()),
permissions: self.get_permissions_for_role(&key.role),
}))
}
pub async fn validate_api_key_legacy(
&self,
key_secret: &str,
) -> Result<Option<AuthContext>, AuthError> {
self.validate_api_key(key_secret, None).await
}
pub async fn list_keys(&self) -> Vec<ApiKey> {
let cache = self.api_keys_cache.read().await;
cache.values().cloned().collect()
}
pub async fn get_key(&self, key_id: &str) -> Option<ApiKey> {
let cache = self.api_keys_cache.read().await;
cache.get(key_id).cloned()
}
pub async fn update_key(&self, key: ApiKey) -> Result<(), AuthError> {
self.storage
.save_key(&key)
.await
.map_err(|e| AuthError::Storage(e.to_string()))?;
{
let mut cache = self.api_keys_cache.write().await;
cache.insert(key.id.clone(), key.clone());
}
debug!("Updated API key: {}", key.id);
Ok(())
}
pub async fn revoke_key(&self, key_id: &str) -> Result<bool, AuthError> {
self.storage
.delete_key(key_id)
.await
.map_err(|e| AuthError::Storage(e.to_string()))?;
let removed = {
let mut cache = self.api_keys_cache.write().await;
cache.remove(key_id).is_some()
};
if removed {
info!("Revoked API key: {}", key_id);
} else {
warn!("Attempted to revoke non-existent key: {}", key_id);
}
Ok(removed)
}
async fn check_rate_limit(&self, client_ip: &str) -> Option<DateTime<Utc>> {
let rate_limits = self.rate_limit_state.read().await;
if let Some(state) = rate_limits.get(client_ip) {
if let Some(blocked_until) = state.blocked_until {
if Utc::now() < blocked_until {
return Some(blocked_until);
}
}
}
None
}
async fn record_failed_attempt(&self, client_ip: &str) {
let mut rate_limits = self.rate_limit_state.write().await;
let now = Utc::now();
let state = rate_limits
.entry(client_ip.to_string())
.or_insert_with(|| RateLimitState {
failed_attempts: 0,
window_start: now,
blocked_until: None,
successful_requests: 0,
success_window_start: now,
});
let window_duration =
chrono::Duration::minutes(self.validation_config.failed_attempt_window_minutes as i64);
if now - state.window_start > window_duration {
state.failed_attempts = 1;
state.window_start = now;
state.blocked_until = None;
} else {
state.failed_attempts += 1;
if state.failed_attempts >= self.validation_config.max_failed_attempts {
let block_duration =
chrono::Duration::minutes(self.validation_config.block_duration_minutes as i64);
state.blocked_until = Some(now + block_duration);
warn!(
"IP {} blocked for {} minutes after {} failed attempts",
client_ip, self.validation_config.block_duration_minutes, state.failed_attempts
);
}
}
debug!(
"Failed attempt #{} from IP {} (window started: {})",
state.failed_attempts, client_ip, state.window_start
);
}
async fn clear_failed_attempts(&self, client_ip: &str) {
let mut rate_limits = self.rate_limit_state.write().await;
if rate_limits.remove(client_ip).is_some() {
debug!("Cleared failed attempts for IP: {}", client_ip);
}
}
fn validate_key_security(&self, key: &ApiKey, client_ip: &str) -> Result<(), String> {
if !key.active {
return Err("API key is disabled".to_string());
}
if let Some(expires_at) = key.expires_at {
if Utc::now() > expires_at {
return Err("API key has expired".to_string());
}
}
if self.validation_config.strict_ip_validation && !key.ip_whitelist.is_empty() {
let is_ip_allowed = key.ip_whitelist.iter().any(|allowed_ip| {
allowed_ip == client_ip || allowed_ip == "*"
});
if !is_ip_allowed {
return Err(format!("IP address {client_ip} not allowed for this key"));
}
}
Ok(())
}
fn get_permissions_for_role(&self, role: &Role) -> Vec<String> {
match role {
Role::Admin => vec![
"admin.*".to_string(),
"device.*".to_string(),
"system.*".to_string(),
"mcp.*".to_string(),
],
Role::Operator => vec![
"device.*".to_string(),
"system.status".to_string(),
"mcp.tools.*".to_string(),
"mcp.resources.read".to_string(),
],
Role::Monitor => vec![
"device.read".to_string(),
"system.status".to_string(),
"mcp.resources.read".to_string(),
],
Role::Device { allowed_devices } => allowed_devices
.iter()
.map(|device| format!("device.{device}"))
.collect(),
Role::Custom { permissions } => permissions.clone(),
}
}
pub async fn get_rate_limit_stats(&self) -> RateLimitStats {
let rate_limits = self.rate_limit_state.read().await;
let role_states = self.role_rate_limit_state.read().await;
let now = Utc::now();
let mut stats = RateLimitStats {
total_tracked_ips: rate_limits.len(),
currently_blocked_ips: 0,
total_failed_attempts: 0,
role_stats: std::collections::HashMap::new(),
};
for state in rate_limits.values() {
stats.total_failed_attempts += state.failed_attempts as u64;
if let Some(blocked_until) = state.blocked_until {
if now < blocked_until {
stats.currently_blocked_ips += 1;
}
}
}
for (role_key, ip_states) in role_states.iter() {
let mut role_statistics = RoleRateLimitStats {
current_requests: 0,
blocked_requests: 0,
total_requests: 0,
in_cooldown: false,
cooldown_ends_at: None,
last_window_start: None,
};
for state in ip_states.values() {
role_statistics.current_requests += state.current_requests;
role_statistics.blocked_requests += state.blocked_requests;
role_statistics.total_requests += state.total_requests;
if let Some(cooldown_end) = state.cooldown_ends_at {
if now < cooldown_end {
role_statistics.in_cooldown = true;
if role_statistics.cooldown_ends_at.is_none()
|| cooldown_end > role_statistics.cooldown_ends_at.unwrap()
{
role_statistics.cooldown_ends_at = Some(cooldown_end);
}
}
}
}
stats.role_stats.insert(role_key.clone(), role_statistics);
}
stats
}
pub async fn cleanup_rate_limits(&self) {
let mut rate_limits = self.rate_limit_state.write().await;
let now = Utc::now();
let cleanup_threshold = chrono::Duration::hours(24);
let initial_count = rate_limits.len();
rate_limits.retain(|_ip, state| {
if let Some(blocked_until) = state.blocked_until {
if now < blocked_until {
return true;
}
}
now - state.window_start < cleanup_threshold
});
let removed_count = initial_count - rate_limits.len();
if removed_count > 0 {
debug!("Cleaned up {} old rate limit entries", removed_count);
}
}
pub async fn check_role_rate_limit(
&self,
role: &Role,
client_ip: &str,
) -> Result<bool, AuthError> {
if !self.validation_config.enable_role_based_rate_limiting {
return Ok(false); }
let role_key = self.get_role_key(role);
let role_config = match self.validation_config.role_rate_limits.get(&role_key) {
Some(config) => config.clone(),
None => {
warn!(
"No rate limit config found for role '{}', using default",
role_key
);
return Ok(false);
}
};
let mut role_states = self.role_rate_limit_state.write().await;
let role_state_map = role_states
.entry(role_key.clone())
.or_insert_with(HashMap::new);
let now = Utc::now();
let state = role_state_map
.entry(client_ip.to_string())
.or_insert_with(|| RoleRateLimitStats {
current_requests: 0,
blocked_requests: 0,
total_requests: 0,
in_cooldown: false,
cooldown_ends_at: None,
last_window_start: None,
});
if let Some(cooldown_end) = state.cooldown_ends_at {
if now < cooldown_end {
state.blocked_requests += 1;
let audit_event = crate::audit::AuditEvent::new(
crate::audit::AuditEventType::AuthRateLimited,
crate::audit::AuditSeverity::Warning,
"role_rate_limiter".to_string(),
format!(
"Role {} from IP {} blocked (cooldown until {})",
role_key,
client_ip,
cooldown_end.format("%Y-%m-%d %H:%M:%S UTC")
),
)
.with_client_ip(client_ip.to_string());
let _ = self.audit_logger.log(audit_event).await;
return Ok(true); }
state.in_cooldown = false;
state.cooldown_ends_at = None;
state.current_requests = 0;
}
let window_duration = chrono::Duration::minutes(role_config.window_duration_minutes as i64);
if let Some(last_window_start) = state.last_window_start {
if now.signed_duration_since(last_window_start) >= window_duration {
state.current_requests = 0;
state.last_window_start = Some(now);
}
} else {
state.last_window_start = Some(now);
}
state.current_requests += 1;
state.total_requests += 1;
let effective_limit = role_config.max_requests_per_window + role_config.burst_allowance;
if state.current_requests > effective_limit {
state.in_cooldown = true;
state.cooldown_ends_at =
Some(now + chrono::Duration::minutes(role_config.cooldown_duration_minutes as i64));
state.blocked_requests += 1;
let audit_event = crate::audit::AuditEvent::new(
crate::audit::AuditEventType::AuthRateLimited,
crate::audit::AuditSeverity::Warning,
"role_rate_limiter".to_string(),
format!(
"Role {} from IP {} rate limited for {} minutes after {} requests",
role_key,
client_ip,
role_config.cooldown_duration_minutes,
state.current_requests
),
)
.with_client_ip(client_ip.to_string());
let _ = self.audit_logger.log(audit_event).await;
warn!(
"Role {} from IP {} rate limited for {} minutes after {} requests",
role_key, client_ip, role_config.cooldown_duration_minutes, state.current_requests
);
return Ok(true); }
if state.current_requests % 100 == 0 {
let audit_event = crate::audit::AuditEvent::new(
crate::audit::AuditEventType::AuthSuccess,
crate::audit::AuditSeverity::Info,
"role_rate_limiter".to_string(),
format!(
"Role {} from IP {} processed {} requests in window",
role_key, client_ip, state.current_requests
),
)
.with_client_ip(client_ip.to_string());
let _ = self.audit_logger.log(audit_event).await;
}
Ok(false) }
fn get_role_key(&self, role: &Role) -> String {
match role {
Role::Admin => "admin".to_string(),
Role::Operator => "operator".to_string(),
Role::Monitor => "monitor".to_string(),
Role::Device { .. } => "device".to_string(),
Role::Custom { .. } => "custom".to_string(),
}
}
pub async fn update_role_rate_limit(
&self,
role_key: String,
config: RoleRateLimitConfig,
) -> Result<(), AuthError> {
warn!(
"Role rate limit update requested for '{}' but configuration is immutable",
role_key
);
let audit_event = crate::audit::AuditEvent::new(
crate::audit::AuditEventType::SystemStartup,
crate::audit::AuditSeverity::Info,
"role_rate_limiter".to_string(),
format!(
"Rate limit configuration update requested for role '{}' (max_requests: {}, window: {} min)",
role_key, config.max_requests_per_window, config.window_duration_minutes
),
);
let _ = self.audit_logger.log(audit_event).await;
Ok(())
}
pub async fn cleanup_role_rate_limits(&self) {
let mut role_states = self.role_rate_limit_state.write().await;
let now = Utc::now();
let cleanup_threshold = chrono::Duration::hours(24);
let mut total_removed = 0;
for (_role_key, ip_states) in role_states.iter_mut() {
let initial_count = ip_states.len();
ip_states.retain(|_ip, state| {
if let Some(cooldown_end) = state.cooldown_ends_at {
if now < cooldown_end {
return true;
}
}
if let Some(window_start) = state.last_window_start {
if now.signed_duration_since(window_start) < cleanup_threshold {
return true;
}
}
false
});
let removed = initial_count - ip_states.len();
total_removed += removed;
}
role_states.retain(|_role, ip_states| !ip_states.is_empty());
if total_removed > 0 {
debug!("Cleaned up {} old role rate limit entries", total_removed);
}
}
async fn refresh_cache(&self) -> Result<(), AuthError> {
let keys = self
.storage
.load_keys()
.await
.map_err(|e| AuthError::Storage(e.to_string()))?;
let mut cache = self.api_keys_cache.write().await;
*cache = keys;
debug!("Refreshed cache with {} keys", cache.len());
Ok(())
}
pub async fn disable_key(&self, key_id: &str) -> Result<bool, AuthError> {
let mut key = match self.get_key(key_id).await {
Some(key) => key,
None => return Ok(false),
};
key.active = false;
self.update_key(key).await?;
info!("Disabled API key: {}", key_id);
Ok(true)
}
pub async fn enable_key(&self, key_id: &str) -> Result<bool, AuthError> {
let mut key = match self.get_key(key_id).await {
Some(key) => key,
None => return Ok(false),
};
key.active = true;
self.update_key(key).await?;
info!("Enabled API key: {}", key_id);
Ok(true)
}
pub async fn update_key_expiration(
&self,
key_id: &str,
expires_at: Option<DateTime<Utc>>,
) -> Result<bool, AuthError> {
let mut key = match self.get_key(key_id).await {
Some(key) => key,
None => return Ok(false),
};
key.expires_at = expires_at;
self.update_key(key).await?;
info!("Updated expiration for API key: {}", key_id);
Ok(true)
}
pub async fn update_key_ip_whitelist(
&self,
key_id: &str,
ip_whitelist: Vec<String>,
) -> Result<bool, AuthError> {
let mut key = match self.get_key(key_id).await {
Some(key) => key,
None => return Ok(false),
};
key.ip_whitelist = ip_whitelist;
self.update_key(key).await?;
info!("Updated IP whitelist for API key: {}", key_id);
Ok(true)
}
pub async fn list_keys_by_role(&self, role: &Role) -> Vec<ApiKey> {
let cache = self.api_keys_cache.read().await;
cache
.values()
.filter(|key| &key.role == role)
.cloned()
.collect()
}
pub async fn list_active_keys(&self) -> Vec<ApiKey> {
let cache = self.api_keys_cache.read().await;
cache
.values()
.filter(|key| key.active && !key.is_expired())
.cloned()
.collect()
}
pub async fn list_expired_keys(&self) -> Vec<ApiKey> {
let cache = self.api_keys_cache.read().await;
cache
.values()
.filter(|key| key.is_expired())
.cloned()
.collect()
}
pub async fn bulk_revoke_keys(&self, key_ids: &[String]) -> Result<Vec<String>, AuthError> {
let mut revoked = Vec::new();
for key_id in key_ids {
match self.revoke_key(key_id).await {
Ok(true) => revoked.push(key_id.clone()),
Ok(false) => debug!("Key {} was already revoked or not found", key_id),
Err(e) => error!("Failed to revoke key {}: {}", key_id, e),
}
}
info!("Bulk revoked {} keys", revoked.len());
Ok(revoked)
}
pub async fn cleanup_expired_keys(&self) -> Result<u32, AuthError> {
let expired_keys = self.list_expired_keys().await;
let key_ids: Vec<String> = expired_keys.iter().map(|k| k.id.clone()).collect();
let revoked = self.bulk_revoke_keys(&key_ids).await?;
info!("Cleaned up {} expired keys", revoked.len());
Ok(revoked.len() as u32)
}
pub async fn get_key_usage_stats(&self) -> Result<KeyUsageStats, AuthError> {
let cache = self.api_keys_cache.read().await;
let mut stats = KeyUsageStats::default();
for key in cache.values() {
stats.total_keys += 1;
if key.active {
stats.active_keys += 1;
} else {
stats.disabled_keys += 1;
}
if key.is_expired() {
stats.expired_keys += 1;
}
stats.total_usage_count += key.usage_count;
match &key.role {
Role::Admin => stats.admin_keys += 1,
Role::Operator => stats.operator_keys += 1,
Role::Monitor => stats.monitor_keys += 1,
Role::Device { .. } => stats.device_keys += 1,
Role::Custom { .. } => stats.custom_keys += 1,
}
}
Ok(stats)
}
pub async fn bulk_create_keys(
&self,
requests: Vec<KeyCreationRequest>,
) -> Result<Vec<Result<ApiKey, AuthError>>, AuthError> {
let mut results = Vec::new();
for request in requests {
let result = self
.create_api_key(
request.name,
request.role,
request.expires_at,
request.ip_whitelist,
)
.await;
results.push(result);
}
Ok(results)
}
pub fn check_api_completeness(&self) -> ApiCompletenessCheck {
ApiCompletenessCheck {
has_create_key: true,
has_validate_key: true,
has_list_keys: true,
has_revoke_key: true,
has_update_key: true,
has_bulk_operations: true,
has_role_based_access: true,
has_rate_limiting: true,
has_ip_whitelisting: true,
has_expiration_support: true,
has_usage_tracking: true,
framework_version: env!("CARGO_PKG_VERSION").to_string(),
production_ready: true,
}
}
pub async fn start_background_tasks(&self) -> Result<(), AuthError> {
Ok(())
}
pub async fn stop_background_tasks(&self) -> Result<(), AuthError> {
Ok(())
}
pub async fn health_check(&self) -> Result<(), AuthError> {
Ok(())
}
pub async fn process_request(
&self,
request: Request,
_context: &RequestContext,
) -> Result<Request, AuthError> {
if !self.config.enabled {
return Ok(request);
}
Ok(request)
}
pub async fn process_response(
&self,
response: Response,
_context: &RequestContext,
) -> Result<Response, AuthError> {
Ok(response)
}
pub async fn generate_token_for_key(
&self,
key_id: &str,
client_ip: Option<String>,
session_id: Option<String>,
scope: Vec<String>,
) -> Result<TokenPair, AuthError> {
let key = self
.get_key(key_id)
.await
.ok_or_else(|| AuthError::Failed("API key not found".to_string()))?;
if !key.is_valid() {
return Err(AuthError::Failed(
"API key is invalid or expired".to_string(),
));
}
let token_pair = self
.jwt_manager
.generate_token_pair(
key.id.clone(),
vec![key.role.clone()],
Some(key.id.clone()),
client_ip.clone(),
session_id.clone(),
scope,
)
.await
.map_err(|e| AuthError::Failed(format!("Token generation failed: {e}")))?;
let audit_event = AuditEvent::new(
AuditEventType::KeyUsed,
AuditSeverity::Info,
"jwt".to_string(),
format!("JWT token pair generated for key {}", key.id),
)
.with_resource(key.id.clone())
.with_client_ip(client_ip.unwrap_or_else(|| "unknown".to_string()));
let _ = self.audit_logger.log(audit_event).await;
Ok(token_pair)
}
pub async fn validate_jwt_token(&self, token: &str) -> Result<AuthContext, AuthError> {
let auth_context = self
.jwt_manager
.token_to_auth_context(token)
.await
.map_err(|e| match e {
crate::jwt::JwtError::Expired => AuthError::Failed("Token expired".to_string()),
crate::jwt::JwtError::InvalidFormat => {
AuthError::Failed("Invalid token format".to_string())
}
_ => AuthError::Failed(format!("Token validation failed: {}", e)),
})?;
let audit_event = AuditEvent::new(
AuditEventType::AuthSuccess,
AuditSeverity::Info,
"jwt".to_string(),
format!("JWT token validated for user {:?}", auth_context.user_id),
);
if let Some(ref user_id) = auth_context.user_id {
let audit_event = audit_event.with_actor(user_id.clone());
let _ = self.audit_logger.log(audit_event).await;
}
Ok(auth_context)
}
pub async fn refresh_jwt_token(
&self,
refresh_token: &str,
client_ip: Option<String>,
scope: Vec<String>,
) -> Result<String, AuthError> {
let token_info = self
.jwt_manager
.validate_token(refresh_token)
.await
.map_err(|e| AuthError::Failed(format!("Invalid refresh token: {}", e)))?;
let roles = if let Some(key_id) = &token_info.claims.key_id {
let key = self
.get_key(key_id)
.await
.ok_or_else(|| AuthError::Failed("Associated API key not found".to_string()))?;
if !key.is_valid() {
return Err(AuthError::Failed(
"Associated API key is invalid or expired".to_string(),
));
}
vec![key.role.clone()]
} else {
token_info.claims.roles
};
let access_token = self
.jwt_manager
.refresh_access_token(refresh_token, roles, client_ip.clone(), scope)
.await
.map_err(|e| AuthError::Failed(format!("Token refresh failed: {}", e)))?;
let audit_event = AuditEvent::new(
AuditEventType::KeyUsed,
AuditSeverity::Info,
"jwt".to_string(),
format!(
"JWT access token refreshed for subject {}",
token_info.claims.sub
),
)
.with_actor(token_info.claims.sub)
.with_client_ip(client_ip.unwrap_or_else(|| "unknown".to_string()));
let _ = self.audit_logger.log(audit_event).await;
Ok(access_token)
}
pub async fn revoke_jwt_token(&self, token: &str) -> Result<(), AuthError> {
self.jwt_manager
.revoke_token(token)
.await
.map_err(|e| AuthError::Failed(format!("Token revocation failed: {}", e)))?;
let audit_event = AuditEvent::new(
AuditEventType::SecurityViolation,
AuditSeverity::Warning,
"jwt".to_string(),
"JWT token revoked".to_string(),
);
let _ = self.audit_logger.log(audit_event).await;
Ok(())
}
pub async fn cleanup_jwt_blacklist(&self) -> Result<usize, AuthError> {
let cleaned = self.jwt_manager.cleanup_blacklist().await;
if cleaned > 0 {
let audit_event = AuditEvent::new(
AuditEventType::SystemStartup,
AuditSeverity::Info,
"jwt".to_string(),
format!("Cleaned up {} expired tokens from blacklist", cleaned),
);
let _ = self.audit_logger.log(audit_event).await;
}
Ok(cleaned)
}
pub fn decode_jwt_token_info(&self, token: &str) -> Result<crate::jwt::TokenClaims, AuthError> {
self.jwt_manager
.decode_token_info(token)
.map_err(|e| AuthError::Failed(format!("Token decoding failed: {}", e)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{AuthConfig, StorageConfig};
use crate::models::Role;
use tokio;
fn create_test_config() -> AuthConfig {
AuthConfig {
storage: StorageConfig::Memory,
enabled: true,
cache_size: 100,
session_timeout_secs: 3600,
max_failed_attempts: 3,
rate_limit_window_secs: 300,
}
}
#[allow(dead_code)]
fn create_test_validation_config() -> ValidationConfig {
ValidationConfig {
max_failed_attempts: 3,
failed_attempt_window_minutes: 15,
block_duration_minutes: 30,
session_timeout_minutes: 60,
strict_ip_validation: false,
enable_role_based_rate_limiting: false,
role_rate_limits: HashMap::new(),
}
}
#[tokio::test]
async fn test_auth_manager_creation() {
let config = create_test_config();
let result = AuthenticationManager::new(config).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_create_api_key() {
let config = create_test_config();
let manager = AuthenticationManager::new(config).await.unwrap();
let result = manager
.create_api_key("Test Key".to_string(), Role::Monitor, None, None)
.await;
assert!(result.is_ok());
let key = result.unwrap();
assert_eq!(key.name, "Test Key");
assert!(key.id.starts_with("lmcp_"));
assert_eq!(key.role, Role::Monitor);
}
}