avl_auth/
audit.rs

1//! Comprehensive audit logging system
2
3use crate::error::Result;
4use crate::models::{AuditLog, AuditResult};
5use chrono::Utc;
6use std::collections::HashMap;
7use std::net::IpAddr;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10use uuid::Uuid;
11
12pub struct AuditManager {
13    logs: Arc<RwLock<Vec<AuditLog>>>,
14    retention_days: u32,
15}
16
17impl AuditManager {
18    pub fn new(retention_days: u32) -> Self {
19        Self {
20            logs: Arc::new(RwLock::new(Vec::new())),
21            retention_days,
22        }
23    }
24
25    pub async fn log(
26        &self,
27        user_id: Option<Uuid>,
28        session_id: Option<Uuid>,
29        action: String,
30        resource: String,
31        result: AuditResult,
32        ip_address: Option<IpAddr>,
33        user_agent: Option<String>,
34        metadata: HashMap<String, serde_json::Value>,
35        risk_score: u8,
36    ) {
37        let log = AuditLog {
38            id: Uuid::new_v4(),
39            user_id,
40            session_id,
41            action,
42            resource,
43            result,
44            ip_address,
45            user_agent,
46            metadata,
47            risk_score,
48            timestamp: Utc::now(),
49        };
50
51        let mut logs = self.logs.write().await;
52        logs.push(log.clone());
53
54        // Persist to database (would normally be async)
55        self.persist_log(&log).await;
56
57        // Send to telemetry
58        self.send_to_telemetry(&log);
59    }
60
61    async fn persist_log(&self, log: &AuditLog) {
62        // In production, write to AvilaDB
63        tracing::debug!("Persisting audit log: {:?}", log.id);
64    }
65
66    fn send_to_telemetry(&self, log: &AuditLog) {
67        tracing::info!(
68            user_id = ?log.user_id,
69            action = %log.action,
70            resource = %log.resource,
71            result = ?log.result,
72            risk_score = log.risk_score,
73            "Audit event"
74        );
75    }
76
77    pub async fn query(
78        &self,
79        user_id: Option<Uuid>,
80        action: Option<String>,
81        start_time: Option<chrono::DateTime<Utc>>,
82        end_time: Option<chrono::DateTime<Utc>>,
83        limit: usize,
84    ) -> Vec<AuditLog> {
85        let logs = self.logs.read().await;
86
87        logs.iter()
88            .filter(|log| {
89                if let Some(uid) = user_id {
90                    if log.user_id != Some(uid) {
91                        return false;
92                    }
93                }
94
95                if let Some(ref act) = action {
96                    if &log.action != act {
97                        return false;
98                    }
99                }
100
101                if let Some(start) = start_time {
102                    if log.timestamp < start {
103                        return false;
104                    }
105                }
106
107                if let Some(end) = end_time {
108                    if log.timestamp > end {
109                        return false;
110                    }
111                }
112
113                true
114            })
115            .take(limit)
116            .cloned()
117            .collect()
118    }
119
120    pub async fn cleanup_old_logs(&self) -> Result<usize> {
121        let cutoff = Utc::now() - chrono::Duration::days(self.retention_days as i64);
122
123        let mut logs = self.logs.write().await;
124        let initial_count = logs.len();
125
126        logs.retain(|log| log.timestamp > cutoff);
127
128        let removed = initial_count - logs.len();
129
130        tracing::info!("Cleaned up {} old audit logs", removed);
131        Ok(removed)
132    }
133
134    pub async fn get_user_activity(&self, user_id: &Uuid, days: u32) -> UserActivity {
135        let since = Utc::now() - chrono::Duration::days(days as i64);
136        let logs = self.logs.read().await;
137
138        let user_logs: Vec<_> = logs
139            .iter()
140            .filter(|log| log.user_id == Some(*user_id) && log.timestamp > since)
141            .collect();
142
143        let total_actions = user_logs.len();
144        let successful = user_logs.iter().filter(|l| l.result == AuditResult::Success).count();
145        let failed = user_logs.iter().filter(|l| l.result == AuditResult::Failure).count();
146        let blocked = user_logs.iter().filter(|l| l.result == AuditResult::Blocked).count();
147
148        let unique_ips: std::collections::HashSet<_> = user_logs
149            .iter()
150            .filter_map(|l| l.ip_address)
151            .collect();
152
153        let action_breakdown: HashMap<String, usize> = user_logs
154            .iter()
155            .fold(HashMap::new(), |mut acc, log| {
156                *acc.entry(log.action.clone()).or_insert(0) += 1;
157                acc
158            });
159
160        UserActivity {
161            user_id: *user_id,
162            total_actions,
163            successful,
164            failed,
165            blocked,
166            unique_ips: unique_ips.len(),
167            action_breakdown,
168            avg_risk_score: if total_actions > 0 {
169                user_logs.iter().map(|l| l.risk_score as f64).sum::<f64>() / total_actions as f64
170            } else {
171                0.0
172            },
173        }
174    }
175
176    // Compliance reporting for LGPD/GDPR
177    pub async fn generate_compliance_report(
178        &self,
179        user_id: &Uuid,
180    ) -> ComplianceReport {
181        let logs = self.logs.read().await;
182
183        let user_logs: Vec<_> = logs
184            .iter()
185            .filter(|log| log.user_id == Some(*user_id))
186            .cloned()
187            .collect();
188
189        let data_accesses = user_logs
190            .iter()
191            .filter(|l| l.action.contains("read") || l.action.contains("access"))
192            .count();
193
194        let data_modifications = user_logs
195            .iter()
196            .filter(|l| l.action.contains("update") || l.action.contains("delete"))
197            .count();
198
199        let data_exports = user_logs
200            .iter()
201            .filter(|l| l.action.contains("export"))
202            .count();
203
204        ComplianceReport {
205            user_id: *user_id,
206            report_date: Utc::now(),
207            total_events: user_logs.len(),
208            data_accesses,
209            data_modifications,
210            data_exports,
211            logs: user_logs,
212        }
213    }
214}
215
216#[derive(Debug, Clone, serde::Serialize)]
217pub struct UserActivity {
218    pub user_id: Uuid,
219    pub total_actions: usize,
220    pub successful: usize,
221    pub failed: usize,
222    pub blocked: usize,
223    pub unique_ips: usize,
224    pub action_breakdown: HashMap<String, usize>,
225    pub avg_risk_score: f64,
226}
227
228#[derive(Debug, Clone, serde::Serialize)]
229pub struct ComplianceReport {
230    pub user_id: Uuid,
231    pub report_date: chrono::DateTime<Utc>,
232    pub total_events: usize,
233    pub data_accesses: usize,
234    pub data_modifications: usize,
235    pub data_exports: usize,
236    pub logs: Vec<AuditLog>,
237}