fix(server): add CSP/security headers middleware, gitignore jks.bak*
Content-Security-Policy, X-Content-Type-Options, and X-Frame-Options are now injected by a single Axum middleware on the web router subtree, so all HTML pages get consistent headers without touching each file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,4 +14,5 @@ data/
|
||||
# Android signing keystores — never commit
|
||||
*.jks
|
||||
*.jks.bak
|
||||
*.jks.bak*
|
||||
*.keystore
|
||||
|
||||
@@ -16,8 +16,9 @@ pub use auth::reset_password;
|
||||
|
||||
use axum::{
|
||||
extract::DefaultBodyLimit,
|
||||
http::{HeaderValue, Request},
|
||||
middleware as axum_middleware,
|
||||
response::Html,
|
||||
response::{Html, Response},
|
||||
routing::{delete, get, post},
|
||||
Router,
|
||||
};
|
||||
@@ -226,7 +227,8 @@ fn build_router_inner(state: AppState, rate_limit: bool) -> Router {
|
||||
get(|| async { Html(include_str!("../web/replays.html")) }),
|
||||
)
|
||||
.nest_service("/web", ServeDir::new("solitaire_server/web"))
|
||||
.nest_service("/assets", ServeDir::new("assets"));
|
||||
.nest_service("/assets", ServeDir::new("assets"))
|
||||
.layer(axum_middleware::from_fn(security_headers));
|
||||
|
||||
Router::new()
|
||||
.merge(protected)
|
||||
@@ -238,6 +240,35 @@ fn build_router_inner(state: AppState, rate_limit: bool) -> Router {
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
const CSP: &str = concat!(
|
||||
"default-src 'self'; ",
|
||||
"script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; ",
|
||||
"style-src 'self' 'unsafe-inline'; ",
|
||||
"font-src 'self'; ",
|
||||
"img-src 'self' data:; ",
|
||||
"connect-src 'self'; ",
|
||||
"object-src 'none'; ",
|
||||
"frame-ancestors 'none'",
|
||||
);
|
||||
|
||||
async fn security_headers(req: Request<axum::body::Body>, next: axum_middleware::Next) -> Response {
|
||||
let mut res = next.run(req).await;
|
||||
let headers = res.headers_mut();
|
||||
headers.insert(
|
||||
"Content-Security-Policy",
|
||||
HeaderValue::from_static(CSP),
|
||||
);
|
||||
headers.insert(
|
||||
"X-Content-Type-Options",
|
||||
HeaderValue::from_static("nosniff"),
|
||||
);
|
||||
headers.insert(
|
||||
"X-Frame-Options",
|
||||
HeaderValue::from_static("DENY"),
|
||||
);
|
||||
res
|
||||
}
|
||||
|
||||
/// `GET /health` — simple liveness probe, no auth required.
|
||||
async fn health() -> axum::Json<serde_json::Value> {
|
||||
axum::Json(serde_json::json!({
|
||||
|
||||
Reference in New Issue
Block a user