impl Default for CorrelationIdGenerator {
fn default() -> Self {
Self::new()
}
}
use crate::errors::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::SystemTime;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AuditEventType {
LoginSuccess,
LoginFailure,
Logout,
TokenRefresh,
TokenExpired,
TokenRevoked,
MfaSetup,
MfaChallengeCreated,
MfaVerificationSuccess,
MfaVerificationFailure,
MfaMethodEnabled,
MfaMethodDisabled,
PermissionGranted,
PermissionDenied,
RoleAssigned,
RoleRevoked,
RoleCreated,
RoleUpdated,
RoleDeleted,
UserCreated,
UserUpdated,
UserDeleted,
UserActivated,
UserDeactivated,
UserPasswordChanged,
UserPasswordReset,
AccountLocked,
AccountUnlocked,
SuspiciousActivity,
BruteForceDetected,
RateLimitExceeded,
SecurityPolicyViolation,
SecurityViolation,
AdminAction,
ConfigurationChanged,
SystemStartup,
SystemShutdown,
BackupCreated,
DataExported,
DataImported,
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)]
pub enum RiskLevel {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum EventOutcome {
Success,
Failure,
Partial,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEvent {
pub id: String,
pub event_type: AuditEventType,
pub timestamp: SystemTime,
pub user_id: Option<String>,
pub session_id: Option<String>,
pub outcome: EventOutcome,
pub risk_level: RiskLevel,
pub description: String,
pub details: HashMap<String, String>,
pub request_metadata: RequestMetadata,
pub resource: Option<ResourceInfo>,
pub actor: ActorInfo,
pub correlation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestMetadata {
pub ip_address: Option<String>,
pub user_agent: Option<String>,
pub request_id: Option<String>,
pub endpoint: Option<String>,
pub http_method: Option<String>,
pub geolocation: Option<GeolocationInfo>,
pub device_info: Option<DeviceInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeolocationInfo {
pub country: Option<String>,
pub region: Option<String>,
pub city: Option<String>,
pub latitude: Option<f64>,
pub longitude: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceInfo {
pub device_type: Option<String>,
pub operating_system: Option<String>,
pub browser: Option<String>,
pub is_mobile: bool,
pub screen_resolution: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceInfo {
pub resource_type: String,
pub resource_id: String,
pub resource_name: Option<String>,
pub attributes: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActorInfo {
pub actor_type: String,
pub actor_id: String,
pub actor_name: Option<String>,
pub roles: Vec<String>,
}
#[async_trait]
pub trait AuditStorage: Send + Sync {
async fn store_event(&self, event: &AuditEvent) -> Result<()>;
async fn query_events(&self, query: &AuditQuery) -> Result<Vec<AuditEvent>>;
async fn get_event(&self, event_id: &str) -> Result<Option<AuditEvent>>;
async fn count_events(&self, query: &AuditQuery) -> Result<u64>;
async fn delete_old_events(&self, before: SystemTime) -> Result<u64>;
async fn get_statistics(&self, query: &StatsQuery) -> Result<AuditStatistics>;
}
#[derive(Debug, Clone)]
pub struct AuditQuery {
pub event_types: Option<Vec<AuditEventType>>,
pub user_id: Option<String>,
pub risk_level: Option<RiskLevel>,
pub outcome: Option<EventOutcome>,
pub time_range: Option<TimeRange>,
pub ip_address: Option<String>,
pub resource_type: Option<String>,
pub actor_id: Option<String>,
pub correlation_id: Option<String>,
pub limit: Option<u64>,
pub offset: Option<u64>,
pub sort_order: SortOrder,
}
#[derive(Debug, Clone)]
pub struct TimeRange {
pub start: SystemTime,
pub end: SystemTime,
}
#[derive(Debug, Clone)]
pub enum SortOrder {
TimestampAsc,
TimestampDesc,
RiskLevelDesc,
}
#[derive(Debug, Clone)]
pub struct StatsQuery {
pub time_range: TimeRange,
pub group_by: Vec<StatsGroupBy>,
}
#[derive(Debug, Clone)]
pub enum StatsGroupBy {
EventType,
RiskLevel,
Outcome,
Hour,
Day,
Week,
UserId,
IpAddress,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditStatistics {
pub total_events: u64,
pub event_type_counts: HashMap<String, u64>,
pub risk_level_counts: HashMap<String, u64>,
pub outcome_counts: HashMap<String, u64>,
pub time_series: Vec<TimeSeriesPoint>,
pub top_users: Vec<UserEventCount>,
pub top_ips: Vec<IpEventCount>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeSeriesPoint {
pub timestamp: SystemTime,
pub count: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserEventCount {
pub user_id: String,
pub event_count: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpEventCount {
pub ip_address: String,
pub event_count: u64,
}
pub struct AuditLogger<S: AuditStorage> {
storage: S,
correlation_generator: CorrelationIdGenerator,
}
impl<S: AuditStorage> AuditLogger<S> {
pub fn new(storage: S) -> Self {
Self {
storage,
correlation_generator: CorrelationIdGenerator::new(),
}
}
pub async fn log_event(&self, mut event: AuditEvent) -> Result<()> {
if event.id.is_empty() {
event.id = uuid::Uuid::new_v4().to_string();
}
if event.timestamp == SystemTime::UNIX_EPOCH {
event.timestamp = SystemTime::now();
}
self.storage.store_event(&event).await?;
self.check_security_alerts(&event).await?;
Ok(())
}
pub async fn log_login_success(
&self,
user_id: &str,
session_id: &str,
metadata: RequestMetadata,
) -> Result<()> {
let event = AuditEvent {
id: String::new(),
event_type: AuditEventType::LoginSuccess,
timestamp: SystemTime::UNIX_EPOCH,
user_id: Some(user_id.to_string()),
session_id: Some(session_id.to_string()),
outcome: EventOutcome::Success,
risk_level: RiskLevel::Low,
description: "User successfully authenticated".to_string(),
details: HashMap::new(),
request_metadata: metadata,
resource: None,
actor: ActorInfo {
actor_type: "user".to_string(),
actor_id: user_id.to_string(),
actor_name: None,
roles: vec![],
},
correlation_id: Some(self.correlation_generator.generate()),
};
self.log_event(event).await
}
pub async fn log_login_failure(
&self,
attempted_user: &str,
reason: &str,
metadata: RequestMetadata,
) -> Result<()> {
let mut details = HashMap::new();
details.insert("failure_reason".to_string(), reason.to_string());
details.insert("attempted_user".to_string(), attempted_user.to_string());
let event = AuditEvent {
id: String::new(),
event_type: AuditEventType::LoginFailure,
timestamp: SystemTime::UNIX_EPOCH,
user_id: None,
session_id: None,
outcome: EventOutcome::Failure,
risk_level: RiskLevel::Medium,
description: format!("Authentication failed for user: {}", attempted_user),
details,
request_metadata: metadata,
resource: None,
actor: ActorInfo {
actor_type: "user".to_string(),
actor_id: attempted_user.to_string(),
actor_name: None,
roles: vec![],
},
correlation_id: Some(self.correlation_generator.generate()),
};
self.log_event(event).await
}
pub async fn log_permission_denied(
&self,
user_id: &str,
resource: ResourceInfo,
permission: &str,
metadata: RequestMetadata,
) -> Result<()> {
let mut details = HashMap::new();
details.insert("requested_permission".to_string(), permission.to_string());
let event = AuditEvent {
id: String::new(),
event_type: AuditEventType::PermissionDenied,
timestamp: SystemTime::UNIX_EPOCH,
user_id: Some(user_id.to_string()),
session_id: None,
outcome: EventOutcome::Failure,
risk_level: RiskLevel::Medium,
description: format!(
"Permission denied: {} on {}",
permission, resource.resource_type
),
details,
request_metadata: metadata,
resource: Some(resource),
actor: ActorInfo {
actor_type: "user".to_string(),
actor_id: user_id.to_string(),
actor_name: None,
roles: vec![],
},
correlation_id: Some(self.correlation_generator.generate()),
};
self.log_event(event).await
}
pub async fn log_suspicious_activity(
&self,
user_id: Option<&str>,
activity_type: &str,
description: &str,
metadata: RequestMetadata,
) -> Result<()> {
let mut details = HashMap::new();
details.insert("activity_type".to_string(), activity_type.to_string());
let event = AuditEvent {
id: String::new(),
event_type: AuditEventType::SuspiciousActivity,
timestamp: SystemTime::UNIX_EPOCH,
user_id: user_id.map(|s| s.to_string()),
session_id: None,
outcome: EventOutcome::Unknown,
risk_level: RiskLevel::High,
description: description.to_string(),
details,
request_metadata: metadata,
resource: None,
actor: ActorInfo {
actor_type: user_id.map(|_| "user").unwrap_or("system").to_string(),
actor_id: user_id.unwrap_or("system").to_string(),
actor_name: None,
roles: vec![],
},
correlation_id: Some(self.correlation_generator.generate()),
};
self.log_event(event).await
}
async fn check_security_alerts(&self, event: &AuditEvent) -> Result<()> {
match event.event_type {
AuditEventType::LoginFailure => {
self.check_brute_force_pattern(event).await?;
}
AuditEventType::SuspiciousActivity => {
self.trigger_security_alert(event).await?;
}
_ => {}
}
Ok(())
}
async fn check_brute_force_pattern(&self, event: &AuditEvent) -> Result<()> {
let query = AuditQuery {
event_types: Some(vec![AuditEventType::LoginFailure]),
ip_address: event.request_metadata.ip_address.clone(),
time_range: Some(TimeRange {
start: SystemTime::now() - std::time::Duration::from_secs(300), end: SystemTime::now(),
}),
limit: Some(10),
offset: None,
user_id: None,
risk_level: None,
outcome: None,
resource_type: None,
actor_id: None,
correlation_id: None,
sort_order: SortOrder::TimestampDesc,
};
let recent_failures = self.storage.query_events(&query).await?;
if recent_failures.len() >= 5 {
let mut details = HashMap::new();
details.insert(
"failure_count".to_string(),
recent_failures.len().to_string(),
);
details.insert("time_window".to_string(), "300".to_string());
let brute_force_event = AuditEvent {
id: String::new(),
event_type: AuditEventType::BruteForceDetected,
timestamp: SystemTime::now(),
user_id: None,
session_id: None,
outcome: EventOutcome::Success,
risk_level: RiskLevel::Critical,
description: "Brute force attack detected".to_string(),
details,
request_metadata: event.request_metadata.clone(),
resource: None,
actor: ActorInfo {
actor_type: "system".to_string(),
actor_id: "security_monitor".to_string(),
actor_name: Some("Security Monitor".to_string()),
roles: vec!["system".to_string()],
},
correlation_id: Some(self.correlation_generator.generate()),
};
self.storage.store_event(&brute_force_event).await?;
}
Ok(())
}
async fn trigger_security_alert(&self, _event: &AuditEvent) -> Result<()> {
Ok(())
}
pub async fn query_events(&self, query: &AuditQuery) -> Result<Vec<AuditEvent>> {
self.storage.query_events(query).await
}
pub async fn get_statistics(&self, query: &StatsQuery) -> Result<AuditStatistics> {
self.storage.get_statistics(query).await
}
pub async fn get_failed_login_count_24h(&self) -> Result<u64> {
let query = AuditQuery {
event_types: Some(vec![AuditEventType::LoginFailure]),
time_range: Some(TimeRange {
start: SystemTime::now() - std::time::Duration::from_secs(24 * 60 * 60),
end: SystemTime::now(),
}),
limit: None,
offset: None,
user_id: None,
risk_level: None,
outcome: None,
resource_type: None,
actor_id: None,
correlation_id: None,
ip_address: None,
sort_order: SortOrder::TimestampDesc,
};
self.storage.count_events(&query).await
}
pub async fn get_successful_login_count_24h(&self) -> Result<u64> {
let query = AuditQuery {
event_types: Some(vec![AuditEventType::LoginSuccess]),
time_range: Some(TimeRange {
start: SystemTime::now() - std::time::Duration::from_secs(24 * 60 * 60),
end: SystemTime::now(),
}),
limit: None,
offset: None,
user_id: None,
risk_level: None,
outcome: None,
resource_type: None,
actor_id: None,
correlation_id: None,
ip_address: None,
sort_order: SortOrder::TimestampDesc,
};
self.storage.count_events(&query).await
}
pub async fn get_token_issued_count_24h(&self) -> Result<u64> {
let query = AuditQuery {
event_types: Some(vec![
AuditEventType::TokenRefresh,
AuditEventType::LoginSuccess,
]),
time_range: Some(TimeRange {
start: SystemTime::now() - std::time::Duration::from_secs(24 * 60 * 60),
end: SystemTime::now(),
}),
limit: None,
offset: None,
user_id: None,
risk_level: None,
outcome: None,
resource_type: None,
actor_id: None,
correlation_id: None,
ip_address: None,
sort_order: SortOrder::TimestampDesc,
};
self.storage.count_events(&query).await
}
pub async fn get_unique_users_24h(&self) -> Result<u64> {
let query = AuditQuery {
event_types: Some(vec![AuditEventType::LoginSuccess]),
time_range: Some(TimeRange {
start: SystemTime::now() - std::time::Duration::from_secs(24 * 60 * 60),
end: SystemTime::now(),
}),
limit: None,
offset: None,
user_id: None,
risk_level: None,
outcome: None,
resource_type: None,
actor_id: None,
correlation_id: None,
ip_address: None,
sort_order: SortOrder::TimestampDesc,
};
let events = self.storage.query_events(&query).await?;
let unique_users: std::collections::HashSet<_> =
events.iter().filter_map(|e| e.user_id.as_ref()).collect();
Ok(unique_users.len() as u64)
}
pub async fn get_password_reset_count_24h(&self) -> Result<u64> {
let query = AuditQuery {
event_types: Some(vec![AuditEventType::UserPasswordReset]),
time_range: Some(TimeRange {
start: SystemTime::now() - std::time::Duration::from_secs(24 * 60 * 60),
end: SystemTime::now(),
}),
limit: None,
offset: None,
user_id: None,
risk_level: None,
outcome: None,
resource_type: None,
actor_id: None,
correlation_id: None,
ip_address: None,
sort_order: SortOrder::TimestampDesc,
};
self.storage.count_events(&query).await
}
pub async fn get_admin_action_count_24h(&self) -> Result<u64> {
let query = AuditQuery {
event_types: Some(vec![
AuditEventType::AdminAction,
AuditEventType::UserCreated,
AuditEventType::UserUpdated,
AuditEventType::UserDeleted,
AuditEventType::RoleCreated,
AuditEventType::RoleUpdated,
AuditEventType::RoleDeleted,
]),
time_range: Some(TimeRange {
start: SystemTime::now() - std::time::Duration::from_secs(24 * 60 * 60),
end: SystemTime::now(),
}),
limit: None,
offset: None,
user_id: None,
risk_level: None,
outcome: None,
resource_type: None,
actor_id: None,
correlation_id: None,
ip_address: None,
sort_order: SortOrder::TimestampDesc,
};
self.storage.count_events(&query).await
}
pub async fn get_security_alert_count_24h(&self) -> Result<u64> {
let query = AuditQuery {
event_types: Some(vec![
AuditEventType::SuspiciousActivity,
AuditEventType::BruteForceDetected,
AuditEventType::SecurityViolation,
]),
time_range: Some(TimeRange {
start: SystemTime::now() - std::time::Duration::from_secs(24 * 60 * 60),
end: SystemTime::now(),
}),
limit: None,
offset: None,
user_id: None,
risk_level: Some(RiskLevel::High),
outcome: None,
resource_type: None,
actor_id: None,
correlation_id: None,
ip_address: None,
sort_order: SortOrder::TimestampDesc,
};
self.storage.count_events(&query).await
}
pub async fn cleanup_old_events(&self, retention_days: u32) -> Result<u64> {
let cutoff_time =
SystemTime::now() - std::time::Duration::from_secs(retention_days as u64 * 86400);
self.storage.delete_old_events(cutoff_time).await
}
}
pub struct CorrelationIdGenerator {
counter: std::sync::atomic::AtomicU64,
}
impl CorrelationIdGenerator {
pub fn new() -> Self {
Self {
counter: std::sync::atomic::AtomicU64::new(0),
}
}
pub fn generate(&self) -> String {
let count = self
.counter
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
format!(
"corr_{:016x}_{}",
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
count
)
}
}
impl RequestMetadata {
pub fn new() -> Self {
Self {
ip_address: None,
user_agent: None,
request_id: None,
endpoint: None,
http_method: None,
geolocation: None,
device_info: None,
}
}
pub fn with_ip(mut self, ip: impl Into<String>) -> Self {
self.ip_address = Some(ip.into());
self
}
pub fn with_user_agent(mut self, user_agent: impl Into<String>) -> Self {
self.user_agent = Some(user_agent.into());
self
}
pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = Some(endpoint.into());
self
}
}
impl Default for RequestMetadata {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_correlation_id_generation() {
let generator = CorrelationIdGenerator::new();
let id1 = generator.generate();
let id2 = generator.generate();
assert_ne!(id1, id2);
assert!(id1.starts_with("corr_"));
assert!(id2.starts_with("corr_"));
}
#[test]
fn test_request_metadata_builder() {
let metadata = RequestMetadata::new()
.with_ip("192.168.1.1")
.with_user_agent("Mozilla/5.0")
.with_endpoint("/api/auth/login");
assert_eq!(metadata.ip_address, Some("192.168.1.1".to_string()));
assert_eq!(metadata.user_agent, Some("Mozilla/5.0".to_string()));
assert_eq!(metadata.endpoint, Some("/api/auth/login".to_string()));
}
}