1use 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}