src/api/state.rs
Rama main · réplica de lectura del repositorio original.
use std::sync::Arc;
use crate::api::types::InfoUrls;
use crate::config::AppConfig;
use crate::dashboard::DecisionLog;
use crate::failover::FailoverGuard;
use crate::puzzle::challenge::PuzzleEngine;
use crate::risk::{RiskScorer, TrustedProxies, VerifyScorer};
use crate::storage::memory::InMemoryStore;
pub type SharedState = Arc<AppState>;
pub struct AppState {
pub store: Arc<InMemoryStore>,
pub engine: PuzzleEngine,
pub risk: RiskScorer,
pub verify_scorer: VerifyScorer,
/// Header name to read for TLS fingerprint (e.g. `x-ja4`). `None` disables
/// the signal entirely.
pub tls_fingerprint_header: Option<String>,
/// Trusted proxies whose TLS fingerprint header we honor (and whose
/// `X-Forwarded-For` we walk to resolve the client IP). Empty if no
/// proxies are configured — direct connections only.
pub trusted_proxies: Arc<TrustedProxies>,
/// Validation dashboard log. When `Some`, every puzzle/verify decision is
/// also persisted to SQLite for the admin dashboard.
pub decision_log: Option<DecisionLog>,
/// Bearer token required to call mutating admin endpoints, including
/// `POST /v1/sites`. When `None`, those endpoints are disabled (404).
pub admin_token: Option<Arc<String>>,
/// Operator-overridden info URLs surfaced in the puzzle response and
/// the 429 block body. `None` when no `INFO_*_URL` env var is set; the
/// widget then uses bundled `/static/*.html` defaults for every field.
pub info_urls: Option<InfoUrls>,
/// Bounds how many Argon2id verifications run at once. Each one allocates
/// `ARGON2_M_COST` KiB (8 MiB by default) for the duration of the hash, and
/// tokio grows its blocking pool to 512 threads on demand — so without a
/// bound, ~500 concurrent `/v1/verify` calls peak around 4 GiB and OOM a
/// small VPS. Permits cap that at `parallelism * 8 MiB`. Requests queue for
/// a permit rather than being refused; the global request timeout is what
/// bounds the queue, so overload degrades into timeouts instead of an OOM.
pub verify_permits: Arc<tokio::sync::Semaphore>,
/// Outage attestation + the accept/refuse decision for client failover
/// claims. Always present; a guard built from a default (disabled) config
/// refuses every claim, so the verify path needs no `Option` handling.
pub failover: Arc<FailoverGuard>,
pub config: AppConfig,
}
/// Concurrency limit for memory-hard verification, as a multiple of available
/// parallelism. Argon2id is CPU- *and* memory-bound, so there's no throughput
/// gained past a small multiple of the core count — the extra concurrency buys
/// only resident memory.
pub const VERIFY_PERMITS_PER_CORE: usize = 2;
/// Permit count for [`AppState::verify_permits`], derived from the host's
/// parallelism. Falls back to a single core's worth when the platform can't
/// report it, which errs toward the safe side.
pub fn verify_permits() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
* VERIFY_PERMITS_PER_CORE
}
/// Build the `info_urls` payload from config. Returns `None` when no
/// override is set so we don't serialize an empty object on every puzzle
/// response.
pub fn info_urls_from_config(config: &AppConfig) -> Option<InfoUrls> {
let urls = InfoUrls {
about: config.info_about_url.clone(),
privacy: config.info_privacy_url.clone(),
terms: config.info_terms_url.clone(),
};
if urls.is_empty() { None } else { Some(urls) }
}