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};
7
8use hyper::header::WWW_AUTHENTICATE;
9use kanidm_proto::oauth2::ErrorResponse;
10use kanidmd_lib::idm::oauth2::Oauth2Error;
11use utoipa::ToSchema;
12
13use kanidm_proto::internal::OperationError;
14
15/// The web app's top level error type, this takes an `OperationError` and converts it into a HTTP response.
16#[derive(Debug, ToSchema)]
17pub enum WebError {
18    /// Something went wrong when doing things.
19    OperationError(OperationError),
20    InternalServerError(String),
21    #[schema(value_type=Object)]
22    OAuth2(Oauth2Error),
23}
24
25impl From<OperationError> for WebError {
26    fn from(inner: OperationError) -> Self {
27        WebError::OperationError(inner)
28    }
29}
30
31impl From<Oauth2Error> for WebError {
32    fn from(inner: Oauth2Error) -> Self {
33        WebError::OAuth2(inner)
34    }
35}
36
37impl WebError {
38    pub(crate) fn response_with_access_control_origin_header(self) -> Response {
39        let mut res = self.into_response();
40        res.headers_mut().insert(
41            ACCESS_CONTROL_ALLOW_ORIGIN,
42            #[allow(clippy::expect_used)]
43            HeaderValue::from_str("*").expect("Header generation failed, this is weird."),
44        );
45        res
46    }
47}
48
49impl IntoResponse for WebError {
50    fn into_response(self) -> Response {
51        match self {
52            WebError::OAuth2(error) => {
53                if let Oauth2Error::AuthenticationRequired = error {
54                    (
55                        StatusCode::UNAUTHORIZED,
56                        [
57                            (WWW_AUTHENTICATE, "Bearer"),
58                            (ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
59                        ],
60                    )
61                        .into_response()
62                } else {
63                    let err = ErrorResponse {
64                        error: error.to_string(),
65                        ..Default::default()
66                    };
67
68                    let body = match serde_json::to_string(&err) {
69                        Ok(val) => val,
70                        Err(e) => {
71                            warn!("Failed to serialize error response: original_error=\"{:?}\" serialization_error=\"{:?}\"", err, e);
72                            format!("{:?}", err)
73                        }
74                    };
75
76                    (
77                        StatusCode::BAD_REQUEST,
78                        [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
79                        body,
80                    )
81                        .into_response()
82                }
83            }
84            WebError::InternalServerError(inner) => {
85                (StatusCode::INTERNAL_SERVER_ERROR, inner).into_response()
86            }
87            WebError::OperationError(inner) => {
88                let (code, headers) = match &inner {
89                    OperationError::NotAuthenticated | OperationError::SessionExpired => {
90                        // https://datatracker.ietf.org/doc/html/rfc7235#section-4.1
91                        (
92                            StatusCode::UNAUTHORIZED,
93                            Some([("WWW-Authenticate", "Bearer"); 1]),
94                        )
95                    }
96                    OperationError::SystemProtectedObject | OperationError::AccessDenied => {
97                        (StatusCode::FORBIDDEN, None)
98                    }
99                    OperationError::NoMatchingEntries => (StatusCode::NOT_FOUND, None),
100                    OperationError::PasswordQuality(_)
101                    | OperationError::EmptyRequest
102                    | OperationError::InvalidAttribute(_)
103                    | OperationError::InvalidAttributeName(_)
104                    | OperationError::SchemaViolation(_)
105                    | OperationError::CU0003WebauthnUserNotVerified
106                    | OperationError::VL0001ValueSshPublicKeyString => {
107                        (StatusCode::BAD_REQUEST, None)
108                    }
109                    _ => (StatusCode::INTERNAL_SERVER_ERROR, None),
110                };
111                let body = serde_json::to_string(&inner).unwrap_or(inner.to_string());
112
113                match headers {
114                    Some(headers) => (code, headers, body).into_response(),
115                    None => (code, body).into_response(),
116                }
117            }
118        }
119    }
120}