1use serde::Serialize;
15
16#[derive(Debug, Clone, Serialize)]
18#[serde(tag = "event_type", rename_all = "snake_case")]
19pub enum AuditEvent {
20 AuthAttemptStarted {
22 email: String,
23 ip_address: Option<String>,
24 },
25
26 AuthSuccess {
28 user_id: String,
29 ip_address: Option<String>,
30 risk_score: Option<f32>,
31 },
32
33 AuthFailure {
35 email: String,
36 reason: String,
37 ip_address: Option<String>,
38 },
39
40 TokenGenerated {
42 user_id: String,
43 token_type: String,
44 },
45
46 TokenValidated {
48 user_id: String,
49 },
50
51 TokenValidationFailed {
53 reason: String,
54 },
55
56 PasswordHashed,
58
59 PasswordVerified {
61 success: bool,
62 },
63
64 PasswordValidationFailed {
66 reason: String,
67 },
68
69 AccountStatusChecked {
71 user_id: String,
72 is_active: bool,
73 is_locked: bool,
74 },
75
76 TwoFactorRequired {
78 user_id: String,
79 },
80
81 NewDeviceDetected {
83 user_id: String,
84 },
85}
86
87impl AuditEvent {
88 pub fn description(&self) -> &'static str {
90 match self {
91 Self::AuthAttemptStarted { .. } => "Authentication attempt started",
92 Self::AuthSuccess { .. } => "Authentication successful",
93 Self::AuthFailure { .. } => "Authentication failed",
94 Self::TokenGenerated { .. } => "Token generated",
95 Self::TokenValidated { .. } => "Token validated",
96 Self::TokenValidationFailed { .. } => "Token validation failed",
97 Self::PasswordHashed => "Password hashed",
98 Self::PasswordVerified { .. } => "Password verified",
99 Self::PasswordValidationFailed { .. } => "Password validation failed",
100 Self::AccountStatusChecked { .. } => "Account status checked",
101 Self::TwoFactorRequired { .. } => "Two-factor authentication required",
102 Self::NewDeviceDetected { .. } => "New device detected",
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_audit_event_serialization() {
113 let event = AuditEvent::AuthSuccess {
114 user_id: "550e8400-e29b-41d4-a716-446655440000".to_string(),
115 ip_address: Some("192.168.1.1".to_string()),
116 risk_score: Some(0.1),
117 };
118 let json = serde_json::to_string(&event).unwrap();
119 assert!(json.contains("auth_success"));
120 assert!(json.contains("192.168.1.1"));
121 assert!(json.contains("0.1"));
122 }
123
124 #[test]
125 fn test_audit_event_failure_serialization() {
126 let event = AuditEvent::AuthFailure {
127 email: "user@example.com".to_string(),
128 reason: "Invalid credentials".to_string(),
129 ip_address: None,
130 };
131 let json = serde_json::to_string(&event).unwrap();
132 assert!(json.contains("auth_failure"));
133 assert!(json.contains("Invalid credentials"));
134 assert!(!json.contains("password"));
136 assert!(!json.contains("token"));
137 assert!(!json.contains("hash"));
138 }
139
140 #[test]
141 fn test_audit_event_no_sensitive_fields() {
142 let event = AuditEvent::PasswordVerified { success: false };
145 let json = serde_json::to_string(&event).unwrap();
146 assert!(!json.contains("password_value"));
147 assert!(!json.contains("secret"));
148 }
149
150 #[test]
151 fn test_audit_event_description() {
152 assert_eq!(
153 AuditEvent::AuthSuccess {
154 user_id: "123".to_string(),
155 ip_address: None,
156 risk_score: None,
157 }.description(),
158 "Authentication successful"
159 );
160 assert_eq!(
161 AuditEvent::AuthFailure {
162 email: "test@test.com".to_string(),
163 reason: "locked".to_string(),
164 ip_address: None,
165 }.description(),
166 "Authentication failed"
167 );
168 assert_eq!(
169 AuditEvent::TwoFactorRequired {
170 user_id: "123".to_string(),
171 }.description(),
172 "Two-factor authentication required"
173 );
174 }
175}