1use thiserror::Error;
2
3#[derive(Debug, Error)]
4pub enum AuthError {
5 #[error("Authentication required: missing or invalid token")]
6 Unauthenticated,
7
8 #[error("Forbidden: insufficient permissions")]
9 Forbidden,
10
11 #[error("Invalid token: {0}")]
12 InvalidToken(String),
13
14 #[error("Token validation failed: {0}")]
15 ValidationFailed(String),
16
17 #[error("JWKS fetch failed: {0}")]
18 JwksFetchFailed(String),
19
20 #[error("Issuer mismatch: expected {expected}, got {actual}")]
21 IssuerMismatch { expected: String, actual: String },
22
23 #[error("Audience mismatch: expected {expected:?}, got {actual:?}")]
24 AudienceMismatch {
25 expected: Vec<String>,
26 actual: Vec<String>,
27 },
28
29 #[error("Token expired")]
30 TokenExpired,
31
32 #[error("Internal error: {0}")]
33 Internal(String),
34}
35
36#[cfg(feature = "axum-ext")]
37impl axum::response::IntoResponse for AuthError {
38 fn into_response(self) -> axum::response::Response {
39 use axum::http::StatusCode;
40 use axum::response::Json;
41 use serde_json::json;
42
43 let (status, message) = match self {
44 AuthError::Unauthenticated | AuthError::InvalidToken(_) | AuthError::TokenExpired => {
45 (StatusCode::UNAUTHORIZED, self.to_string())
46 }
47 AuthError::Forbidden => (StatusCode::FORBIDDEN, self.to_string()),
48 _ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
49 };
50
51 let body = Json(json!({
52 "error": message,
53 "status": status.as_u16(),
54 }));
55
56 (status, body).into_response()
57 }
58}