modkit_auth/
claims_error.rs1use thiserror::Error;
2
3#[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
67impl 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 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 ClaimsError::IntrospectionDenied => (StatusCode::FORBIDDEN, self.to_string()),
116
117 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}