use thiserror::Error;
pub type SecurityResult<T> = Result<T, SecurityError>;
#[derive(Error, Debug)]
pub enum SecurityError {
#[error("Input validation failed: {0}")]
ValidationError(String),
#[error("Rate limit exceeded: {0}")]
RateLimitExceeded(String),
#[error("Cryptographic operation failed: {0}")]
CryptoError(String),
#[error("Security policy violation: {0}")]
PolicyViolation(String),
#[error("Authentication failed: {0}")]
AuthenticationError(String),
#[error("Authorization failed: {0}")]
AuthorizationError(String),
#[error("Invalid session: {0}")]
InvalidSession(String),
#[error("Suspicious activity detected: {0}")]
SuspiciousActivity(String),
#[error("Security configuration error: {0}")]
ConfigError(String),
#[error("Audit log validation failed: {0}")]
AuditError(String),
#[error("SQL injection attempt detected")]
SqlInjectionAttempt,
#[error("XSS attempt detected")]
XssAttempt,
#[error("Path traversal attempt detected")]
PathTraversalAttempt,
#[error("Command injection attempt detected")]
CommandInjectionAttempt,
#[error("LDAP injection attempt detected")]
LdapInjectionAttempt,
#[error("Regular expression DoS attempt detected")]
RegexDosAttempt,
#[error("Request size exceeds maximum allowed: {0} bytes")]
RequestTooLarge(usize),
#[error("Invalid content type: {0}")]
InvalidContentType(String),
#[error("Password does not meet security requirements: {0}")]
WeakPassword(String),
#[error("Invalid or expired token")]
InvalidToken,
#[error("CSRF token mismatch")]
CsrfTokenMismatch,
#[error("Insecure protocol: {0}")]
InsecureProtocol(String),
#[error("Certificate validation failed: {0}")]
CertificateError(String),
#[error("Security error: {0}")]
General(String),
}
impl SecurityError {
pub fn severity(&self) -> Severity {
match self {
Self::SqlInjectionAttempt
| Self::XssAttempt
| Self::CommandInjectionAttempt
| Self::PathTraversalAttempt
| Self::SuspiciousActivity(_)
| Self::AuthenticationError(_)
| Self::AuthorizationError(_) => Severity::Critical,
Self::RateLimitExceeded(_)
| Self::PolicyViolation(_)
| Self::WeakPassword(_)
| Self::InvalidToken
| Self::CsrfTokenMismatch => Severity::High,
Self::ValidationError(_)
| Self::RequestTooLarge(_)
| Self::InvalidContentType(_)
| Self::InvalidSession(_) => Severity::Medium,
Self::ConfigError(_)
| Self::AuditError(_)
| Self::General(_) => Severity::Low,
Self::CryptoError(_)
| Self::CertificateError(_)
| Self::InsecureProtocol(_) => Severity::High,
Self::LdapInjectionAttempt | Self::RegexDosAttempt => Severity::Critical,
}
}
pub fn should_alert(&self) -> bool {
matches!(self.severity(), Severity::Critical | Severity::High)
}
pub fn public_message(&self) -> String {
match self {
Self::SqlInjectionAttempt
| Self::XssAttempt
| Self::CommandInjectionAttempt
| Self::PathTraversalAttempt
| Self::LdapInjectionAttempt
| Self::RegexDosAttempt
| Self::SuspiciousActivity(_) => {
"Request rejected due to security policy".to_string()
}
Self::RateLimitExceeded(_) => "Rate limit exceeded. Please try again later".to_string(),
Self::AuthenticationError(_) => "Authentication failed".to_string(),
Self::AuthorizationError(_) => "Access denied".to_string(),
Self::InvalidSession(_) => "Session expired. Please log in again".to_string(),
Self::WeakPassword(_) => {
"Password does not meet security requirements".to_string()
}
Self::RequestTooLarge(_) => "Request size too large".to_string(),
_ => self.to_string(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Severity {
Low,
Medium,
High,
Critical,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_severity() {
assert_eq!(
SecurityError::SqlInjectionAttempt.severity(),
Severity::Critical
);
assert_eq!(
SecurityError::RateLimitExceeded("test".to_string()).severity(),
Severity::High
);
assert_eq!(
SecurityError::ValidationError("test".to_string()).severity(),
Severity::Medium
);
}
#[test]
fn test_should_alert() {
assert!(SecurityError::SqlInjectionAttempt.should_alert());
assert!(SecurityError::WeakPassword("test".to_string()).should_alert());
assert!(!SecurityError::ValidationError("test".to_string()).should_alert());
}
#[test]
fn test_public_message() {
let err = SecurityError::SqlInjectionAttempt;
assert_eq!(
err.public_message(),
"Request rejected due to security policy"
);
let err = SecurityError::ValidationError("internal details".to_string());
assert!(err.public_message().contains("validation"));
}
}