Skip to main content

modkit_auth/
claims_error.rs

1use thiserror::Error;
2
3/// Errors that can occur during JWT claims validation and processing
4#[derive(Debug, Error)]
5pub enum ClaimsError {
6    #[error("Invalid signature or key")]
7    InvalidSignature,
8
9    #[error("Invalid issuer: expected one of {expected:?}, got {actual}")]
10    InvalidIssuer {
11        expected: Vec<String>,
12        actual: String,
13    },
14
15    #[error("Invalid audience: expected one of {expected:?}, got {actual:?}")]
16    InvalidAudience {
17        expected: Vec<String>,
18        actual: Vec<String>,
19    },
20
21    #[error("Token expired")]
22    Expired,
23
24    #[error("Token not yet valid (nbf check failed)")]
25    NotYetValid,
26
27    #[error("Malformed claims: {0}")]
28    Malformed(String),
29
30    #[error("Provider error: {0}")]
31    Provider(String),
32
33    #[error("Missing required claim: {0}")]
34    MissingClaim(String),
35
36    #[error("Invalid claim format: {field} - {reason}")]
37    InvalidClaimFormat { field: String, reason: String },
38
39    #[error("No matching plugin found for token")]
40    NoMatchingPlugin,
41
42    #[error("No key provider could validate this token")]
43    NoValidatingKey,
44
45    #[error("No matching key provider")]
46    NoMatchingProvider,
47
48    #[error("Unknown key ID after refresh")]
49    UnknownKidAfterRefresh,
50
51    #[error("Introspection denied")]
52    IntrospectionDenied,
53
54    #[error("Invalid configuration: {0}")]
55    ConfigError(String),
56
57    #[error("JWT decode failed: {0}")]
58    DecodeFailed(String),
59
60    #[error("JWKS fetch failed: {0}")]
61    JwksFetchFailed(String),
62
63    #[error("Unknown key ID: {0}")]
64    UnknownKeyId(String),
65}
66
67// Conversion from ClaimsError to AuthError for backward compatibility
68impl From<ClaimsError> for crate::errors::AuthError {
69    fn from(err: ClaimsError) -> Self {
70        match err {
71            ClaimsError::Expired => crate::errors::AuthError::TokenExpired,
72            ClaimsError::InvalidSignature => {
73                crate::errors::AuthError::InvalidToken("Invalid signature".into())
74            }
75            ClaimsError::InvalidIssuer { expected, actual } => {
76                crate::errors::AuthError::IssuerMismatch {
77                    expected: expected.join(", "),
78                    actual,
79                }
80            }
81            ClaimsError::InvalidAudience { expected, actual } => {
82                crate::errors::AuthError::AudienceMismatch { expected, actual }
83            }
84            ClaimsError::JwksFetchFailed(msg) => crate::errors::AuthError::JwksFetchFailed(msg),
85            other => crate::errors::AuthError::ValidationFailed(other.to_string()),
86        }
87    }
88}
89
90#[cfg(feature = "axum-ext")]
91impl axum::response::IntoResponse for ClaimsError {
92    fn into_response(self) -> axum::response::Response {
93        use axum::http::StatusCode;
94        use axum::response::Json;
95        use serde_json::json;
96
97        let (status, message) = match self {
98            // 401 Unauthorized - authentication failures
99            ClaimsError::Expired
100            | ClaimsError::NotYetValid
101            | ClaimsError::InvalidSignature
102            | ClaimsError::InvalidIssuer { .. }
103            | ClaimsError::InvalidAudience { .. }
104            | ClaimsError::Malformed(_)
105            | ClaimsError::MissingClaim(_)
106            | ClaimsError::InvalidClaimFormat { .. }
107            | ClaimsError::NoMatchingPlugin
108            | ClaimsError::NoValidatingKey
109            | ClaimsError::NoMatchingProvider
110            | ClaimsError::UnknownKidAfterRefresh
111            | ClaimsError::DecodeFailed(_)
112            | ClaimsError::UnknownKeyId(_) => (StatusCode::UNAUTHORIZED, self.to_string()),
113
114            // 403 Forbidden - introspection denied
115            ClaimsError::IntrospectionDenied => (StatusCode::FORBIDDEN, self.to_string()),
116
117            // 500 Internal Server Error - system/config errors
118            ClaimsError::ConfigError(_)
119            | ClaimsError::Provider(_)
120            | ClaimsError::JwksFetchFailed(_) => {
121                (StatusCode::INTERNAL_SERVER_ERROR, self.to_string())
122            }
123        };
124
125        let body = Json(json!({
126            "error": message,
127            "status": status.as_u16(),
128        }));
129
130        (status, body).into_response()
131    }
132}