Compare commits

..

1 Commits

Author SHA1 Message Date
cupcakearmy 1f180a2e53 Merge pull request #213 from cupcakearmy/feat/redis-key-prefix
feat: add REDIS_PREFIX env var for sharing a Redis instance
2026-06-25 22:30:59 +02:00
2 changed files with 16 additions and 33 deletions
+8 -27
View File
@@ -1,35 +1,16 @@
use axum::{ use axum::{body::Body, extract::Request, http::HeaderValue, middleware::Next, response::Response};
http::HeaderValue,
response::{Html, IntoResponse, Response},
};
use ring::rand::SecureRandom;
use std::sync::OnceLock;
const CSP_POLICY: &str = "default-src 'self'; script-src 'nonce-{nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; connect-src 'self'"; const CUSTOM_HEADER_NAME: &str = "Content-Security-Policy";
const CUSTOM_HEADER_VALUE: &str = "default-src 'self'; script-src 'report-sample' 'self'; style-src 'report-sample' 'self'; object-src 'none'; base-uri 'self'; connect-src 'self' data:; font-src 'self'; frame-src 'self'; img-src 'self'; manifest-src 'self'; media-src 'self'; worker-src 'none';";
fn index_html() -> &'static str { lazy_static! {
static HTML: OnceLock<String> = OnceLock::new(); static ref HEADER_VALUE: HeaderValue = HeaderValue::from_static(CUSTOM_HEADER_VALUE);
HTML.get_or_init(|| {
let path = format!("{}index.html", *crate::config::FRONTEND_PATH);
std::fs::read_to_string(&path).expect("Failed to read index.html for CSP injection")
})
} }
fn generate_nonce() -> String { pub async fn add_csp_header(request: Request<Body>, next: Next) -> Response {
let rng = ring::rand::SystemRandom::new(); let mut response = next.run(request).await;
let mut bytes = [0u8; 32];
rng.fill(&mut bytes).expect("Failed to generate CSP nonce");
bs62::encode_data(&bytes)
}
pub async fn spa_fallback() -> Response {
let nonce = generate_nonce();
let csp = CSP_POLICY.replace("{nonce}", &nonce);
let html = index_html().replace("<script>", &format!("<script nonce=\"{}\">", nonce));
let mut response = Html(html).into_response();
response response
.headers_mut() .headers_mut()
.insert("Content-Security-Policy", HeaderValue::from_str(&csp).unwrap()); .append(CUSTOM_HEADER_NAME, HEADER_VALUE.clone());
response response
} }
+7 -5
View File
@@ -12,7 +12,7 @@ use tower::Layer;
use tower_http::{ use tower_http::{
compression::CompressionLayer, compression::CompressionLayer,
normalize_path::NormalizePathLayer, normalize_path::NormalizePathLayer,
services::ServeDir, services::{ServeDir, ServeFile},
}; };
#[macro_use] #[macro_use]
@@ -50,12 +50,14 @@ async fn main() {
.merge(health_routes) .merge(health_routes)
.merge(status_routes); .merge(status_routes);
let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
let serve_dir =
ServeDir::new(config::FRONTEND_PATH.to_string()).not_found_service(ServeFile::new(index));
let app = Router::new() let app = Router::new()
.nest("/api", api_routes) .nest("/api", api_routes)
.fallback_service( .fallback_service(serve_dir)
ServeDir::new(config::FRONTEND_PATH.to_string()) // Disabled for now, as svelte inlines scripts
.not_found_service(axum::Router::new().fallback(csp::spa_fallback)), // .layer(middleware::from_fn(csp::add_csp_header))
)
.layer(DefaultBodyLimit::max(*config::LIMIT)) .layer(DefaultBodyLimit::max(*config::LIMIT))
.layer( .layer(
CompressionLayer::new() CompressionLayer::new()