refactor: replace blocking std::fs and TCP I/O with async tokio equivalents

- R6: Convert 6 std::fs calls in session.rs to tokio::fs async
- R7: Convert std::fs::read_to_string in docker_packages.rs to async
- R8: Convert 3 std::fs calls in port_allocator.rs to async, switch to tokio::sync::Mutex
- R9+R10+R11: Fix blocking I/O in node_message.rs and nostr_discovery.rs
- R12: Convert electrs_status.rs from sync TCP to async tokio::net with 5s timeouts
- R4+R5: Spawn periodic cleanup tasks for endpoint and login rate limiters

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-21 01:21:08 +00:00
co-authored by Claude Opus 4.6
parent 1d98de24d0
commit 2443ae6bba
12 changed files with 161 additions and 117 deletions
+41 -16
View File
@@ -42,7 +42,7 @@ use anyhow::{Context, Result};
use hyper::{Request, Response, StatusCode};
use serde::{Deserialize, Serialize};
use std::net::IpAddr;
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use tracing::{debug, error};
#[derive(Debug, Deserialize)]
@@ -164,7 +164,7 @@ pub struct RpcHandler {
orchestrator: Option<Arc<DevContainerOrchestrator>>,
state_manager: Arc<StateManager>,
pub(crate) metrics_store: Arc<MetricsStore>,
port_allocator: Arc<Mutex<PortAllocator>>,
port_allocator: Arc<tokio::sync::Mutex<PortAllocator>>,
pub session_store: SessionStore,
login_rate_limiter: LoginRateLimiter,
endpoint_rate_limiter: EndpointRateLimiter,
@@ -188,7 +188,32 @@ impl RpcHandler {
} else {
None
};
let port_allocator = Arc::new(Mutex::new(PortAllocator::new(&config.data_dir)?));
let port_allocator = Arc::new(tokio::sync::Mutex::new(PortAllocator::new(&config.data_dir).await?));
let login_rate_limiter = LoginRateLimiter::new();
let endpoint_rate_limiter = EndpointRateLimiter::new();
// Spawn periodic rate limiter cleanup (every 5 minutes)
{
let limiter = endpoint_rate_limiter.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(300));
loop {
interval.tick().await;
limiter.cleanup().await;
}
});
}
{
let limiter = login_rate_limiter.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(300));
loop {
interval.tick().await;
limiter.cleanup().await;
}
});
}
Ok(Self {
config,
@@ -198,8 +223,8 @@ impl RpcHandler {
metrics_store,
port_allocator,
session_store,
login_rate_limiter: LoginRateLimiter::new(),
endpoint_rate_limiter: EndpointRateLimiter::new(),
login_rate_limiter,
endpoint_rate_limiter,
response_cache: ResponseCache::new(5),
mesh_service: Arc::new(tokio::sync::RwLock::new(None)),
transport_router: Arc::new(tokio::sync::RwLock::new(None)),
@@ -253,10 +278,10 @@ impl RpcHandler {
// If session invalid, try remember-me token to auto-restore session
if !authenticated {
if let Some(remember) = extract_cookie(&parts.headers, "remember") {
if crate::session::SessionStore::validate_remember_token(&remember) {
if crate::session::SessionStore::validate_remember_token(&remember).await {
// Auto-create a new session from the remember-me token
let new_token = self.session_store.create().await;
let new_csrf = derive_csrf_token(&new_token);
let new_csrf = derive_csrf_token(&new_token).await;
tracing::info!("Auto-restored session from remember-me token");
new_session_cookies = Some((new_token, new_csrf));
authenticated = true;
@@ -325,7 +350,7 @@ impl RpcHandler {
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
let secret = SessionStore::load_or_create_remember_secret();
let secret = SessionStore::load_or_create_remember_secret().await;
let mut mac = match HmacSha256::new_from_slice(&secret) {
Ok(m) => m,
Err(_) => { return Ok(Response::builder().status(500).body(hyper::Body::empty()).unwrap()); }
@@ -858,7 +883,7 @@ impl RpcHandler {
if let Ok(Some(totp_data)) = self.auth_manager.get_totp_data().await {
if let Ok(secret) = crate::totp::decrypt_secret(&totp_data, password) {
let token = self.session_store.create_pending(secret).await;
let csrf_token = derive_csrf_token(&token);
let csrf_token = derive_csrf_token(&token).await;
response.headers_mut().append(
"Set-Cookie",
format!("session={}; HttpOnly; SameSite=Lax; Path=/{}", token, self.cookie_suffix())
@@ -884,8 +909,8 @@ impl RpcHandler {
} else {
// No 2FA: create a full session immediately
let token = self.session_store.create().await;
let csrf_token = derive_csrf_token(&token);
let remember_token = self.session_store.create_remember_token();
let csrf_token = derive_csrf_token(&token).await;
let remember_token = self.session_store.create_remember_token().await;
response.headers_mut().append(
"Set-Cookie",
format!("session={}; HttpOnly; SameSite=Lax; Path=/{}", token, self.cookie_suffix())
@@ -921,8 +946,8 @@ impl RpcHandler {
.map(|s| s.to_string());
if let Some(new_token) = new_token_opt {
let csrf_token = derive_csrf_token(&new_token);
let remember_token = self.session_store.create_remember_token();
let csrf_token = derive_csrf_token(&new_token).await;
let remember_token = self.session_store.create_remember_token().await;
response.headers_mut().append(
"Set-Cookie",
format!(
@@ -969,7 +994,7 @@ impl RpcHandler {
if rpc_req.method == "auth.changePassword" && rpc_resp.error.is_none() {
if let Some(token) = &session_token {
let new_token = self.session_store.rotate(token).await;
let csrf_token = derive_csrf_token(&new_token);
let csrf_token = derive_csrf_token(&new_token).await;
response.headers_mut().append(
"Set-Cookie",
format!(
@@ -1059,11 +1084,11 @@ impl RpcHandler {
/// Deterministic: same session token always produces the same CSRF token.
/// Survives backend restarts because it depends only on the session token
/// and the on-disk remember secret (not ephemeral state).
fn derive_csrf_token(session_token: &str) -> String {
async fn derive_csrf_token(session_token: &str) -> String {
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
let secret = SessionStore::load_or_create_remember_secret();
let secret = SessionStore::load_or_create_remember_secret().await;
let mut mac = HmacSha256::new_from_slice(&secret).expect("HMAC key");
mac.update(format!("csrf:{}", session_token).as_bytes());
hex::encode(mac.finalize().into_bytes())