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:
co-authored by
Claude Opus 4.6
parent
1d98de24d0
commit
2443ae6bba
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user