datasynth_server/rest/
security_headers.rs1use axum::{
6 body::Body,
7 http::{Request, Response},
8 middleware::Next,
9};
10
11pub async fn security_headers_middleware(request: Request<Body>, next: Next) -> Response<Body> {
21 let mut response = next.run(request).await;
22 let headers = response.headers_mut();
23
24 headers.insert("x-content-type-options", "nosniff".parse().unwrap());
25 headers.insert("x-frame-options", "DENY".parse().unwrap());
26 headers.insert("x-xss-protection", "0".parse().unwrap());
27 headers.insert(
28 "referrer-policy",
29 "strict-origin-when-cross-origin".parse().unwrap(),
30 );
31 headers.insert(
32 "content-security-policy",
33 "default-src 'none'; frame-ancestors 'none'"
34 .parse()
35 .unwrap(),
36 );
37 headers.insert("cache-control", "no-store".parse().unwrap());
38
39 response
40}
41
42#[cfg(test)]
43#[allow(clippy::unwrap_used)]
44mod tests {
45 use super::*;
46 use axum::{routing::get, Router};
47 use tower::ServiceExt;
48
49 async fn ok_handler() -> &'static str {
50 "ok"
51 }
52
53 fn test_router() -> Router {
54 Router::new()
55 .route("/test", get(ok_handler))
56 .layer(axum::middleware::from_fn(security_headers_middleware))
57 }
58
59 #[tokio::test]
60 async fn test_security_headers_present() {
61 let router = test_router();
62 let request = Request::builder().uri("/test").body(Body::empty()).unwrap();
63
64 let response = router.oneshot(request).await.unwrap();
65 let headers = response.headers();
66
67 assert_eq!(headers.get("x-content-type-options").unwrap(), "nosniff");
68 assert_eq!(headers.get("x-frame-options").unwrap(), "DENY");
69 assert_eq!(headers.get("x-xss-protection").unwrap(), "0");
70 assert_eq!(
71 headers.get("referrer-policy").unwrap(),
72 "strict-origin-when-cross-origin"
73 );
74 assert_eq!(
75 headers.get("content-security-policy").unwrap(),
76 "default-src 'none'; frame-ancestors 'none'"
77 );
78 assert_eq!(headers.get("cache-control").unwrap(), "no-store");
79 }
80}