use axum::extract::Request;
use axum::http::{HeaderValue, header};
use axum::middleware::Next;
use axum::response::Response;
pub const CONTENT_SECURITY_POLICY: &str = "default-src 'self'; \
script-src 'self'; \
style-src 'self' 'unsafe-inline'; \
img-src 'self' data:; \
font-src 'self' data:; \
connect-src 'self'; \
object-src 'none'; \
base-uri 'none'; \
form-action 'none'; \
frame-ancestors 'none'";
const HEADERS: &[(header::HeaderName, &str)] = &[
(header::CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY),
(header::X_FRAME_OPTIONS, "DENY"),
(header::X_CONTENT_TYPE_OPTIONS, "nosniff"),
(header::REFERRER_POLICY, "no-referrer"),
];
pub async fn apply(request: Request, next: Next) -> Response {
let mut response = next.run(request).await;
let headers = response.headers_mut();
for (name, value) in HEADERS {
headers.insert(name, HeaderValue::from_static(value));
}
response
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_policy_pins_scripts_to_this_origin_and_forbids_framing() {
assert!(CONTENT_SECURITY_POLICY.contains("script-src 'self'"));
assert!(CONTENT_SECURITY_POLICY.contains("frame-ancestors 'none'"));
assert!(
!CONTENT_SECURITY_POLICY.contains("script-src 'self' 'unsafe-inline'"),
"inline script would re-open the injection path to the localStorage token"
);
}
#[test]
fn every_hardening_header_has_a_valid_value() {
for (name, value) in HEADERS {
assert!(
HeaderValue::from_str(value).is_ok(),
"{name} carries an unsendable value"
);
}
}
}