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 InvalidScope(String),
26
27 #[error("{0}")]
28 AuthFailed(String),
29
30 #[error("{0}")]
31 Validation(String),
32
33 #[error("{0}")]
34 Network(String),
35
36 #[error("{0}")]
37 Server(String),
38
39 #[error("{0}")]
40 Decode(String),
41
42 #[error("{message}")]
43 RateLimited {
44 message: String,
45 retry_after_ms: u64,
46 },
47
48 #[error("invalid access token")]
49 InvalidAccessToken,
50
51 #[error("path `{path}` has insecure permissions")]
52 InsecurePermissions { path: PathBuf },
53}
54
55impl AuthError {
56 pub const fn kind(&self) -> &'static str {
57 match self {
58 Self::Config(_) | Self::Storage(_) | Self::InsecurePermissions { .. } => {
59 "internal_error"
60 }
61 Self::InvalidGrant(_) => "invalid_grant",
62 Self::InvalidScope(_) => "invalid_scope",
63 Self::AuthFailed(_) | Self::InvalidAccessToken => "auth_failed",
64 Self::Validation(_) => "validation_failed",
65 Self::Network(_) => "network_error",
66 Self::Server(_) => "server_error",
67 Self::Decode(_) => "decode_error",
68 Self::RateLimited { .. } => "rate_limited",
69 }
70 }
71
72 #[cfg(feature = "http-axum")]
73 const fn status(&self) -> StatusCode {
74 match self {
75 Self::InvalidGrant(_) | Self::InvalidScope(_) => StatusCode::BAD_REQUEST,
76 Self::AuthFailed(_) | Self::InvalidAccessToken => StatusCode::UNAUTHORIZED,
77 Self::Validation(_) => StatusCode::UNPROCESSABLE_ENTITY,
78 Self::Network(_) | Self::Server(_) => StatusCode::BAD_GATEWAY,
79 Self::Decode(_) => StatusCode::INTERNAL_SERVER_ERROR,
80 Self::RateLimited { .. } => StatusCode::TOO_MANY_REQUESTS,
81 Self::Config(_) | Self::Storage(_) | Self::InsecurePermissions { .. } => {
82 StatusCode::INTERNAL_SERVER_ERROR
83 }
84 }
85 }
86}
87
88#[cfg(feature = "http-axum")]
89impl IntoResponse for AuthError {
90 fn into_response(self) -> Response {
91 let status = self.status();
92 let body = axum::Json(serde_json::json!({
93 "kind": self.kind(),
94 "message": self.to_string(),
95 }));
96 let mut response = (status, body).into_response();
97 response.extensions_mut().insert(AuthErrorKind(self.kind()));
98 if let Self::RateLimited { retry_after_ms, .. } = self
99 && let Ok(value) = HeaderValue::from_str(&(retry_after_ms / 1_000).max(1).to_string())
100 {
101 response.headers_mut().insert(header::RETRY_AFTER, value);
102 }
103 crate::util::apply_no_store(response)
104 }
105}