kanidmd_core/https/
errors.rs

1//! Where we hide the error handling widgets
2//!
3
4use axum::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;
5use axum::http::{HeaderValue, StatusCode};
6use axum::response::{IntoResponse, Response};
7use utoipa::ToSchema;
8
9use kanidm_proto::internal::OperationError;
10
11/// The web app's top level error type, this takes an `OperationError` and converts it into a HTTP response.
12#[derive(Debug, ToSchema)]
13pub enum WebError {
14    /// Something went wrong when doing things.
15    OperationError(OperationError),
16    InternalServerError(String),
17}
18
19impl From<OperationError> for WebError {
20    fn from(inner: OperationError) -> Self {
21        WebError::OperationError(inner)
22    }
23}
24
25impl WebError {
26    pub(crate) fn response_with_access_control_origin_header(self) -> Response {
27        let mut res = self.into_response();
28        res.headers_mut().insert(
29            ACCESS_CONTROL_ALLOW_ORIGIN,
30            #[allow(clippy::expect_used)]
31            HeaderValue::from_str("*").expect("Header generation failed, this is weird."),
32        );
33        res
34    }
35}
36
37impl IntoResponse for WebError {
38    fn into_response(self) -> Response {
39        match self {
40            WebError::InternalServerError(inner) => {
41                (StatusCode::INTERNAL_SERVER_ERROR, inner).into_response()
42            }
43            WebError::OperationError(inner) => {
44                let (code, headers) = match &inner {
45                    OperationError::NotAuthenticated | OperationError::SessionExpired => {
46                        // https://datatracker.ietf.org/doc/html/rfc7235#section-4.1
47                        (
48                            StatusCode::UNAUTHORIZED,
49                            Some([("WWW-Authenticate", "Bearer"); 1]),
50                        )
51                    }
52                    OperationError::SystemProtectedObject | OperationError::AccessDenied => {
53                        (StatusCode::FORBIDDEN, None)
54                    }
55                    OperationError::NoMatchingEntries => (StatusCode::NOT_FOUND, None),
56                    OperationError::PasswordQuality(_)
57                    | OperationError::EmptyRequest
58                    | OperationError::InvalidAttribute(_)
59                    | OperationError::InvalidAttributeName(_)
60                    | OperationError::SchemaViolation(_)
61                    | OperationError::CU0003WebauthnUserNotVerified
62                    | OperationError::VL0001ValueSshPublicKeyString => {
63                        (StatusCode::BAD_REQUEST, None)
64                    }
65                    _ => (StatusCode::INTERNAL_SERVER_ERROR, None),
66                };
67                let body = serde_json::to_string(&inner).unwrap_or(inner.to_string());
68
69                match headers {
70                    Some(headers) => (code, headers, body).into_response(),
71                    None => (code, body).into_response(),
72                }
73            }
74        }
75    }
76}