Skip to main content

soma_auth/
error.rs

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}