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    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}