v0.2.8
RustBox CI / rustbox (push) Canceled after 0s

This commit is contained in:
Mateusz Gruszczyński committed 2026-11-01 20:40:40 +01:00
1 parent cfc74d9db6
commit dacdf9c27d
76 files changed
+5209 -741

No files matched your search

+98 -20
View File
@@ -6,6 +6,7 @@ pub mod contacts;
pub mod diagnostics;
pub mod display_preferences;
pub mod drafts;
pub mod folder_counts;
pub mod folder_mgmt;
pub mod folder_preferences;
pub mod folders;
@@ -22,6 +23,7 @@ pub mod send;
pub mod settings;
pub mod sieve;
pub mod special_folders;
pub mod system_diagnostics;
pub mod tags;
pub mod trusted_senders;
pub mod unsubscribe;
@@ -32,7 +34,7 @@ use std::time::Duration;
use axum::body::Body;
use axum::extract::{ConnectInfo, DefaultBodyLimit};
use axum::http::{HeaderValue, Request};
use axum::http::{HeaderValue, Request, header};
use axum::response::Response;
use axum::routing::{delete, get, patch, post, put};
use axum::{Extension, Router, middleware};
@@ -104,29 +106,70 @@ impl KeyExtractor for ProxyAwareIpExtractor {
}
}
fn response_content_type_is(response: &Response, expected: &str) -> bool {
response
.headers()
.get(header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.split(';').next())
.is_some_and(|value| value.trim().eq_ignore_ascii_case(expected))
}
async fn add_security_headers(request: Request<Body>, next: middleware::Next) -> Response {
let request_path = request.uri().path().to_owned();
let is_api =
request_path == "/api" || request_path.ends_with("/api") || request_path.contains("/api/");
let mut response = next.run(request).await;
let is_html = response_content_type_is(&response, "text/html")
|| response_content_type_is(&response, "application/xhtml+xml");
let is_css = response_content_type_is(&response, "text/css");
let headers = response.headers_mut();
headers.insert(
"x-content-type-options",
HeaderValue::from_static("nosniff"),
);
headers.insert("x-frame-options", HeaderValue::from_static("DENY"));
headers.insert("referrer-policy", HeaderValue::from_static("no-referrer"));
headers.insert(
"permissions-policy",
HeaderValue::from_static("camera=(), microphone=(), geolocation=()"),
);
headers.insert(
"cross-origin-opener-policy",
HeaderValue::from_static("same-origin"),
);
headers.insert(
"content-security-policy",
HeaderValue::from_static(
"default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' ws: wss:",
),
);
if is_api {
headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
headers.insert(
"cross-origin-resource-policy",
HeaderValue::from_static("same-origin"),
);
headers.insert("x-frame-options", HeaderValue::from_static("DENY"));
headers.insert(
"content-security-policy",
HeaderValue::from_static("frame-ancestors 'none'"),
);
} else if is_html || is_css {
headers.insert("referrer-policy", HeaderValue::from_static("no-referrer"));
}
if request_path.contains("/assets/") || request_path.ends_with("/favicon.svg") {
headers.insert(
"cross-origin-resource-policy",
HeaderValue::from_static("same-origin"),
);
}
if is_html {
headers.insert("x-frame-options", HeaderValue::from_static("DENY"));
headers.insert(
"permissions-policy",
HeaderValue::from_static("camera=(), microphone=(), geolocation=()"),
);
headers.insert(
"cross-origin-opener-policy",
HeaderValue::from_static("same-origin"),
);
headers.insert(
"content-security-policy",
HeaderValue::from_static(
"default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self'; script-src-attr 'none'; style-src 'self'; style-src-attr 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' ws: wss:; frame-src 'self' blob:",
),
);
}
response
}
@@ -176,6 +219,8 @@ pub fn create_router_with_storage(config: Arc<AppConfig>, services: RouterServic
event_bus,
idle_manager,
} = services;
let system_diagnostics_telemetry =
Arc::new(system_diagnostics::SystemDiagnosticsTelemetry::new());
let governor_conf = GovernorConfigBuilder::default()
.key_extractor(ProxyAwareIpExtractor {
trusted_proxies: config.parsed_trusted_proxies(),
@@ -200,6 +245,7 @@ pub fn create_router_with_storage(config: Arc<AppConfig>, services: RouterServic
let protected_auth = Router::new()
.route("/session", get(auth::get_session))
.route("/session-policy", get(auth::session_policy))
.route("/sessions", get(auth::list_active_sessions))
.route("/sessions/revoke-others", post(auth::revoke_other_sessions))
.route("/sessions/{id}", delete(auth::revoke_active_session))
@@ -532,13 +578,27 @@ pub fn create_router_with_storage(config: Arc<AppConfig>, services: RouterServic
let ws_route = Router::new().route("/ws", get(crate::realtime::ws::ws_handler));
let api_router = Router::new()
let mut api_router = Router::new()
.route("/health", get(health::health_check))
.route("/ready", get(health::readiness_check))
.nest("/auth", auth_router)
.merge(ws_route)
.merge(protected_data)
.fallback(|| async { axum::http::StatusCode::NOT_FOUND });
.merge(protected_data);
if config.diagnostics_endpoint.enabled {
let diagnostics_path = config
.diagnostics_endpoint
.path
.trim()
.strip_prefix("/api")
.unwrap_or(config.diagnostics_endpoint.path.trim());
api_router = api_router.route(
diagnostics_path,
get(system_diagnostics::system_diagnostics),
);
}
let api_router = api_router.fallback(|| async { axum::http::StatusCode::NOT_FOUND });
let inner = Router::new()
.nest("/api", api_router)
@@ -555,6 +615,24 @@ pub fn create_router_with_storage(config: Arc<AppConfig>, services: RouterServic
}
};
let router = if config.diagnostics_endpoint.enabled {
let system_diagnostics_dependencies =
Arc::new(system_diagnostics::SystemDiagnosticsDependencies::new(
config.clone(),
store.clone(),
search_engine.clone(),
storage.clone(),
idle_manager.clone(),
system_diagnostics_telemetry.clone(),
));
router
.layer(middleware::from_fn(system_diagnostics::observe_requests))
.layer(Extension(system_diagnostics_dependencies))
.layer(Extension(system_diagnostics_telemetry))
} else {
router
};
let router = router
.layer(Extension(idle_manager))
.layer(Extension(event_bus))