1use std::path::PathBuf;
2
3#[cfg(feature = "http-axum")]
4use axum::http::{HeaderValue, StatusCode, header};
5#[cfg(feature = "http-axum")]
6use axum::response::{IntoResponse, Response};
7use thiserror::Error;
8
9#[cfg(feature = "http-axum")]
10#[derive(Clone, Copy, Debug)]
11pub struct AuthErrorKind(pub &'static str);
12
13#[derive(Debug, Error)]
14pub enum AuthError {
15 #[error("{0}")]
16 Config(String),
17
18 #[error("{0}")]
19 Storage(String),
20
21 #[error("{0}")]
22 InvalidGrant(String),
23
24 #[error("{0}")]
25 AuthFailed(String),
26
27 #[error("{0}")]
28 Validation(String),
29
30 #[error("{0}")]
31 Network(String),
32
33 #[error("{0}")]
34 Server(String),
35
36 #[error("{0}")]
37 Decode(String),
38
39 #[error("{message}")]
40 RateLimited {
41 message: String,
42 retry_after_ms: u64,
43 },
44
45 #[error("invalid access token")]
46 InvalidAccessToken,
47
48 #[error("path `{path}` has insecure permissions")]
49 InsecurePermissions { path: PathBuf },
50}
51
52impl AuthError {
53 pub const fn kind(&self) -> &'static str {
54 match self {
55 Self::Config(_) | Self::Storage(_) | Self::InsecurePermissions { .. } => {
56 "internal_error"
57 }
58 Self::InvalidGrant(_) => "invalid_grant",
59 Self::AuthFailed(_) | Self::InvalidAccessToken => "auth_failed",
60 Self::Validation(_) => "validation_failed",
61 Self::Network(_) => "network_error",
62 Self::Server(_) => "server_error",
63 Self::Decode(_) => "decode_error",
64 Self::RateLimited { .. } => "rate_limited",
65 }
66 }
67
68 #[cfg(feature = "http-axum")]
69 const fn status(&self) -> StatusCode {
70 match self {
71 Self::InvalidGrant(_) => StatusCode::BAD_REQUEST,
72 Self::AuthFailed(_) | Self::InvalidAccessToken => StatusCode::UNAUTHORIZED,
73 Self::Validation(_) => StatusCode::UNPROCESSABLE_ENTITY,
74 Self::Network(_) | Self::Server(_) => StatusCode::BAD_GATEWAY,
75 Self::Decode(_) => StatusCode::INTERNAL_SERVER_ERROR,
76 Self::RateLimited { .. } => StatusCode::TOO_MANY_REQUESTS,
77 Self::Config(_) | Self::Storage(_) | Self::InsecurePermissions { .. } => {
78 StatusCode::INTERNAL_SERVER_ERROR
79 }
80 }
81 }
82}
83
84#[cfg(feature = "http-axum")]
85impl IntoResponse for AuthError {
86 fn into_response(self) -> Response {
87 let status = self.status();
88 let body = axum::Json(serde_json::json!({
89 "kind": self.kind(),
90 "message": self.to_string(),
91 }));
92 let mut response = (status, body).into_response();
93 response.extensions_mut().insert(AuthErrorKind(self.kind()));
94 response
95 .headers_mut()
96 .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
97 response
98 .headers_mut()
99 .insert(header::PRAGMA, HeaderValue::from_static("no-cache"));
100 if let Self::RateLimited { retry_after_ms, .. } = self
101 && let Ok(value) = HeaderValue::from_str(&(retry_after_ms / 1_000).max(1).to_string())
102 {
103 response.headers_mut().insert(header::RETRY_AFTER, value);
104 }
105 response
106 }
107}