1use std::fmt;
2use anyhow::{Error as AnyhowError, Result as AnyhowResult};
3
4#[derive(Debug)]
6pub enum JwtError {
7 ExpiredToken {
9 exp: Option<u64>,
11 current_time: Option<u64>,
13 },
14 TokenNotYetValid {
16 nbf: Option<u64>,
18 current_time: Option<u64>,
20 },
21 InvalidSignature,
23 InvalidClaim {
25 claim: String,
27 reason: String,
29 value: Option<String>,
31 },
32 InvalidIssuer {
34 expected: String,
36 actual: String,
38 },
39 InvalidClientId {
41 expected: Vec<String>,
43 actual: String,
45 },
46 InvalidTokenUse {
48 expected: String,
50 actual: String,
52 },
53 KeyNotFound(String),
55 JwksFetchError {
57 url: Option<String>,
59 error: String,
61 },
62 ParseError {
64 part: Option<String>,
66 error: String,
68 },
69 ConfigurationError {
71 parameter: Option<String>,
73 error: String,
75 },
76 MissingToken,
78 UnsupportedTokenType {
80 token_type: String,
82 },
83 InvalidToken(String),
85 UnexpectedError(String),
87}
88
89impl fmt::Display for JwtError {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 JwtError::ExpiredToken { exp, current_time } => {
93 if let (Some(exp), Some(current_time)) = (exp, current_time) {
94 write!(f, "Token has expired at {} (current time: {})", exp, current_time)
95 } else {
96 write!(f, "Token has expired")
97 }
98 },
99 JwtError::TokenNotYetValid { nbf, current_time } => {
100 if let (Some(nbf), Some(current_time)) = (nbf, current_time) {
101 write!(f, "Token is not yet valid until {} (current time: {})", nbf, current_time)
102 } else {
103 write!(f, "Token is not yet valid")
104 }
105 },
106 JwtError::InvalidSignature => write!(f, "Token signature is invalid"),
107 JwtError::InvalidClaim { claim, reason, value } => {
108 if let Some(value) = value {
109 write!(f, "Invalid claim '{}': {} (value: '{}')", claim, reason, value)
110 } else {
111 write!(f, "Invalid claim '{}': {}", claim, reason)
112 }
113 },
114 JwtError::InvalidIssuer { expected, actual } => {
115 write!(f, "Invalid issuer: expected '{}', got '{}'", expected, actual)
116 },
117 JwtError::InvalidClientId { expected, actual } => {
118 write!(f, "Invalid client ID: expected one of {:?}, got '{}'", expected, actual)
119 },
120 JwtError::InvalidTokenUse { expected, actual } => {
121 write!(f, "Invalid token use: expected '{}', got '{}'", expected, actual)
122 },
123 JwtError::KeyNotFound(kid) => write!(f, "JWK not found for key ID: {}", kid),
124 JwtError::JwksFetchError { url, error } => {
125 if let Some(url) = url {
126 write!(f, "Failed to fetch JWKs from {}: {}", url, error)
127 } else {
128 write!(f, "Failed to fetch JWKs: {}", error)
129 }
130 },
131 JwtError::ParseError { part, error } => {
132 if let Some(part) = part {
133 write!(f, "Failed to parse token {}: {}", part, error)
134 } else {
135 write!(f, "Failed to parse token: {}", error)
136 }
137 },
138 JwtError::ConfigurationError { parameter, error } => {
139 if let Some(parameter) = parameter {
140 write!(f, "Configuration error for '{}': {}", parameter, error)
141 } else {
142 write!(f, "Configuration error: {}", error)
143 }
144 },
145 JwtError::MissingToken => write!(f, "Token is missing"),
146 JwtError::UnsupportedTokenType { token_type } => {
147 write!(f, "Unsupported token type: {}", token_type)
148 },
149 JwtError::InvalidToken(err) => write!(f, "Invalid token: {}", err),
150 JwtError::UnexpectedError(err) => write!(f, "Unexpected error: {}", err),
151 }
152 }
153}
154
155impl std::error::Error for JwtError {}
156
157impl From<jsonwebtoken::errors::Error> for JwtError {
158 fn from(err: jsonwebtoken::errors::Error) -> Self {
159 use jsonwebtoken::errors::ErrorKind;
160 match err.kind() {
161 ErrorKind::ExpiredSignature => JwtError::ExpiredToken {
162 exp: None,
163 current_time: None,
164 },
165 ErrorKind::InvalidSignature => JwtError::InvalidSignature,
166 ErrorKind::InvalidToken => JwtError::InvalidToken("Token format is invalid".to_string()),
167 ErrorKind::InvalidIssuer => JwtError::InvalidIssuer {
168 expected: "unknown".to_string(),
169 actual: "unknown".to_string(),
170 },
171 ErrorKind::InvalidAudience => JwtError::InvalidClaim {
172 claim: "aud".to_string(),
173 reason: "Audience is invalid".to_string(),
174 value: None,
175 },
176 ErrorKind::InvalidSubject => JwtError::InvalidClaim {
177 claim: "sub".to_string(),
178 reason: "Subject is invalid".to_string(),
179 value: None,
180 },
181 ErrorKind::ImmatureSignature => JwtError::TokenNotYetValid {
182 nbf: None,
183 current_time: None,
184 },
185 ErrorKind::InvalidAlgorithm => JwtError::InvalidClaim {
186 claim: "alg".to_string(),
187 reason: "Algorithm is invalid".to_string(),
188 value: None,
189 },
190 _ => JwtError::ParseError {
191 part: None,
192 error: format!("{}", err),
193 },
194 }
195 }
196}
197
198impl From<reqwest::Error> for JwtError {
199 fn from(err: reqwest::Error) -> Self {
200 let url = err.url().map(|u| u.to_string());
201 JwtError::JwksFetchError {
202 url,
203 error: format!("{}", err),
204 }
205 }
206}
207
208impl From<serde_json::Error> for JwtError {
209 fn from(err: serde_json::Error) -> Self {
210 JwtError::ParseError {
211 part: Some("payload".to_string()),
212 error: format!("JSON error: {}", err),
213 }
214 }
215}
216
217impl From<AnyhowError> for JwtError {
218 fn from(err: AnyhowError) -> Self {
219 JwtError::UnexpectedError(format!("{}", err))
220 }
221}
222
223impl From<std::io::Error> for JwtError {
224 fn from(err: std::io::Error) -> Self {
225 JwtError::UnexpectedError(format!("IO error: {}", err))
226 }
227}
228
229impl From<base64::DecodeError> for JwtError {
230 fn from(err: base64::DecodeError) -> Self {
231 JwtError::ParseError {
232 part: Some("base64".to_string()),
233 error: format!("Base64 decode error: {}", err),
234 }
235 }
236}
237
238impl From<std::str::Utf8Error> for JwtError {
239 fn from(err: std::str::Utf8Error) -> Self {
240 JwtError::ParseError {
241 part: Some("utf8".to_string()),
242 error: format!("UTF-8 decode error: {}", err),
243 }
244 }
245}
246
247#[derive(Debug)]
249pub enum PublicJwtError {
250 InvalidToken,
252}
253
254impl fmt::Display for PublicJwtError {
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 match self {
257 PublicJwtError::InvalidToken => write!(f, "Invalid authentication token"),
258 }
259 }
260}
261
262impl std::error::Error for PublicJwtError {}
263
264impl From<JwtError> for PublicJwtError {
265 fn from(_err: JwtError) -> Self {
266 PublicJwtError::InvalidToken
268 }
269}
270
271#[derive(Debug)]
273pub struct ErrorLogger {
274 verbosity: ErrorVerbosity,
275}
276
277#[derive(Debug, Clone, Copy)]
279pub enum ErrorVerbosity {
280 Minimal,
282 Standard,
284 Detailed,
286}
287
288impl Default for ErrorVerbosity {
289 fn default() -> Self {
290 Self::Standard
291 }
292}
293
294impl ErrorLogger {
295 pub fn new(verbosity: ErrorVerbosity) -> Self {
297 Self { verbosity }
298 }
299
300 pub fn default() -> Self {
302 Self {
303 verbosity: ErrorVerbosity::default(),
304 }
305 }
306
307 pub fn log(&self, error: &JwtError) {
309 match self.verbosity {
310 ErrorVerbosity::Minimal => {
311 let error_type = match error {
313 JwtError::ExpiredToken { .. } => "ExpiredToken",
314 JwtError::TokenNotYetValid { .. } => "TokenNotYetValid",
315 JwtError::InvalidSignature => "InvalidSignature",
316 JwtError::InvalidClaim { .. } => "InvalidClaim",
317 JwtError::InvalidIssuer { .. } => "InvalidIssuer",
318 JwtError::InvalidClientId { .. } => "InvalidClientId",
319 JwtError::InvalidTokenUse { .. } => "InvalidTokenUse",
320 JwtError::KeyNotFound(_) => "KeyNotFound",
321 JwtError::JwksFetchError { .. } => "JwksFetchError",
322 JwtError::ParseError { .. } => "ParseError",
323 JwtError::ConfigurationError { .. } => "ConfigurationError",
324 JwtError::MissingToken => "MissingToken",
325 JwtError::UnsupportedTokenType { .. } => "UnsupportedTokenType",
326 JwtError::InvalidToken(_) => "InvalidToken",
327 JwtError::UnexpectedError(_) => "UnexpectedError",
328 };
329 tracing::error!("JWT verification error: {}", error_type);
330 }
331 ErrorVerbosity::Standard => {
332 match error {
334 JwtError::ExpiredToken { exp, current_time } => {
335 if let (Some(exp), Some(current_time)) = (exp, current_time) {
336 tracing::error!("JWT verification error: Token expired at {} (current time: {})", exp, current_time);
337 } else {
338 tracing::error!("JWT verification error: Token has expired");
339 }
340 }
341 JwtError::TokenNotYetValid { nbf, current_time } => {
342 if let (Some(nbf), Some(current_time)) = (nbf, current_time) {
343 tracing::error!("JWT verification error: Token not valid until {} (current time: {})", nbf, current_time);
344 } else {
345 tracing::error!("JWT verification error: Token is not yet valid");
346 }
347 }
348 JwtError::InvalidSignature => {
349 tracing::error!("JWT verification error: Invalid signature");
350 }
351 JwtError::InvalidClaim { claim, reason, .. } => {
352 tracing::error!("JWT verification error: Invalid claim '{}': {}", claim, reason);
353 }
354 JwtError::InvalidIssuer { expected, actual } => {
355 tracing::error!("JWT verification error: Invalid issuer, expected '{}', got '{}'", expected, actual);
356 }
357 JwtError::InvalidClientId { expected, actual } => {
358 tracing::error!("JWT verification error: Invalid client ID, expected one of {:?}, got '{}'", expected, actual);
359 }
360 JwtError::InvalidTokenUse { expected, actual } => {
361 tracing::error!(
362 "JWT verification error: Invalid token use, expected '{}', got '{}'",
363 expected, actual
364 );
365 }
366 JwtError::KeyNotFound(kid) => {
367 tracing::error!("JWT verification error: Key not found for ID '{}'", kid);
368 }
369 JwtError::JwksFetchError { url, error } => {
370 if let Some(url) = url {
371 tracing::error!("JWT verification error: Failed to fetch JWKs from {}: {}", url, error);
372 } else {
373 tracing::error!("JWT verification error: Failed to fetch JWKs: {}", error);
374 }
375 }
376 JwtError::ParseError { part, error } => {
377 if let Some(part) = part {
378 tracing::error!("JWT verification error: Failed to parse token {}: {}", part, error);
379 } else {
380 tracing::error!("JWT verification error: Failed to parse token: {}", error);
381 }
382 }
383 JwtError::ConfigurationError { parameter, error } => {
384 if let Some(parameter) = parameter {
385 tracing::error!("JWT verification error: Configuration error for '{}': {}", parameter, error);
386 } else {
387 tracing::error!("JWT verification error: Configuration error: {}", error);
388 }
389 }
390 JwtError::MissingToken => {
391 tracing::error!("JWT verification error: Token is missing");
392 }
393 JwtError::UnsupportedTokenType { token_type } => {
394 tracing::error!("JWT verification error: Unsupported token type: {}", token_type);
395 }
396 JwtError::InvalidToken(err) => {
397 tracing::error!("JWT verification error: Invalid token: {}", err);
398 }
399 JwtError::UnexpectedError(err) => {
400 tracing::error!("JWT verification error: Unexpected error: {}", err);
401 }
402 }
403 }
404 ErrorVerbosity::Detailed => {
405 tracing::error!("JWT verification error: {:?}", error);
408
409 match error {
411 JwtError::ExpiredToken { exp, current_time } => {
412 tracing::debug!("Token expiration details - exp: {:?}, current_time: {:?}", exp, current_time);
413 }
414 JwtError::InvalidClaim { claim, reason, value } => {
415 tracing::debug!("Invalid claim details - claim: {}, reason: {}, value: {:?}", claim, reason, value);
416 }
417 JwtError::JwksFetchError { url, error } => {
418 tracing::debug!("JWK fetch error details - url: {:?}, error: {}", url, error);
419 }
420 _ => {}
421 }
422 }
423 }
424 }
425
426 pub fn to_public_error(&self, error: JwtError) -> PublicJwtError {
428 self.log(&error);
430
431 PublicJwtError::InvalidToken
433 }
434
435 pub fn sanitize_error_message(&self, _error: &JwtError) -> String {
437 "Invalid authentication token".to_string()
439 }
440
441 pub fn status_code_for_error(&self, error: &JwtError) -> u16 {
443 match error {
444 JwtError::KeyNotFound(_) => 500, JwtError::JwksFetchError { .. } => 500, JwtError::ConfigurationError { .. } => 500, JwtError::UnexpectedError(_) => 500, _ => 401, }
450 }
451}
452
453pub type Result<T> = AnyhowResult<T>;