auth_framework/monitoring/
alerts.rs1use super::{SecurityEvent, SecurityEventSeverity, SecurityEventType};
4use crate::errors::Result;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct AlertConfig {
11 pub enabled: bool,
13 pub thresholds: AlertThresholds,
15 pub channels: Vec<NotificationChannel>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct AlertThresholds {
22 pub failed_logins_per_minute: u64,
24 pub max_response_time_ms: u64,
26 pub error_rate_threshold: f64,
28 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, alert_cooldown_seconds: 300, }
40 }
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub enum NotificationChannel {
46 Email { recipients: Vec<String> },
48 Slack { webhook_url: String },
50 Teams { webhook_url: String },
52 Webhook {
54 url: String,
55 headers: HashMap<String, String>,
56 },
57 Log { level: String },
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
63pub enum AlertSeverity {
64 Info,
65 Warning,
66 Critical,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Alert {
72 pub id: String,
74 pub title: String,
76 pub message: String,
78 pub severity: AlertSeverity,
80 pub source: String,
82 pub metrics: HashMap<String, f64>,
84 pub timestamp: u64,
86}
87
88pub struct AlertManager {
90 config: AlertConfig,
92 recent_alerts: HashMap<String, u64>,
94}
95
96impl AlertManager {
97 pub fn new(config: AlertConfig) -> Self {
99 Self {
100 config,
101 recent_alerts: HashMap::new(),
102 }
103 }
104
105 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 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 if let Some(&response_time) = metrics.get("avg_response_time_us") {
172 let response_time_ms = response_time / 1000; 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 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 async fn send_alert(&mut self, alert: Alert) -> Result<()> {
243 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 self.recent_alerts.insert(alert.id.clone(), alert.timestamp);
254
255 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 async fn send_to_channel(&self, alert: &Alert, channel: &NotificationChannel) -> Result<()> {
272 match channel {
273 NotificationChannel::Email { recipients } => {
274 tracing::info!(
276 "EMAIL ALERT to {:?}: {} - {}",
277 recipients,
278 alert.title,
279 alert.message
280 );
281 }
282 NotificationChannel::Slack { webhook_url } => {
283 tracing::info!(
285 "SLACK ALERT to {}: {} - {}",
286 webhook_url,
287 alert.title,
288 alert.message
289 );
290 }
291 NotificationChannel::Teams { webhook_url } => {
292 tracing::info!(
294 "TEAMS ALERT to {}: {} - {}",
295 webhook_url,
296 alert.title,
297 alert.message
298 );
299 }
300 NotificationChannel::Webhook { url, headers: _ } => {
301 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 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