Safety commit, resolved 14/10 second telegram API timeout, password governer builder & tower governer .key_extrator() error.
This commit is contained in:
@@ -13,6 +13,7 @@ use cgcx_crypto::{unwrap_content_key, DecryptStream, MasterKey};
|
||||
use cgcx_db::{Database, ContentRepo, ContentFileRepo};
|
||||
use cgcx_storage::Storage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::io::AsyncReadExt;
|
||||
@@ -168,15 +169,19 @@ async fn main() -> cgcx_core::Result<()> {
|
||||
allowed_roots,
|
||||
};
|
||||
|
||||
let governor_conf = tower_governor::governor::GovernorConfigBuilder::default()
|
||||
.period(Duration::from_secs(60) / config.rate_limiting.requests_per_minute)
|
||||
.burst_size(config.rate_limiting.burst)
|
||||
let mut governor_builder = tower_governor::governor::GovernorConfigBuilder::default();
|
||||
let mut governor_builder = governor_builder.key_extractor(CgcxKeyExtractor);
|
||||
governor_builder.period(Duration::from_secs(60) / config.rate_limiting.requests_per_minute);
|
||||
governor_builder.burst_size(config.rate_limiting.burst);
|
||||
let governor_conf = governor_builder
|
||||
.finish()
|
||||
.expect("invalid general rate limit config");
|
||||
|
||||
let password_governor_conf = tower_governor::governor::GovernorConfigBuilder::default()
|
||||
.period(Duration::from_secs(60) / config.rate_limiting.password_attempts_per_minute)
|
||||
.burst_size(3)
|
||||
let mut password_governor_builder = tower_governor::governor::GovernorConfigBuilder::default();
|
||||
let mut password_governor_builder = password_governor_builder.key_extractor(CgcxKeyExtractor);
|
||||
password_governor_builder.period(Duration::from_secs(60) / config.rate_limiting.password_attempts_per_minute);
|
||||
password_governor_builder.burst_size(3);
|
||||
let password_governor_conf = password_governor_builder
|
||||
.finish()
|
||||
.expect("invalid password rate limit config");
|
||||
|
||||
@@ -568,7 +573,7 @@ async fn serve_file(
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::GONE)
|
||||
.body(Body::empty())
|
||||
.unwrap());
|
||||
.map_err(|e| CgcxError::Storage(format!("response build failed: {}", e)))?);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -797,3 +802,43 @@ fn verify_cookie(cxid: &str, cookie_value: &str, secret: &[u8]) -> bool {
|
||||
use subtle::ConstantTimeEq;
|
||||
mac_bytes.ct_eq(&expected).into()
|
||||
}
|
||||
|
||||
// Custom key extractor for tower_governor that never fails with UnableToExtractKey.
|
||||
// It tries forwarded headers, then ConnectInfo, then falls back to User-Agent or a global key.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct CgcxKeyExtractor;
|
||||
|
||||
impl tower_governor::key_extractor::KeyExtractor for CgcxKeyExtractor {
|
||||
type Key = String;
|
||||
|
||||
fn extract<T>(&self, req: &axum::http::Request<T>) -> Result<Self::Key, tower_governor::errors::GovernorError> {
|
||||
// 1. Try X-Forwarded-For header (reverse proxy)
|
||||
if let Some(xff) = req.headers().get("x-forwarded-for") {
|
||||
if let Ok(s) = xff.to_str() {
|
||||
if let Some(ip) = s.split(',').next() {
|
||||
return Ok(ip.trim().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. Try X-Real-Ip header
|
||||
if let Some(xri) = req.headers().get("x-real-ip") {
|
||||
if let Ok(s) = xri.to_str() {
|
||||
if let Ok(ip) = s.parse::<IpAddr>() {
|
||||
return Ok(ip.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3. Try ConnectInfo extension (direct connections)
|
||||
if let Some(addr) = req.extensions().get::<axum::extract::ConnectInfo<std::net::SocketAddr>>() {
|
||||
return Ok(addr.ip().to_string());
|
||||
}
|
||||
// 4. Fall back to User-Agent so different browsers get different buckets
|
||||
if let Some(ua) = req.headers().get("user-agent") {
|
||||
if let Ok(s) = ua.to_str() {
|
||||
return Ok(format!("ua:{}", s));
|
||||
}
|
||||
}
|
||||
// 5. Ultimate fallback: global bucket
|
||||
Ok("global".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user