use axum::{
http::{HeaderValue, Request, Response, StatusCode},
middleware::Next,
};
const ACCESS_CONTROL_ALLOW_ORIGIN: &str = "Access-Control-Allow-Origin";
const ACCESS_CONTROL_ALLOW_METHODS: &str = "Access-Control-Allow-Methods";
const ACCESS_CONTROL_ALLOW_HEADERS: &str = "Access-Control-Allow-Headers";
const ACCESS_CONTROL_MAX_AGE: &str = "Access-Control-Max-Age";
const X_CONTENT_TYPE_OPTIONS: &str = "X-Content-Type-Options";
const X_FRAME_OPTIONS: &str = "X-Frame-Options";
const X_XSS_PROTECTION: &str = "X-XSS-Protection";
const STRICT_TRANSPORT_SECURITY: &str = "Strict-Transport-Security";
const CONTENT_SECURITY_POLICY: &str = "Content-Security-Policy";
const X_PERMITTED_CROSS_DOMAIN_POLICIES: &str = "X-Permitted-Cross-Domain-Policies";
const CROSS_ORIGIN_EMBEDDER_POLICY: &str = "Cross-Origin-Embedder-Policy";
const CROSS_ORIGIN_OPENER_POLICY: &str = "Cross-Origin-Opener-Policy";
const CROSS_ORIGIN_RESOURCE_POLICY: &str = "Cross-Origin-Resource-Policy";
const REFERRER_POLICY: &str = "Referrer-Policy";
const PERMISSIONS_POLICY: &str = "Permissions-Policy";
const ALLOW_ALL_ORIGINS: &str = "*";
const ALLOWED_METHODS: &str = "GET, POST, PUT, DELETE, OPTIONS";
const ALLOWED_HEADERS: &str = "Content-Type, Authorization, Accept, Origin, X-Requested-With, Cache-Control, Pragma, X-RSC-Streaming";
const MAX_AGE_SECONDS: &str = "86400";
const NOSNIFF: &str = "nosniff";
const FRAME_DENY: &str = "DENY";
const XSS_PROTECTION: &str = "1; mode=block";
const HSTS_HEADER: &str = "max-age=31536000; includeSubDomains";
const CROSS_DOMAIN_POLICIES_NONE: &str = "none";
const COEP_REQUIRE_CORP: &str = "require-corp";
const COOP_SAME_ORIGIN: &str = "same-origin";
const CORP_SAME_ORIGIN: &str = "same-origin";
const REFERRER_STRICT_ORIGIN: &str = "strict-origin-when-cross-origin";
const PERMISSIONS_RESTRICTIVE: &str = "geolocation=(), microphone=(), camera=(), payment=()";
fn apply_error_cache_control(headers: &mut axum::http::HeaderMap, status: StatusCode) {
if status.is_client_error() || status.is_server_error() {
headers.insert(
axum::http::header::CACHE_CONTROL,
HeaderValue::from_static("no-cache, no-store, must-revalidate"),
);
}
}
pub async fn cors_middleware(
request: Request<axum::body::Body>,
next: Next,
) -> Response<axum::body::Body> {
let mut response = next.run(request).await;
let status = response.status();
let headers = response.headers_mut();
add_cors_headers(headers);
apply_error_cache_control(headers, status);
response
}
fn add_cors_headers(headers: &mut axum::http::HeaderMap) {
let cors_headers = [
(ACCESS_CONTROL_ALLOW_ORIGIN, ALLOW_ALL_ORIGINS),
(ACCESS_CONTROL_ALLOW_METHODS, ALLOWED_METHODS),
(ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_HEADERS),
(ACCESS_CONTROL_MAX_AGE, MAX_AGE_SECONDS),
];
for (header_name, header_value) in cors_headers {
if let Ok(value) = HeaderValue::from_str(header_value) {
headers.insert(header_name, value);
}
}
}
pub async fn security_headers_middleware(
request: Request<axum::body::Body>,
next: Next,
) -> Response<axum::body::Body> {
let mut response = next.run(request).await;
let status = response.status();
let headers = response.headers_mut();
add_security_headers(headers);
apply_error_cache_control(headers, status);
response
}
fn add_security_headers(headers: &mut axum::http::HeaderMap) {
let csp_policy = if let Some(config) = crate::server::config::Config::get() {
config.build_csp_policy()
} else {
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:".to_string()
};
let security_headers = [
(X_CONTENT_TYPE_OPTIONS, NOSNIFF),
(X_FRAME_OPTIONS, FRAME_DENY),
(X_XSS_PROTECTION, XSS_PROTECTION),
(STRICT_TRANSPORT_SECURITY, HSTS_HEADER),
(X_PERMITTED_CROSS_DOMAIN_POLICIES, CROSS_DOMAIN_POLICIES_NONE),
(CROSS_ORIGIN_EMBEDDER_POLICY, COEP_REQUIRE_CORP),
(CROSS_ORIGIN_OPENER_POLICY, COOP_SAME_ORIGIN),
(CROSS_ORIGIN_RESOURCE_POLICY, CORP_SAME_ORIGIN),
(REFERRER_POLICY, REFERRER_STRICT_ORIGIN),
(PERMISSIONS_POLICY, PERMISSIONS_RESTRICTIVE),
];
for (header_name, header_value) in security_headers {
if let Ok(value) = HeaderValue::from_str(header_value) {
headers.insert(header_name, value);
}
}
if let Ok(value) = HeaderValue::from_str(&csp_policy) {
headers.insert(CONTENT_SECURITY_POLICY, value);
}
}