a.0.0.7
This commit is contained in:
1 parent
b0d63c9f0f
commit
552c009120
8 files changed
+114
-83
No files matched your search
@@ -8,7 +8,11 @@ use axum::{
|
||||
Json,
|
||||
};
|
||||
use futures_util::StreamExt;
|
||||
use std::{io, net::IpAddr, sync::Arc};
|
||||
use std::{
|
||||
io,
|
||||
net::IpAddr,
|
||||
sync::{atomic::Ordering, Arc},
|
||||
};
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
const INDEX_HTML: &str = include_str!("static/index.html");
|
||||
@@ -17,31 +21,11 @@ const APP_JS: &str = include_str!("static/app.js");
|
||||
const CSP: &str = "default-src 'none'; script-src 'self'; style-src 'unsafe-inline'; img-src 'self'; \
|
||||
connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'";
|
||||
|
||||
// ───────────────────────── Защита UI (CSRF / DNS-rebinding) ─────────────────────────
|
||||
// ───────────────────────── Защита UI (анти-CSRF / DNS-rebinding без токена) ─────────────────────────
|
||||
pub struct UiGuard {
|
||||
pub token: String,
|
||||
pub cookie_name: String,
|
||||
pub hosts: Vec<String>,
|
||||
}
|
||||
|
||||
fn ct_eq(a: &[u8], b: &[u8]) -> bool {
|
||||
a.len() == b.len() && a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
|
||||
}
|
||||
|
||||
fn cookie_ok(headers: &HeaderMap, g: &UiGuard) -> bool {
|
||||
headers
|
||||
.get_all(header::COOKIE)
|
||||
.iter()
|
||||
.filter_map(|v| v.to_str().ok())
|
||||
.flat_map(|s| s.split(';'))
|
||||
.any(|kv| {
|
||||
kv.trim()
|
||||
.strip_prefix(g.cookie_name.as_str())
|
||||
.and_then(|r| r.strip_prefix('='))
|
||||
.map_or(false, |v| ct_eq(v.as_bytes(), g.token.as_bytes()))
|
||||
})
|
||||
}
|
||||
|
||||
fn with_security_headers(mut resp: Response) -> Response {
|
||||
let items: [(HeaderName, &'static str); 5] = [
|
||||
(header::CONTENT_SECURITY_POLICY, CSP),
|
||||
@@ -57,9 +41,8 @@ fn with_security_headers(mut resp: Response) -> Response {
|
||||
resp
|
||||
}
|
||||
|
||||
/// 1) Host только 127.0.0.1/localhost:порт (анти-rebinding)
|
||||
/// 2) POST только с Origin нашего же хоста (анти-CSRF)
|
||||
/// 3) Доступ по токену из консоли: /?t=… выставляет HttpOnly SameSite=Strict cookie
|
||||
/// 1) Host только 127.0.0.1/localhost:порт (анти-DNS-rebinding)
|
||||
/// 2) POST только с Origin нашего же локального хоста (анти-CSRF)
|
||||
pub async fn ui_guard(State(g): State<Arc<UiGuard>>, req: Request, next: Next) -> Response {
|
||||
let headers = req.headers();
|
||||
|
||||
@@ -82,31 +65,6 @@ pub async fn ui_guard(State(g): State<Arc<UiGuard>>, req: Request, next: Next) -
|
||||
}
|
||||
}
|
||||
|
||||
if req.uri().path() == "/" {
|
||||
if let Some(q) = req.uri().query() {
|
||||
let token_ok = q
|
||||
.split('&')
|
||||
.filter_map(|kv| kv.strip_prefix("t="))
|
||||
.any(|t| ct_eq(t.as_bytes(), g.token.as_bytes()));
|
||||
if token_ok {
|
||||
let mut resp = StatusCode::SEE_OTHER.into_response();
|
||||
resp.headers_mut().insert(header::LOCATION, HeaderValue::from_static("/"));
|
||||
if let Ok(c) = HeaderValue::from_str(&format!(
|
||||
"{}={}; Path=/; HttpOnly; SameSite=Strict",
|
||||
g.cookie_name, g.token
|
||||
)) {
|
||||
resp.headers_mut().insert(header::SET_COOKIE, c);
|
||||
}
|
||||
return with_security_headers(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !cookie_ok(headers, &g) {
|
||||
return with_security_headers(
|
||||
(StatusCode::FORBIDDEN, "Откройте адрес из консоли (с токеном ?t=…)").into_response(),
|
||||
);
|
||||
}
|
||||
with_security_headers(next.run(req).await)
|
||||
}
|
||||
|
||||
@@ -137,7 +95,9 @@ pub async fn ui_get_state(State(state): State<Arc<AppState>>) -> Json<serde_json
|
||||
"my_fingerprint": state.my_fingerprint,
|
||||
"my_pin": short_id(&state.my_fingerprint),
|
||||
"media_dir": state.media_dir.display().to_string(),
|
||||
"ping_interval_sec": state.ping_interval_sec.load(std::sync::atomic::Ordering::Relaxed),
|
||||
"ping_interval_sec": state.ping_interval_sec.load(Ordering::Relaxed),
|
||||
"last_ping": state.last_ping.load(Ordering::Relaxed),
|
||||
"now": now_sec(),
|
||||
"ping_min": MIN_PING_SEC,
|
||||
"ping_max": MAX_PING_SEC,
|
||||
"peers": peers,
|
||||
@@ -213,7 +173,7 @@ pub async fn ui_scan_range(
|
||||
}
|
||||
|
||||
pub async fn ui_ping_peers(State(state): State<Arc<AppState>>) -> StatusCode {
|
||||
tokio::spawn(async move { net::ping_all_peers(&state).await });
|
||||
net::ping_all_peers(&state).await;
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user