Files
gree-controller/src/api/middleware.rs
T
2026-09-16 17:07:17 +02:00

66 lines
2.1 KiB
Rust

async fn security_headers(request: Request, next: Next) -> Response {
let path = request.uri().path();
let is_api = path == "/api" || path.starts_with("/api/");
let is_api_docs = path == "/api-docs" || path.starts_with("/api-docs/");
let is_custom_chart = path.starts_with("/charts/custom/");
let mut response = next.run(request).await;
let is_html = response
.headers()
.get(header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.is_some_and(|value| {
value
.split(';')
.next()
.is_some_and(|mime| mime.trim().eq_ignore_ascii_case("text/html"))
});
let headers = response.headers_mut();
headers.insert(
header::HeaderName::from_static("x-content-type-options"),
HeaderValue::from_static("nosniff"),
);
headers.insert(
header::HeaderName::from_static("referrer-policy"),
HeaderValue::from_static(if is_custom_chart {
"no-referrer"
} else {
"same-origin"
}),
);
if is_html {
headers.insert(
header::HeaderName::from_static("x-frame-options"),
HeaderValue::from_static("SAMEORIGIN"),
);
headers.insert(
header::HeaderName::from_static("content-security-policy"),
HeaderValue::from_static(
"default-src 'self'; connect-src 'self' ws: wss:; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; object-src 'none'"
),
);
headers.insert(
header::HeaderName::from_static("permissions-policy"),
HeaderValue::from_static("camera=(), microphone=(), geolocation=()"),
);
}
if is_api {
headers.insert(
header::CACHE_CONTROL,
HeaderValue::from_static("no-cache, no-store"),
);
} else if is_api_docs {
headers.insert(
header::CACHE_CONTROL,
HeaderValue::from_static("no-cache, must-revalidate"),
);
}
response
}