diff --git a/Cargo.toml b/Cargo.toml index 38a1b08..7cc0d7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "json", "fmt"] } tower = "0.4" tower-cookies = "0.10" -tower-http = { version = "0.6", features = ["trace"] } +tower-http = { version = "0.6", features = ["limit", "set-header", "trace"] } slug = "0.1.6" url = "2" uuid = { version = "1", features = ["v4"] } diff --git a/src/application/routes/mod.rs b/src/application/routes/mod.rs index d1047b1..e13fa18 100644 --- a/src/application/routes/mod.rs +++ b/src/application/routes/mod.rs @@ -5,10 +5,12 @@ pub mod support; pub(crate) use app::auth::is_authenticated; use askama::Template; -use axum::http::StatusCode; +use axum::http::{HeaderValue, StatusCode}; use axum::response::Html; use tower::ServiceBuilder; use tower_cookies::CookieManagerLayer; +use tower_http::limit::RequestBodyLimitLayer; +use tower_http::set_header::SetResponseHeaderLayer; use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; use tracing::Level; use tracing::error; @@ -17,6 +19,9 @@ use crate::application::state::AppState; use crate::presentation::web::templates::render_template; +/// 5 MB request body limit. +const BODY_LIMIT_BYTES: usize = 5 * 1024 * 1024; + pub fn app_router(state: AppState) -> axum::Router { axum::Router::new() .merge(app::router()) @@ -29,7 +34,35 @@ pub fn app_router(state: AppState) -> axum::Router { .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) .on_response(DefaultOnResponse::new().level(Level::INFO)), ) - .layer(CookieManagerLayer::new()), + .layer(CookieManagerLayer::new()) + .layer(RequestBodyLimitLayer::new(BODY_LIMIT_BYTES)) + .layer(SetResponseHeaderLayer::overriding( + axum::http::header::X_CONTENT_TYPE_OPTIONS, + HeaderValue::from_static("nosniff"), + )) + .layer(SetResponseHeaderLayer::overriding( + axum::http::header::X_FRAME_OPTIONS, + HeaderValue::from_static("DENY"), + )) + .layer(SetResponseHeaderLayer::overriding( + axum::http::header::REFERRER_POLICY, + HeaderValue::from_static("strict-origin-when-cross-origin"), + )) + .layer(SetResponseHeaderLayer::overriding( + axum::http::header::CONTENT_SECURITY_POLICY, + HeaderValue::from_static( + "default-src 'self'; \ + script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net; \ + style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; \ + font-src 'self' https://fonts.gstatic.com; \ + img-src 'self' data:; \ + frame-ancestors 'none'", + ), + )) + .layer(SetResponseHeaderLayer::overriding( + axum::http::header::STRICT_TRANSPORT_SECURITY, + HeaderValue::from_static("max-age=63072000; includeSubDomains"), + )), ) .with_state(state) }