avl_auth/
error.rs

1//! Error types for AVL Auth
2
3use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, AuthError>;
6
7#[derive(Error, Debug)]
8pub enum AuthError {
9    #[error("Invalid credentials")]
10    InvalidCredentials,
11
12    #[error("User not found: {0}")]
13    UserNotFound(String),
14
15    #[error("User already exists: {0}")]
16    UserAlreadyExists(String),
17
18    #[error("Invalid token: {0}")]
19    InvalidToken(String),
20
21    #[error("Token expired")]
22    TokenExpired,
23
24    #[error("Session not found")]
25    SessionNotFound,
26
27    #[error("Session expired")]
28    SessionExpired,
29
30    #[error("Permission denied: {0}")]
31    PermissionDenied(String),
32
33    #[error("MFA required")]
34    MfaRequired,
35
36    #[error("Invalid MFA code")]
37    InvalidMfaCode,
38
39    #[error("Invalid password: {0}")]
40    InvalidPassword(String),
41
42    #[error("Rate limit exceeded")]
43    RateLimitExceeded,
44
45    #[error("Suspicious activity detected: {0}")]
46    SuspiciousActivity(String),
47
48    #[error("Account locked: {0}")]
49    AccountLocked(String),
50
51    #[error("Invalid OAuth2 state")]
52    InvalidOAuth2State,
53
54    #[error("OAuth2 provider error: {0}")]
55    OAuth2Error(String),
56
57    #[error("Invalid API key")]
58    InvalidApiKey,
59
60    #[error("API key expired")]
61    ApiKeyExpired,
62
63    #[error("Database error: {0}")]
64    DatabaseError(String),
65
66    #[error("Cryptographic error: {0}")]
67    CryptoError(String),
68
69    #[error("Configuration error: {0}")]
70    ConfigError(String),
71
72    #[error("Internal error: {0}")]
73    Internal(String),
74}
75
76impl AuthError {
77    pub fn is_retriable(&self) -> bool {
78        matches!(
79            self,
80            AuthError::DatabaseError(_) | AuthError::Internal(_)
81        )
82    }
83
84    pub fn status_code(&self) -> u16 {
85        match self {
86            AuthError::InvalidCredentials => 401,
87            AuthError::UserNotFound(_) => 404,
88            AuthError::UserAlreadyExists(_) => 409,
89            AuthError::InvalidToken(_) => 401,
90            AuthError::TokenExpired => 401,
91            AuthError::SessionNotFound => 404,
92            AuthError::SessionExpired => 401,
93            AuthError::PermissionDenied(_) => 403,
94            AuthError::MfaRequired => 401,
95            AuthError::InvalidMfaCode => 401,
96            AuthError::InvalidPassword(_) => 400,
97            AuthError::RateLimitExceeded => 429,
98            AuthError::SuspiciousActivity(_) => 403,
99            AuthError::AccountLocked(_) => 423,
100            AuthError::InvalidOAuth2State => 400,
101            AuthError::OAuth2Error(_) => 502,
102            AuthError::InvalidApiKey => 401,
103            AuthError::ApiKeyExpired => 401,
104            AuthError::DatabaseError(_) => 500,
105            AuthError::CryptoError(_) => 500,
106            AuthError::ConfigError(_) => 500,
107            AuthError::Internal(_) => 500,
108        }
109    }
110}
111
112impl From<jsonwebtoken::errors::Error> for AuthError {
113    fn from(err: jsonwebtoken::errors::Error) -> Self {
114        match err.kind() {
115            jsonwebtoken::errors::ErrorKind::ExpiredSignature => AuthError::TokenExpired,
116            _ => AuthError::InvalidToken(err.to_string()),
117        }
118    }
119}
120
121impl From<argon2::password_hash::Error> for AuthError {
122    fn from(err: argon2::password_hash::Error) -> Self {
123        AuthError::CryptoError(err.to_string())
124    }
125}