1use std::collections::HashMap;
26use std::fmt;
27use std::sync::Arc;
28use std::time::{SystemTime, UNIX_EPOCH};
29
30use tokio::sync::RwLock;
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum SecurityEventType {
35 AuthenticationSuccess,
38 AuthenticationFailure,
40 Logout,
42 SessionCreated,
44 SessionDestroyed,
46 SessionExpired,
48
49 AccessGranted,
52 AccessDenied,
54 InsufficientPermissions,
56
57 AccountLocked,
60 AccountUnlocked,
62 PasswordChanged,
64 PasswordResetRequested,
66
67 TokenGenerated,
70 TokenRefreshed,
72 TokenRevoked,
74 TokenExpired,
76 InvalidToken,
78
79 RateLimitExceeded,
82 RateLimitWarning,
84
85 CsrfValidationFailed,
88 CsrfTokenMissing,
90
91 BruteForceDetected,
94 SuspiciousIp,
96 MultipleFailures,
98
99 Custom(String),
102}
103
104impl fmt::Display for SecurityEventType {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match self {
107 SecurityEventType::AuthenticationSuccess => write!(f, "AUTHENTICATION_SUCCESS"),
108 SecurityEventType::AuthenticationFailure => write!(f, "AUTHENTICATION_FAILURE"),
109 SecurityEventType::Logout => write!(f, "LOGOUT"),
110 SecurityEventType::SessionCreated => write!(f, "SESSION_CREATED"),
111 SecurityEventType::SessionDestroyed => write!(f, "SESSION_DESTROYED"),
112 SecurityEventType::SessionExpired => write!(f, "SESSION_EXPIRED"),
113 SecurityEventType::AccessGranted => write!(f, "ACCESS_GRANTED"),
114 SecurityEventType::AccessDenied => write!(f, "ACCESS_DENIED"),
115 SecurityEventType::InsufficientPermissions => write!(f, "INSUFFICIENT_PERMISSIONS"),
116 SecurityEventType::AccountLocked => write!(f, "ACCOUNT_LOCKED"),
117 SecurityEventType::AccountUnlocked => write!(f, "ACCOUNT_UNLOCKED"),
118 SecurityEventType::PasswordChanged => write!(f, "PASSWORD_CHANGED"),
119 SecurityEventType::PasswordResetRequested => write!(f, "PASSWORD_RESET_REQUESTED"),
120 SecurityEventType::TokenGenerated => write!(f, "TOKEN_GENERATED"),
121 SecurityEventType::TokenRefreshed => write!(f, "TOKEN_REFRESHED"),
122 SecurityEventType::TokenRevoked => write!(f, "TOKEN_REVOKED"),
123 SecurityEventType::TokenExpired => write!(f, "TOKEN_EXPIRED"),
124 SecurityEventType::InvalidToken => write!(f, "INVALID_TOKEN"),
125 SecurityEventType::RateLimitExceeded => write!(f, "RATE_LIMIT_EXCEEDED"),
126 SecurityEventType::RateLimitWarning => write!(f, "RATE_LIMIT_WARNING"),
127 SecurityEventType::CsrfValidationFailed => write!(f, "CSRF_VALIDATION_FAILED"),
128 SecurityEventType::CsrfTokenMissing => write!(f, "CSRF_TOKEN_MISSING"),
129 SecurityEventType::BruteForceDetected => write!(f, "BRUTE_FORCE_DETECTED"),
130 SecurityEventType::SuspiciousIp => write!(f, "SUSPICIOUS_IP"),
131 SecurityEventType::MultipleFailures => write!(f, "MULTIPLE_FAILURES"),
132 SecurityEventType::Custom(name) => write!(f, "CUSTOM_{}", name.to_uppercase()),
133 }
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
139pub enum SecurityEventSeverity {
140 #[default]
142 Info,
143 Warning,
145 Error,
147 Critical,
149}
150
151impl fmt::Display for SecurityEventSeverity {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 match self {
154 SecurityEventSeverity::Info => write!(f, "INFO"),
155 SecurityEventSeverity::Warning => write!(f, "WARNING"),
156 SecurityEventSeverity::Error => write!(f, "ERROR"),
157 SecurityEventSeverity::Critical => write!(f, "CRITICAL"),
158 }
159 }
160}
161
162impl SecurityEventType {
163 pub fn default_severity(&self) -> SecurityEventSeverity {
165 match self {
166 SecurityEventType::AuthenticationSuccess
167 | SecurityEventType::Logout
168 | SecurityEventType::SessionCreated
169 | SecurityEventType::AccessGranted
170 | SecurityEventType::TokenGenerated
171 | SecurityEventType::TokenRefreshed
172 | SecurityEventType::PasswordChanged => SecurityEventSeverity::Info,
173
174 SecurityEventType::SessionExpired
175 | SecurityEventType::TokenExpired
176 | SecurityEventType::RateLimitWarning => SecurityEventSeverity::Warning,
177
178 SecurityEventType::AuthenticationFailure
179 | SecurityEventType::SessionDestroyed
180 | SecurityEventType::AccessDenied
181 | SecurityEventType::InsufficientPermissions
182 | SecurityEventType::TokenRevoked
183 | SecurityEventType::InvalidToken
184 | SecurityEventType::RateLimitExceeded
185 | SecurityEventType::CsrfValidationFailed
186 | SecurityEventType::CsrfTokenMissing
187 | SecurityEventType::AccountLocked
188 | SecurityEventType::PasswordResetRequested => SecurityEventSeverity::Error,
189
190 SecurityEventType::BruteForceDetected
191 | SecurityEventType::SuspiciousIp
192 | SecurityEventType::MultipleFailures => SecurityEventSeverity::Critical,
193
194 SecurityEventType::AccountUnlocked | SecurityEventType::Custom(_) => {
195 SecurityEventSeverity::Info
196 }
197 }
198 }
199}
200
201#[derive(Debug, Clone)]
203pub struct SecurityEvent {
204 pub id: String,
206 pub timestamp: u64,
208 pub event_type: SecurityEventType,
210 pub severity: SecurityEventSeverity,
212 pub username: Option<String>,
214 pub ip_address: Option<String>,
216 pub user_agent: Option<String>,
218 pub path: Option<String>,
220 pub method: Option<String>,
222 pub session_id: Option<String>,
224 pub details: HashMap<String, String>,
226 pub error: Option<String>,
228}
229
230impl SecurityEvent {
231 pub fn new(event_type: SecurityEventType) -> Self {
233 let now = SystemTime::now()
234 .duration_since(UNIX_EPOCH)
235 .unwrap_or_default()
236 .as_millis() as u64;
237
238 Self {
239 id: generate_event_id(),
240 timestamp: now,
241 severity: event_type.default_severity(),
242 event_type,
243 username: None,
244 ip_address: None,
245 user_agent: None,
246 path: None,
247 method: None,
248 session_id: None,
249 details: HashMap::new(),
250 error: None,
251 }
252 }
253
254 pub fn username(mut self, username: impl Into<String>) -> Self {
256 self.username = Some(username.into());
257 self
258 }
259
260 pub fn ip_address(mut self, ip: impl Into<String>) -> Self {
262 self.ip_address = Some(ip.into());
263 self
264 }
265
266 pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
268 self.user_agent = Some(ua.into());
269 self
270 }
271
272 pub fn path(mut self, path: impl Into<String>) -> Self {
274 self.path = Some(path.into());
275 self
276 }
277
278 pub fn method(mut self, method: impl Into<String>) -> Self {
280 self.method = Some(method.into());
281 self
282 }
283
284 pub fn session_id(mut self, id: impl Into<String>) -> Self {
286 self.session_id = Some(id.into());
287 self
288 }
289
290 pub fn severity(mut self, severity: SecurityEventSeverity) -> Self {
292 self.severity = severity;
293 self
294 }
295
296 pub fn detail(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
298 self.details.insert(key.into(), value.into());
299 self
300 }
301
302 pub fn error(mut self, error: impl Into<String>) -> Self {
304 self.error = Some(error.into());
305 self
306 }
307
308 pub fn login_success(username: &str, ip: &str) -> Self {
312 Self::new(SecurityEventType::AuthenticationSuccess)
313 .username(username)
314 .ip_address(ip)
315 }
316
317 pub fn login_failure(username: &str, ip: &str, reason: &str) -> Self {
319 Self::new(SecurityEventType::AuthenticationFailure)
320 .username(username)
321 .ip_address(ip)
322 .error(reason)
323 }
324
325 pub fn access_denied(username: &str, path: &str, ip: &str) -> Self {
327 Self::new(SecurityEventType::AccessDenied)
328 .username(username)
329 .path(path)
330 .ip_address(ip)
331 }
332
333 pub fn rate_limit_exceeded(ip: &str, path: &str) -> Self {
335 Self::new(SecurityEventType::RateLimitExceeded)
336 .ip_address(ip)
337 .path(path)
338 }
339
340 pub fn account_locked(username: &str, ip: &str, reason: &str) -> Self {
342 Self::new(SecurityEventType::AccountLocked)
343 .username(username)
344 .ip_address(ip)
345 .detail("reason", reason)
346 }
347
348 pub fn brute_force_detected(ip: &str, attempts: u32) -> Self {
350 Self::new(SecurityEventType::BruteForceDetected)
351 .ip_address(ip)
352 .detail("attempts", attempts.to_string())
353 }
354
355 pub fn to_log_line(&self) -> String {
357 let mut parts = vec![
358 format!("[{}]", self.severity),
359 format!("[{}]", self.event_type),
360 ];
361
362 if let Some(ref username) = self.username {
363 parts.push(format!("user={}", username));
364 }
365 if let Some(ref ip) = self.ip_address {
366 parts.push(format!("ip={}", ip));
367 }
368 if let Some(ref path) = self.path {
369 parts.push(format!("path={}", path));
370 }
371 if let Some(ref error) = self.error {
372 parts.push(format!("error=\"{}\"", error));
373 }
374 for (k, v) in &self.details {
375 parts.push(format!("{}={}", k, v));
376 }
377
378 parts.join(" ")
379 }
380
381 #[cfg(any(feature = "jwt", feature = "session", feature = "oauth2"))]
385 pub fn to_json(&self) -> String {
386 serde_json::to_string(self).unwrap_or_else(|_| self.to_log_line())
387 }
388
389 #[cfg(not(any(feature = "jwt", feature = "session", feature = "oauth2")))]
391 pub fn to_json(&self) -> String {
392 self.to_log_line()
393 }
394}
395
396#[cfg(any(feature = "jwt", feature = "session", feature = "oauth2"))]
397impl serde::Serialize for SecurityEvent {
398 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
399 where
400 S: serde::Serializer,
401 {
402 use serde::ser::SerializeStruct;
403 let mut state = serializer.serialize_struct("SecurityEvent", 12)?;
404 state.serialize_field("id", &self.id)?;
405 state.serialize_field("timestamp", &self.timestamp)?;
406 state.serialize_field("event_type", &self.event_type.to_string())?;
407 state.serialize_field("severity", &self.severity.to_string())?;
408 state.serialize_field("username", &self.username)?;
409 state.serialize_field("ip_address", &self.ip_address)?;
410 state.serialize_field("user_agent", &self.user_agent)?;
411 state.serialize_field("path", &self.path)?;
412 state.serialize_field("method", &self.method)?;
413 state.serialize_field("session_id", &self.session_id)?;
414 state.serialize_field("details", &self.details)?;
415 state.serialize_field("error", &self.error)?;
416 state.end()
417 }
418}
419
420fn generate_event_id() -> String {
422 use rand::Rng;
423 let timestamp = SystemTime::now()
424 .duration_since(UNIX_EPOCH)
425 .unwrap_or_default()
426 .as_micros();
427 let random: u32 = rand::thread_rng().gen();
428 format!("{:x}-{:08x}", timestamp, random)
429}
430
431pub trait SecurityEventHandler: Send + Sync {
433 fn handle(&self, event: &SecurityEvent);
435}
436
437#[derive(Default)]
439pub struct StdoutHandler {
440 min_severity: SecurityEventSeverity,
441}
442
443impl StdoutHandler {
444 pub fn new() -> Self {
446 Self::default()
447 }
448
449 pub fn min_severity(mut self, severity: SecurityEventSeverity) -> Self {
451 self.min_severity = severity;
452 self
453 }
454}
455
456impl SecurityEventHandler for StdoutHandler {
457 fn handle(&self, event: &SecurityEvent) {
458 if event.severity >= self.min_severity {
459 println!("[SECURITY] {}", event.to_log_line());
460 }
461 }
462}
463
464#[derive(Default, Clone, Copy)]
483pub struct TracingHandler;
484
485impl TracingHandler {
486 pub fn new() -> Self {
490 Self
491 }
492}
493
494impl SecurityEventHandler for TracingHandler {
495 fn handle(&self, event: &SecurityEvent) {
496 let username = event.username.as_deref().unwrap_or("-");
497 let ip = event.ip_address.as_deref().unwrap_or("-");
498 let path = event.path.as_deref().unwrap_or("-");
499 let method = event.method.as_deref().unwrap_or("-");
500 let event_type = event.event_type.to_string();
501 let error_msg = event.error.as_deref().unwrap_or("");
502
503 match event.severity {
504 SecurityEventSeverity::Info => {
505 tracing::info!(
506 target: "actix_security::audit",
507 event_type = %event_type,
508 user = %username,
509 ip = %ip,
510 path = %path,
511 method = %method,
512 "Security event"
513 );
514 }
515 SecurityEventSeverity::Warning => {
516 tracing::warn!(
517 target: "actix_security::audit",
518 event_type = %event_type,
519 user = %username,
520 ip = %ip,
521 path = %path,
522 method = %method,
523 "Security warning"
524 );
525 }
526 SecurityEventSeverity::Error => {
527 tracing::error!(
528 target: "actix_security::audit",
529 event_type = %event_type,
530 user = %username,
531 ip = %ip,
532 path = %path,
533 method = %method,
534 error = %error_msg,
535 "Security error"
536 );
537 }
538 SecurityEventSeverity::Critical => {
539 tracing::error!(
540 target: "actix_security::audit",
541 event_type = %event_type,
542 user = %username,
543 ip = %ip,
544 path = %path,
545 method = %method,
546 error = %error_msg,
547 severity = "CRITICAL",
548 "Critical security event"
549 );
550 }
551 }
552 }
553}
554
555pub struct ClosureHandler<F>
557where
558 F: Fn(&SecurityEvent) + Send + Sync,
559{
560 handler: F,
561}
562
563impl<F> ClosureHandler<F>
564where
565 F: Fn(&SecurityEvent) + Send + Sync,
566{
567 pub fn new(handler: F) -> Self {
569 Self { handler }
570 }
571}
572
573impl<F> SecurityEventHandler for ClosureHandler<F>
574where
575 F: Fn(&SecurityEvent) + Send + Sync,
576{
577 fn handle(&self, event: &SecurityEvent) {
578 (self.handler)(event);
579 }
580}
581
582#[derive(Clone)]
584pub struct InMemoryEventStore {
585 events: Arc<RwLock<Vec<SecurityEvent>>>,
586 max_events: usize,
587}
588
589impl Default for InMemoryEventStore {
590 fn default() -> Self {
591 Self::new()
592 }
593}
594
595impl InMemoryEventStore {
596 pub fn new() -> Self {
598 Self {
599 events: Arc::new(RwLock::new(Vec::new())),
600 max_events: 10000,
601 }
602 }
603
604 pub fn max_events(mut self, max: usize) -> Self {
606 self.max_events = max;
607 self
608 }
609
610 pub async fn get_events(&self) -> Vec<SecurityEvent> {
612 self.events.read().await.clone()
613 }
614
615 pub async fn get_events_by_type(&self, event_type: &SecurityEventType) -> Vec<SecurityEvent> {
617 self.events
618 .read()
619 .await
620 .iter()
621 .filter(|e| &e.event_type == event_type)
622 .cloned()
623 .collect()
624 }
625
626 pub async fn get_events_by_user(&self, username: &str) -> Vec<SecurityEvent> {
628 self.events
629 .read()
630 .await
631 .iter()
632 .filter(|e| e.username.as_deref() == Some(username))
633 .cloned()
634 .collect()
635 }
636
637 pub async fn clear(&self) {
639 self.events.write().await.clear();
640 }
641}
642
643impl SecurityEventHandler for InMemoryEventStore {
644 fn handle(&self, event: &SecurityEvent) {
645 let rt = tokio::runtime::Handle::try_current();
647 if let Ok(handle) = rt {
648 let events = Arc::clone(&self.events);
649 let event = event.clone();
650 let max = self.max_events;
651 handle.spawn(async move {
652 let mut guard = events.write().await;
653 guard.push(event);
654 if guard.len() > max {
655 guard.remove(0);
656 }
657 });
658 }
659 }
660}
661
662#[derive(Clone)]
664pub struct AuditLogger {
665 handlers: Arc<Vec<Arc<dyn SecurityEventHandler>>>,
666 enabled: bool,
667}
668
669impl Default for AuditLogger {
670 fn default() -> Self {
671 Self::new()
672 }
673}
674
675impl AuditLogger {
676 pub fn new() -> Self {
678 Self {
679 handlers: Arc::new(Vec::new()),
680 enabled: true,
681 }
682 }
683
684 pub fn with_stdout() -> Self {
686 Self::new().add_handler(StdoutHandler::new())
687 }
688
689 pub fn add_handler<H: SecurityEventHandler + 'static>(mut self, handler: H) -> Self {
691 let handlers = Arc::make_mut(&mut self.handlers);
692 handlers.push(Arc::new(handler));
693 self
694 }
695
696 pub fn with_handler<F>(self, handler: F) -> Self
698 where
699 F: Fn(&SecurityEvent) + Send + Sync + 'static,
700 {
701 self.add_handler(ClosureHandler::new(handler))
702 }
703
704 pub fn enabled(mut self, enabled: bool) -> Self {
706 self.enabled = enabled;
707 self
708 }
709
710 pub fn log(&self, event: SecurityEvent) {
712 if !self.enabled {
713 return;
714 }
715
716 for handler in self.handlers.iter() {
717 handler.handle(&event);
718 }
719 }
720
721 pub fn log_login_success(&self, username: &str, ip: &str) {
723 self.log(SecurityEvent::login_success(username, ip));
724 }
725
726 pub fn log_login_failure(&self, username: &str, ip: &str, reason: &str) {
728 self.log(SecurityEvent::login_failure(username, ip, reason));
729 }
730
731 pub fn log_access_denied(&self, username: &str, path: &str, ip: &str) {
733 self.log(SecurityEvent::access_denied(username, path, ip));
734 }
735
736 pub fn log_rate_limit_exceeded(&self, ip: &str, path: &str) {
738 self.log(SecurityEvent::rate_limit_exceeded(ip, path));
739 }
740}
741
742static GLOBAL_LOGGER: std::sync::OnceLock<AuditLogger> = std::sync::OnceLock::new();
744
745pub fn init_global_logger(logger: AuditLogger) {
747 let _ = GLOBAL_LOGGER.set(logger);
748}
749
750pub fn global_logger() -> &'static AuditLogger {
752 GLOBAL_LOGGER.get_or_init(AuditLogger::new)
753}
754
755pub fn audit_log(event: SecurityEvent) {
757 global_logger().log(event);
758}
759
760#[cfg(test)]
761mod tests {
762 use super::*;
763
764 #[test]
765 fn test_event_creation() {
766 let event = SecurityEvent::login_success("admin", "192.168.1.1");
767 assert_eq!(event.event_type, SecurityEventType::AuthenticationSuccess);
768 assert_eq!(event.username, Some("admin".to_string()));
769 assert_eq!(event.ip_address, Some("192.168.1.1".to_string()));
770 assert_eq!(event.severity, SecurityEventSeverity::Info);
771 }
772
773 #[test]
774 fn test_event_builder() {
775 let event = SecurityEvent::new(SecurityEventType::AccessDenied)
776 .username("user1")
777 .ip_address("10.0.0.1")
778 .path("/admin")
779 .detail("reason", "missing role")
780 .error("Access denied");
781
782 assert_eq!(event.username, Some("user1".to_string()));
783 assert_eq!(event.path, Some("/admin".to_string()));
784 assert!(event.details.contains_key("reason"));
785 assert_eq!(event.error, Some("Access denied".to_string()));
786 }
787
788 #[test]
789 fn test_severity_ordering() {
790 assert!(SecurityEventSeverity::Info < SecurityEventSeverity::Warning);
791 assert!(SecurityEventSeverity::Warning < SecurityEventSeverity::Error);
792 assert!(SecurityEventSeverity::Error < SecurityEventSeverity::Critical);
793 }
794
795 #[test]
796 fn test_event_type_display() {
797 assert_eq!(
798 SecurityEventType::AuthenticationSuccess.to_string(),
799 "AUTHENTICATION_SUCCESS"
800 );
801 assert_eq!(
802 SecurityEventType::Custom("test".to_string()).to_string(),
803 "CUSTOM_TEST"
804 );
805 }
806
807 #[test]
808 fn test_log_line_format() {
809 let event = SecurityEvent::login_failure("admin", "192.168.1.1", "Invalid password");
810 let log_line = event.to_log_line();
811
812 assert!(log_line.contains("[ERROR]"));
813 assert!(log_line.contains("[AUTHENTICATION_FAILURE]"));
814 assert!(log_line.contains("user=admin"));
815 assert!(log_line.contains("ip=192.168.1.1"));
816 }
817
818 #[test]
819 fn test_default_severity() {
820 assert_eq!(
821 SecurityEventType::AuthenticationSuccess.default_severity(),
822 SecurityEventSeverity::Info
823 );
824 assert_eq!(
825 SecurityEventType::BruteForceDetected.default_severity(),
826 SecurityEventSeverity::Critical
827 );
828 }
829
830 #[test]
831 fn test_audit_logger_with_closure() {
832 use std::sync::atomic::{AtomicUsize, Ordering};
833 let counter = Arc::new(AtomicUsize::new(0));
834 let counter_clone = counter.clone();
835
836 let logger = AuditLogger::new().with_handler(move |_event| {
837 counter_clone.fetch_add(1, Ordering::SeqCst);
838 });
839
840 logger.log_login_success("admin", "127.0.0.1");
841 logger.log_login_failure("user", "127.0.0.1", "Bad password");
842
843 assert_eq!(counter.load(Ordering::SeqCst), 2);
844 }
845
846 #[test]
847 fn test_disabled_logger() {
848 use std::sync::atomic::{AtomicUsize, Ordering};
849 let counter = Arc::new(AtomicUsize::new(0));
850 let counter_clone = counter.clone();
851
852 let logger = AuditLogger::new()
853 .with_handler(move |_event| {
854 counter_clone.fetch_add(1, Ordering::SeqCst);
855 })
856 .enabled(false);
857
858 logger.log_login_success("admin", "127.0.0.1");
859
860 assert_eq!(counter.load(Ordering::SeqCst), 0);
861 }
862}