use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "event_type", rename_all = "snake_case")]
pub enum AuditEvent {
AuthAttemptStarted {
email: String,
ip_address: Option<String>,
},
AuthSuccess {
user_id: String,
ip_address: Option<String>,
risk_score: Option<f32>,
},
AuthFailure {
email: String,
reason: String,
ip_address: Option<String>,
},
TokenGenerated {
user_id: String,
token_type: String,
},
TokenValidated {
user_id: String,
},
TokenValidationFailed {
reason: String,
},
PasswordHashed,
PasswordVerified {
success: bool,
},
PasswordValidationFailed {
reason: String,
},
AccountStatusChecked {
user_id: String,
is_active: bool,
is_locked: bool,
},
TwoFactorRequired {
user_id: String,
},
NewDeviceDetected {
user_id: String,
},
}
impl AuditEvent {
pub fn description(&self) -> &'static str {
match self {
Self::AuthAttemptStarted { .. } => "Authentication attempt started",
Self::AuthSuccess { .. } => "Authentication successful",
Self::AuthFailure { .. } => "Authentication failed",
Self::TokenGenerated { .. } => "Token generated",
Self::TokenValidated { .. } => "Token validated",
Self::TokenValidationFailed { .. } => "Token validation failed",
Self::PasswordHashed => "Password hashed",
Self::PasswordVerified { .. } => "Password verified",
Self::PasswordValidationFailed { .. } => "Password validation failed",
Self::AccountStatusChecked { .. } => "Account status checked",
Self::TwoFactorRequired { .. } => "Two-factor authentication required",
Self::NewDeviceDetected { .. } => "New device detected",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_audit_event_serialization() {
let event = AuditEvent::AuthSuccess {
user_id: "550e8400-e29b-41d4-a716-446655440000".to_string(),
ip_address: Some("192.168.1.1".to_string()),
risk_score: Some(0.1),
};
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("auth_success"));
assert!(json.contains("192.168.1.1"));
assert!(json.contains("0.1"));
}
#[test]
fn test_audit_event_failure_serialization() {
let event = AuditEvent::AuthFailure {
email: "user@example.com".to_string(),
reason: "Invalid credentials".to_string(),
ip_address: None,
};
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("auth_failure"));
assert!(json.contains("Invalid credentials"));
assert!(!json.contains("password"));
assert!(!json.contains("token"));
assert!(!json.contains("hash"));
}
#[test]
fn test_audit_event_no_sensitive_fields() {
let event = AuditEvent::PasswordVerified { success: false };
let json = serde_json::to_string(&event).unwrap();
assert!(!json.contains("password_value"));
assert!(!json.contains("secret"));
}
#[test]
fn test_audit_event_description() {
assert_eq!(
AuditEvent::AuthSuccess {
user_id: "123".to_string(),
ip_address: None,
risk_score: None,
}.description(),
"Authentication successful"
);
assert_eq!(
AuditEvent::AuthFailure {
email: "test@test.com".to_string(),
reason: "locked".to_string(),
ip_address: None,
}.description(),
"Authentication failed"
);
assert_eq!(
AuditEvent::TwoFactorRequired {
user_id: "123".to_string(),
}.description(),
"Two-factor authentication required"
);
}
}