auth_framework/monitoring/
alerts.rs

1//! Alerting system for security and performance monitoring
2
3use super::{SecurityEvent, SecurityEventSeverity, SecurityEventType};
4use crate::errors::Result;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Alert configuration
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct AlertConfig {
11    /// Enable alerting
12    pub enabled: bool,
13    /// Alert thresholds
14    pub thresholds: AlertThresholds,
15    /// Notification channels
16    pub channels: Vec<NotificationChannel>,
17}
18
19/// Alert threshold configuration
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct AlertThresholds {
22    /// Failed login attempts per minute
23    pub failed_logins_per_minute: u64,
24    /// Maximum response time in milliseconds
25    pub max_response_time_ms: u64,
26    /// Error rate threshold (0.0 - 1.0)
27    pub error_rate_threshold: f64,
28    /// Minimum time between duplicate alerts in seconds
29    pub alert_cooldown_seconds: u64,
30}
31
32impl Default for AlertThresholds {
33    fn default() -> Self {
34        Self {
35            failed_logins_per_minute: 10,
36            max_response_time_ms: 5000,
37            error_rate_threshold: 0.1,   // 10%
38            alert_cooldown_seconds: 300, // 5 minutes
39        }
40    }
41}
42
43/// Notification channel types
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub enum NotificationChannel {
46    /// Email notifications
47    Email { recipients: Vec<String> },
48    /// Slack webhook
49    Slack { webhook_url: String },
50    /// Microsoft Teams webhook
51    Teams { webhook_url: String },
52    /// Generic webhook
53    Webhook {
54        url: String,
55        headers: HashMap<String, String>,
56    },
57    /// Log-based alerts
58    Log { level: String },
59}
60
61/// Alert severity levels
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
63pub enum AlertSeverity {
64    Info,
65    Warning,
66    Critical,
67}
68
69/// Alert message
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Alert {
72    /// Alert ID
73    pub id: String,
74    /// Alert title
75    pub title: String,
76    /// Alert message
77    pub message: String,
78    /// Severity level
79    pub severity: AlertSeverity,
80    /// Source component
81    pub source: String,
82    /// Related metrics
83    pub metrics: HashMap<String, f64>,
84    /// Timestamp
85    pub timestamp: u64,
86}
87
88/// Alert manager
89pub struct AlertManager {
90    /// Configuration
91    config: AlertConfig,
92    /// Recent alerts for cooldown tracking
93    recent_alerts: HashMap<String, u64>,
94}
95
96impl AlertManager {
97    /// Create new alert manager
98    pub fn new(config: AlertConfig) -> Self {
99        Self {
100            config,
101            recent_alerts: HashMap::new(),
102        }
103    }
104
105    /// Process security event and generate alerts if needed
106    pub async fn process_security_event(&mut self, event: &SecurityEvent) -> Result<()> {
107        if !self.config.enabled {
108            return Ok(());
109        }
110
111        let alert = match event.event_type {
112            SecurityEventType::FailedLogin => {
113                if event.severity >= SecurityEventSeverity::High {
114                    Some(Alert {
115                        id: format!("failed_login_{}", event.timestamp),
116                        title: "High volume of failed login attempts detected".to_string(),
117                        message: format!(
118                            "Multiple failed login attempts detected for user {:?} from IP {:?}",
119                            event.user_id, event.ip_address
120                        ),
121                        severity: AlertSeverity::Warning,
122                        source: "authentication".to_string(),
123                        metrics: HashMap::new(),
124                        timestamp: event.timestamp,
125                    })
126                } else {
127                    None
128                }
129            }
130            SecurityEventType::AccountLockout => Some(Alert {
131                id: format!("account_lockout_{}", event.timestamp),
132                title: "Account lockout triggered".to_string(),
133                message: format!(
134                    "Account {:?} has been locked due to security policy",
135                    event.user_id
136                ),
137                severity: AlertSeverity::Warning,
138                source: "security".to_string(),
139                metrics: HashMap::new(),
140                timestamp: event.timestamp,
141            }),
142            SecurityEventType::PrivilegeEscalation => Some(Alert {
143                id: format!("privilege_escalation_{}", event.timestamp),
144                title: "Privilege escalation attempt detected".to_string(),
145                message: format!("Privilege escalation attempt by user {:?}", event.user_id),
146                severity: AlertSeverity::Critical,
147                source: "authorization".to_string(),
148                metrics: HashMap::new(),
149                timestamp: event.timestamp,
150            }),
151            _ => None,
152        };
153
154        if let Some(alert) = alert {
155            self.send_alert(alert).await?;
156        }
157
158        Ok(())
159    }
160
161    /// Process performance metrics and generate alerts
162    pub async fn process_performance_metrics(
163        &mut self,
164        metrics: &HashMap<String, u64>,
165    ) -> Result<()> {
166        if !self.config.enabled {
167            return Ok(());
168        }
169
170        // Check response time threshold
171        if let Some(&response_time) = metrics.get("avg_response_time_us") {
172            let response_time_ms = response_time / 1000; // Convert to milliseconds
173
174            if response_time_ms > self.config.thresholds.max_response_time_ms {
175                let alert = Alert {
176                    id: format!(
177                        "high_response_time_{}",
178                        crate::monitoring::current_timestamp()
179                    ),
180                    title: "High response time detected".to_string(),
181                    message: format!(
182                        "Average response time is {}ms, which exceeds the threshold of {}ms",
183                        response_time_ms, self.config.thresholds.max_response_time_ms
184                    ),
185                    severity: AlertSeverity::Warning,
186                    source: "performance".to_string(),
187                    metrics: {
188                        let mut m = HashMap::new();
189                        m.insert("response_time_ms".to_string(), response_time_ms as f64);
190                        m.insert(
191                            "threshold_ms".to_string(),
192                            self.config.thresholds.max_response_time_ms as f64,
193                        );
194                        m
195                    },
196                    timestamp: crate::monitoring::current_timestamp(),
197                };
198
199                self.send_alert(alert).await?;
200            }
201        }
202
203        // Check error rate
204        if let (Some(&auth_requests), Some(&auth_failures)) =
205            (metrics.get("auth_requests"), metrics.get("auth_failures"))
206            && auth_requests > 0 {
207                let error_rate = auth_failures as f64 / auth_requests as f64;
208
209                if error_rate > self.config.thresholds.error_rate_threshold {
210                    let alert = Alert {
211                        id: format!("high_error_rate_{}", crate::monitoring::current_timestamp()),
212                        title: "High authentication error rate".to_string(),
213                        message: format!(
214                            "Authentication error rate is {:.1}%, which exceeds the threshold of {:.1}%",
215                            error_rate * 100.0,
216                            self.config.thresholds.error_rate_threshold * 100.0
217                        ),
218                        severity: AlertSeverity::Critical,
219                        source: "authentication".to_string(),
220                        metrics: {
221                            let mut m = HashMap::new();
222                            m.insert("error_rate".to_string(), error_rate);
223                            m.insert(
224                                "threshold".to_string(),
225                                self.config.thresholds.error_rate_threshold,
226                            );
227                            m.insert("total_requests".to_string(), auth_requests as f64);
228                            m.insert("failed_requests".to_string(), auth_failures as f64);
229                            m
230                        },
231                        timestamp: crate::monitoring::current_timestamp(),
232                    };
233
234                    self.send_alert(alert).await?;
235                }
236            }
237
238        Ok(())
239    }
240
241    /// Send alert through configured channels
242    async fn send_alert(&mut self, alert: Alert) -> Result<()> {
243        // Check cooldown
244        if let Some(&last_alert_time) = self.recent_alerts.get(&alert.id) {
245            let current_time = crate::monitoring::current_timestamp();
246            if current_time - last_alert_time < self.config.thresholds.alert_cooldown_seconds {
247                tracing::debug!("Alert {} is in cooldown period, skipping", alert.id);
248                return Ok(());
249            }
250        }
251
252        // Update recent alerts tracking
253        self.recent_alerts.insert(alert.id.clone(), alert.timestamp);
254
255        // Send to all configured channels
256        for channel in &self.config.channels {
257            self.send_to_channel(&alert, channel).await?;
258        }
259
260        tracing::info!(
261            "Alert sent: {} - {} (Severity: {:?})",
262            alert.title,
263            alert.message,
264            alert.severity
265        );
266
267        Ok(())
268    }
269
270    /// Send alert to specific channel
271    async fn send_to_channel(&self, alert: &Alert, channel: &NotificationChannel) -> Result<()> {
272        match channel {
273            NotificationChannel::Email { recipients } => {
274                // In production: Send email using SMTP or email service API
275                tracing::info!(
276                    "EMAIL ALERT to {:?}: {} - {}",
277                    recipients,
278                    alert.title,
279                    alert.message
280                );
281            }
282            NotificationChannel::Slack { webhook_url } => {
283                // In production: Send POST request to Slack webhook
284                tracing::info!(
285                    "SLACK ALERT to {}: {} - {}",
286                    webhook_url,
287                    alert.title,
288                    alert.message
289                );
290            }
291            NotificationChannel::Teams { webhook_url } => {
292                // In production: Send POST request to Teams webhook
293                tracing::info!(
294                    "TEAMS ALERT to {}: {} - {}",
295                    webhook_url,
296                    alert.title,
297                    alert.message
298                );
299            }
300            NotificationChannel::Webhook { url, headers: _ } => {
301                // In production: Send POST request to generic webhook
302                tracing::info!(
303                    "WEBHOOK ALERT to {}: {} - {}",
304                    url,
305                    alert.title,
306                    alert.message
307                );
308            }
309            NotificationChannel::Log { level } => match level.as_str() {
310                "error" => tracing::error!("ALERT: {} - {}", alert.title, alert.message),
311                "warn" => tracing::warn!("ALERT: {} - {}", alert.title, alert.message),
312                _ => tracing::info!("ALERT: {} - {}", alert.title, alert.message),
313            },
314        }
315
316        Ok(())
317    }
318
319    /// Clean up old alert tracking data
320    pub fn cleanup_alert_history(&mut self, max_age_seconds: u64) {
321        let current_time = crate::monitoring::current_timestamp();
322        self.recent_alerts
323            .retain(|_, &mut timestamp| current_time - timestamp < max_age_seconds);
324    }
325}
326
327impl Default for AlertConfig {
328    fn default() -> Self {
329        Self {
330            enabled: true,
331            thresholds: AlertThresholds::default(),
332            channels: vec![NotificationChannel::Log {
333                level: "warn".to_string(),
334            }],
335        }
336    }
337}
338
339