fix(server): add CSP/security headers middleware, gitignore jks.bak*
Build and Deploy / build-and-push (push) Failing after 3m52s
Build and Deploy / build-and-push (push) Failing after 3m52s
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
|
# Android signing keystores — never commit
|
||||||
*.jks
|
*.jks
|
||||||
*.jks.bak
|
*.jks.bak
|
||||||
|
*.jks.bak*
|
||||||
*.keystore
|
*.keystore
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ pub use auth::reset_password;
|
|||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::DefaultBodyLimit,
|
extract::DefaultBodyLimit,
|
||||||
|
http::{HeaderValue, Request},
|
||||||
middleware as axum_middleware,
|
middleware as axum_middleware,
|
||||||
response::Html,
|
response::{Html, Response},
|
||||||
routing::{delete, get, post},
|
routing::{delete, get, post},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
@@ -226,7 +227,8 @@ fn build_router_inner(state: AppState, rate_limit: bool) -> Router {
|
|||||||
get(|| async { Html(include_str!("../web/replays.html")) }),
|
get(|| async { Html(include_str!("../web/replays.html")) }),
|
||||||
)
|
)
|
||||||
.nest_service("/web", ServeDir::new("solitaire_server/web"))
|
.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()
|
Router::new()
|
||||||
.merge(protected)
|
.merge(protected)
|
||||||
@@ -238,6 +240,35 @@ fn build_router_inner(state: AppState, rate_limit: bool) -> Router {
|
|||||||
.with_state(state)
|
.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.
|
/// `GET /health` — simple liveness probe, no auth required.
|
||||||
async fn health() -> axum::Json<serde_json::Value> {
|
async fn health() -> axum::Json<serde_json::Value> {
|
||||||
axum::Json(serde_json::json!({
|
axum::Json(serde_json::json!({
|
||||||
|
|||||||
Reference in New Issue
Block a user