elif_auth/
error.rs

1//! Authentication and authorization error types
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6/// Authentication and authorization errors
7#[derive(Debug, Error, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub enum AuthError {
9    /// Invalid credentials provided
10    #[error("Invalid credentials")]
11    InvalidCredentials,
12
13    /// Token-related errors
14    #[error("Token error: {message}")]
15    TokenError { message: String },
16
17    /// Session-related errors  
18    #[error("Session error: {message}")]
19    SessionError { message: String },
20
21    /// User not found
22    #[error("User not found")]
23    UserNotFound,
24
25    /// User account is disabled
26    #[error("User account is disabled")]
27    UserDisabled,
28
29    /// Account is locked due to failed attempts
30    #[error("Account locked due to failed login attempts")]
31    AccountLocked,
32
33    /// Multi-factor authentication required
34    #[error("Multi-factor authentication required")]
35    MfaRequired,
36
37    /// Invalid MFA code
38    #[error("Invalid multi-factor authentication code")]
39    InvalidMfaCode,
40
41    /// Authorization/permission errors
42    #[error("Access denied: {message}")]
43    AccessDenied { message: String },
44
45    /// Role not found
46    #[error("Role not found: {role}")]
47    RoleNotFound { role: String },
48
49    /// Permission not found
50    #[error("Permission not found: {permission}")]
51    PermissionNotFound { permission: String },
52
53    /// Configuration errors
54    #[error("Authentication configuration error: {message}")]
55    ConfigurationError { message: String },
56
57    /// Cryptographic errors
58    #[error("Cryptographic error: {message}")]
59    CryptographicError { message: String },
60
61    /// Database errors
62    #[error("Database error during authentication: {message}")]
63    DatabaseError { message: String },
64
65    /// Generic authentication error
66    #[error("Authentication error: {message}")]
67    Generic { message: String },
68}
69
70impl AuthError {
71    /// Get the error code for API responses
72    pub fn error_code(&self) -> &'static str {
73        match self {
74            AuthError::InvalidCredentials => "INVALID_CREDENTIALS",
75            AuthError::TokenError { .. } => "TOKEN_ERROR",
76            AuthError::SessionError { .. } => "SESSION_ERROR",
77            AuthError::UserNotFound => "USER_NOT_FOUND",
78            AuthError::UserDisabled => "USER_DISABLED",
79            AuthError::AccountLocked => "ACCOUNT_LOCKED",
80            AuthError::MfaRequired => "MFA_REQUIRED",
81            AuthError::InvalidMfaCode => "INVALID_MFA_CODE",
82            AuthError::AccessDenied { .. } => "ACCESS_DENIED",
83            AuthError::RoleNotFound { .. } => "ROLE_NOT_FOUND",
84            AuthError::PermissionNotFound { .. } => "PERMISSION_NOT_FOUND",
85            AuthError::ConfigurationError { .. } => "CONFIGURATION_ERROR",
86            AuthError::CryptographicError { .. } => "CRYPTOGRAPHIC_ERROR",
87            AuthError::DatabaseError { .. } => "DATABASE_ERROR",
88            AuthError::Generic { .. } => "AUTHENTICATION_ERROR",
89        }
90    }
91
92    /// Get HTTP status code for the error
93    pub fn status_code(&self) -> u16 {
94        match self {
95            AuthError::InvalidCredentials => 401,
96            AuthError::TokenError { .. } => 401,
97            AuthError::SessionError { .. } => 401,
98            AuthError::UserNotFound => 401, // Don't reveal user existence
99            AuthError::UserDisabled => 401,
100            AuthError::AccountLocked => 429, // Too Many Requests
101            AuthError::MfaRequired => 202,   // Accepted, but MFA needed
102            AuthError::InvalidMfaCode => 401,
103            AuthError::AccessDenied { .. } => 403,
104            AuthError::RoleNotFound { .. } => 403,
105            AuthError::PermissionNotFound { .. } => 403,
106            AuthError::ConfigurationError { .. } => 500,
107            AuthError::CryptographicError { .. } => 500,
108            AuthError::DatabaseError { .. } => 500,
109            AuthError::Generic { .. } => 500,
110        }
111    }
112
113    /// Create a token error
114    pub fn token_error(message: impl Into<String>) -> Self {
115        Self::TokenError {
116            message: message.into(),
117        }
118    }
119
120    /// Create a session error
121    pub fn session_error(message: impl Into<String>) -> Self {
122        Self::SessionError {
123            message: message.into(),
124        }
125    }
126
127    /// Create an access denied error
128    pub fn access_denied(message: impl Into<String>) -> Self {
129        Self::AccessDenied {
130            message: message.into(),
131        }
132    }
133
134    /// Create a configuration error
135    pub fn config_error(message: impl Into<String>) -> Self {
136        Self::ConfigurationError {
137            message: message.into(),
138        }
139    }
140
141    /// Create a cryptographic error
142    pub fn crypto_error(message: impl Into<String>) -> Self {
143        Self::CryptographicError {
144            message: message.into(),
145        }
146    }
147
148    /// Create a database error
149    pub fn database_error(message: impl Into<String>) -> Self {
150        Self::DatabaseError {
151            message: message.into(),
152        }
153    }
154
155    /// Create a generic error
156    pub fn generic_error(message: impl Into<String>) -> Self {
157        Self::Generic {
158            message: message.into(),
159        }
160    }
161
162    /// Create an authentication failed error (alias for InvalidCredentials with message)
163    pub fn authentication_failed(message: impl Into<String>) -> Self {
164        Self::Generic {
165            message: format!("Authentication failed: {}", message.into()),
166        }
167    }
168
169    /// Create a configuration error (alias for config_error)
170    pub fn configuration_error(message: impl Into<String>) -> Self {
171        Self::config_error(message)
172    }
173
174    /// Create an insufficient permissions error (alias for access_denied)
175    pub fn insufficient_permissions(message: impl Into<String>) -> Self {
176        Self::access_denied(message)
177    }
178
179    /// Create an unauthorized error (alias for InvalidCredentials with message)
180    pub fn unauthorized(message: impl Into<String>) -> Self {
181        Self::Generic {
182            message: format!("Unauthorized: {}", message.into()),
183        }
184    }
185
186    /// Create an invalid credentials error with message
187    pub fn invalid_credentials(message: impl Into<String>) -> Self {
188        Self::Generic {
189            message: format!("Invalid credentials: {}", message.into()),
190        }
191    }
192
193    /// Create a not found error
194    pub fn not_found(message: impl Into<String>) -> Self {
195        Self::Generic {
196            message: format!("Not found: {}", message.into()),
197        }
198    }
199}
200
201// Conversion from common error types
202#[cfg(feature = "jwt")]
203impl From<jsonwebtoken::errors::Error> for AuthError {
204    fn from(err: jsonwebtoken::errors::Error) -> Self {
205        Self::token_error(err.to_string())
206    }
207}
208
209#[cfg(feature = "argon2")]
210impl From<argon2::Error> for AuthError {
211    fn from(err: argon2::Error) -> Self {
212        Self::crypto_error(err.to_string())
213    }
214}
215
216#[cfg(feature = "bcrypt")]
217impl From<bcrypt::BcryptError> for AuthError {
218    fn from(err: bcrypt::BcryptError) -> Self {
219        Self::crypto_error(err.to_string())
220    }
221}
222
223impl From<sqlx::Error> for AuthError {
224    fn from(err: sqlx::Error) -> Self {
225        Self::database_error(err.to_string())
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_error_codes() {
235        assert_eq!(
236            AuthError::InvalidCredentials.error_code(),
237            "INVALID_CREDENTIALS"
238        );
239        assert_eq!(AuthError::token_error("test").error_code(), "TOKEN_ERROR");
240        assert_eq!(
241            AuthError::access_denied("test").error_code(),
242            "ACCESS_DENIED"
243        );
244    }
245
246    #[test]
247    fn test_status_codes() {
248        assert_eq!(AuthError::InvalidCredentials.status_code(), 401);
249        assert_eq!(AuthError::access_denied("test").status_code(), 403);
250        assert_eq!(AuthError::AccountLocked.status_code(), 429);
251        assert_eq!(AuthError::MfaRequired.status_code(), 202);
252        assert_eq!(AuthError::config_error("test").status_code(), 500);
253    }
254
255    #[test]
256    fn test_error_creation_helpers() {
257        let token_err = AuthError::token_error("Invalid token");
258        assert_eq!(
259            token_err,
260            AuthError::TokenError {
261                message: "Invalid token".to_string()
262            }
263        );
264
265        let access_err = AuthError::access_denied("No permission");
266        assert_eq!(
267            access_err,
268            AuthError::AccessDenied {
269                message: "No permission".to_string()
270            }
271        );
272    }
273
274    #[test]
275    fn test_error_display() {
276        let err = AuthError::token_error("JWT expired");
277        assert_eq!(err.to_string(), "Token error: JWT expired");
278
279        let err = AuthError::access_denied("Insufficient privileges");
280        assert_eq!(err.to_string(), "Access denied: Insufficient privileges");
281    }
282}