kanidmd_core/https/middleware/
mod.rs

1use axum::{
2    body::Body,
3    http::{HeaderValue, Request},
4    middleware::Next,
5    response::Response,
6};
7use kanidm_proto::constants::{KOPID, KVERSION};
8use uuid::Uuid;
9
10pub(crate) mod caching;
11pub(crate) mod compression;
12pub(crate) mod hsts_header;
13pub(crate) mod security_headers;
14
15// the version middleware injects
16const KANIDM_VERSION: &str = env!("CARGO_PKG_VERSION");
17
18/// Injects a header into the response with "X-KANIDM-VERSION" matching the version of the package.
19pub async fn version_middleware(request: Request<Body>, next: Next) -> Response {
20    let mut response = next.run(request).await;
21    response
22        .headers_mut()
23        .insert(KVERSION, HeaderValue::from_static(KANIDM_VERSION));
24    response
25}
26
27#[cfg(any(test, debug_assertions))]
28/// This is a debug middleware to ensure that /v1/ endpoints only return JSON
29#[instrument(level = "trace", name = "are_we_json_yet", skip_all)]
30pub async fn are_we_json_yet(request: Request<Body>, next: Next) -> Response {
31    let uri = request.uri().path().to_string();
32
33    let response = next.run(request).await;
34
35    if uri.starts_with("/v1") && response.status().is_success() {
36        let headers = response.headers();
37        assert!(headers.contains_key(axum::http::header::CONTENT_TYPE));
38        assert!(
39            headers.get(axum::http::header::CONTENT_TYPE)
40                == Some(&HeaderValue::from_static(
41                    kanidm_proto::constants::APPLICATION_JSON
42                ))
43        );
44    }
45
46    response
47}
48
49#[derive(Clone, Debug)]
50/// For holding onto the event ID and other handy request-based things
51pub struct KOpId {
52    /// The event correlation ID
53    pub eventid: Uuid,
54}
55
56/// This runs at the start of the request, adding an extension with `KOpId` which has useful things inside it.
57#[instrument(level = "trace", name = "kopid_middleware", skip_all)]
58pub async fn kopid_middleware(mut request: Request<Body>, next: Next) -> Response {
59    // generate the event ID
60    let eventid = sketching::tracing_forest::id();
61
62    // insert the extension so we can pull it out later
63    request.extensions_mut().insert(KOpId { eventid });
64    let mut response = next.run(request).await;
65
66    // This conversion *should never* fail. If it does, rather than panic, we warn and
67    // just don't put the id in the response.
68    let _ = HeaderValue::from_str(&eventid.as_hyphenated().to_string())
69        .map(|hv| response.headers_mut().insert(KOPID, hv))
70        .map_err(|err| {
71            warn!(?err, "An invalid operation id was encountered");
72        });
73
74    response
75}