1use totp_rs::SecretParseError;
4
5pub type Result<T> = std::result::Result<T, SecurityError>;
7
8impl std::fmt::Display for SecurityError {
9 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10 match self {
11 SecurityError::Configuration(msg) => write!(f, "Configuration error: {}", msg),
12 SecurityError::Authentication(msg) => write!(f, "Authentication failed: {}", msg),
13 SecurityError::Authorization(msg) => write!(f, "Authorization failed: {}", msg),
14 SecurityError::Jwt(e) => write!(f, "JWT error: {}", e),
15 SecurityError::OAuth2(msg) => write!(f, "OAuth2 error: {}", msg),
16 SecurityError::Mfa(msg) => write!(f, "MFA error: {}", msg),
17 SecurityError::Password(msg) => write!(f, "Password hashing error: {}", msg),
18 SecurityError::Session(msg) => write!(f, "Session error: {}", msg),
19 SecurityError::Http(e) => write!(f, "HTTP client error: {}", e),
20 SecurityError::Json(e) => write!(f, "JSON parsing error: {}", e),
21 SecurityError::Url(e) => write!(f, "URL parsing error: {}", e),
22 SecurityError::Io(e) => write!(f, "IO error: {}", e),
23 SecurityError::Utf8(e) => write!(f, "UTF-8 error: {}", e),
24 SecurityError::Time(msg) => write!(f, "Time error: {}", msg),
25 SecurityError::Crypto(msg) => write!(f, "Cryptography error: {}", msg),
26 SecurityError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
27 SecurityError::TokenExpired => write!(f, "Token expired"),
28 SecurityError::TokenInvalid => write!(f, "Token invalid"),
29 SecurityError::UserNotFound => write!(f, "User not found"),
30 SecurityError::UserExists => write!(f, "User already exists"),
31 SecurityError::RateLimitExceeded => write!(f, "Rate limit exceeded"),
32 SecurityError::MfaRequired => write!(f, "MFA required"),
33 SecurityError::MfaSetupRequired => write!(f, "MFA setup required"),
34 SecurityError::AccountLocked => write!(f, "Account locked"),
35 SecurityError::AccountDisabled => write!(f, "Account disabled"),
36 SecurityError::InvalidCredentials => write!(f, "Invalid credentials"),
37 SecurityError::InsufficientPermissions => write!(f, "Insufficient permissions"),
38 SecurityError::ProviderNotSupported => write!(f, "Provider not supported"),
39 SecurityError::StateMismatch => write!(f, "State mismatch"),
40 SecurityError::CsrfTokenInvalid => write!(f, "CSRF token invalid"),
41 SecurityError::SessionExpired => write!(f, "Session expired"),
42 SecurityError::SessionInvalid => write!(f, "Session invalid"),
43 SecurityError::Database(msg) => write!(f, "Database error: {}", msg),
44 SecurityError::Cache(msg) => write!(f, "Cache error: {}", msg),
45 SecurityError::ExternalService(msg) => write!(f, "External service error: {}", msg),
46 }
47 }
48}
49
50impl std::error::Error for SecurityError {}
51
52#[derive(Debug)]
54pub enum SecurityError {
55 Configuration(String),
56 Authentication(String),
57 Authorization(String),
58 Jwt(jsonwebtoken::errors::Error),
59 OAuth2(String),
60 Mfa(String),
61 Password(String),
62 Session(String),
63 Http(reqwest::Error),
64 Json(serde_json::Error),
65 Url(url::ParseError),
66 Io(std::io::Error),
67 Utf8(std::string::FromUtf8Error),
68 Time(String),
69 Crypto(String),
70 InvalidInput(String),
71 TokenExpired,
72 TokenInvalid,
73 UserNotFound,
74 UserExists,
75 RateLimitExceeded,
76 MfaRequired,
77 MfaSetupRequired,
78 AccountLocked,
79 AccountDisabled,
80 InvalidCredentials,
81 InsufficientPermissions,
82 ProviderNotSupported,
83 StateMismatch,
84 CsrfTokenInvalid,
85 SessionExpired,
86 SessionInvalid,
87 Database(String),
88 Cache(String),
89 ExternalService(String),
90}
91
92impl SecurityError {
93 pub fn is_retryable(&self) -> bool {
95 matches!(
96 self,
97 SecurityError::Http(_)
98 | SecurityError::Io(_)
99 | SecurityError::ExternalService(_)
100 | SecurityError::Database(_)
101 | SecurityError::Cache(_)
102 )
103 }
104
105 pub fn is_client_error(&self) -> bool {
107 matches!(
108 self,
109 SecurityError::Authentication(_)
110 | SecurityError::Authorization(_)
111 | SecurityError::InvalidInput(_)
112 | SecurityError::TokenExpired
113 | SecurityError::TokenInvalid
114 | SecurityError::UserNotFound
115 | SecurityError::InvalidCredentials
116 | SecurityError::InsufficientPermissions
117 | SecurityError::CsrfTokenInvalid
118 | SecurityError::StateMismatch
119 | SecurityError::RateLimitExceeded
120 )
121 }
122
123 pub fn is_server_error(&self) -> bool {
125 matches!(
126 self,
127 SecurityError::Configuration(_)
128 | SecurityError::Jwt(_)
129 | SecurityError::OAuth2(_)
130 | SecurityError::Mfa(_)
131 | SecurityError::Password(_)
132 | SecurityError::Session(_)
133 | SecurityError::Json(_)
134 | SecurityError::Url(_)
135 | SecurityError::Time(_)
136 | SecurityError::Crypto(_)
137 | SecurityError::Database(_)
138 | SecurityError::Cache(_)
139 | SecurityError::ExternalService(_)
140 )
141 }
142
143 pub fn http_status_code(&self) -> u16 {
145 match self {
146 SecurityError::Authentication(_) => 401,
148 SecurityError::Authorization(_) => 403,
149 SecurityError::InvalidInput(_) => 400,
150 SecurityError::TokenExpired => 401,
151 SecurityError::TokenInvalid => 401,
152 SecurityError::UserNotFound => 404,
153 SecurityError::InvalidCredentials => 401,
154 SecurityError::InsufficientPermissions => 403,
155 SecurityError::CsrfTokenInvalid => 403,
156 SecurityError::StateMismatch => 400,
157 SecurityError::RateLimitExceeded => 429,
158 SecurityError::MfaRequired => 401,
159 SecurityError::MfaSetupRequired => 401,
160 SecurityError::AccountLocked => 423,
161 SecurityError::AccountDisabled => 401,
162
163 _ => 500,
165 }
166 }
167
168 pub fn user_message(&self) -> &'static str {
170 match self {
171 SecurityError::Authentication(_) => "Authentication failed. Please check your credentials.",
172 SecurityError::Authorization(_) => "You don't have permission to access this resource.",
173 SecurityError::InvalidInput(_) => "Invalid input provided.",
174 SecurityError::TokenExpired => "Your session has expired. Please log in again.",
175 SecurityError::TokenInvalid => "Invalid authentication token.",
176 SecurityError::UserNotFound => "User not found.",
177 SecurityError::InvalidCredentials => "Invalid username or password.",
178 SecurityError::InsufficientPermissions => "Insufficient permissions for this action.",
179 SecurityError::CsrfTokenInvalid => "Security token is invalid.",
180 SecurityError::StateMismatch => "Request state mismatch. Please try again.",
181 SecurityError::RateLimitExceeded => "Too many requests. Please try again later.",
182 SecurityError::MfaRequired => "Multi-factor authentication is required.",
183 SecurityError::MfaSetupRequired => "Multi-factor authentication setup is required.",
184 SecurityError::AccountLocked => "Account is locked. Please contact support.",
185 SecurityError::AccountDisabled => "Account is disabled. Please contact support.",
186 _ => "An internal error occurred. Please try again later.",
187 }
188 }
189}
190
191impl From<anyhow::Error> for SecurityError {
193 fn from(err: anyhow::Error) -> Self {
194 SecurityError::Configuration(err.to_string())
195 }
196}
197
198impl From<chrono::ParseError> for SecurityError {
200 fn from(err: chrono::ParseError) -> Self {
201 SecurityError::Time(err.to_string())
202 }
203}
204
205impl From<SecretParseError> for SecurityError {
207 fn from(err: SecretParseError) -> Self {
208 SecurityError::Mfa(format!("TOTP secret parsing failed: {}", err))
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_error_classification() {
218 assert!(SecurityError::InvalidCredentials.is_client_error());
219 assert!(!SecurityError::InvalidCredentials.is_server_error());
220 assert!(!SecurityError::InvalidCredentials.is_retryable());
221
222 assert!(SecurityError::Jwt(jsonwebtoken::errors::Error::from(
223 jsonwebtoken::errors::ErrorKind::InvalidToken
224 )).is_server_error());
225 assert!(!SecurityError::Jwt(jsonwebtoken::errors::Error::from(
226 jsonwebtoken::errors::ErrorKind::InvalidToken
227 )).is_client_error());
228
229 }
233
234 #[test]
235 fn test_http_status_codes() {
236 assert_eq!(SecurityError::InvalidCredentials.http_status_code(), 401);
237 assert_eq!(SecurityError::Authorization("test".to_string()).http_status_code(), 403);
238 assert_eq!(SecurityError::InvalidInput("test".to_string()).http_status_code(), 400);
239 assert_eq!(SecurityError::Configuration("test".to_string()).http_status_code(), 500);
240 }
241
242 #[test]
243 fn test_user_messages() {
244 assert_eq!(
245 SecurityError::InvalidCredentials.user_message(),
246 "Invalid username or password."
247 );
248 assert_eq!(
249 SecurityError::TokenExpired.user_message(),
250 "Your session has expired. Please log in again."
251 );
252 }
253}