1 parent
cfc74d9db6
commit
dacdf9c27d
76 files changed
+5209
-741
No files matched your search
+98
-20
@@ -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))
|
||||
|
||||
Reference in new issue
Block a user