From 6d004f2a65086025da5b7a92daf3b49e9ea5b74427c53569b9ff449a4ced0d47 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 22 May 2026 12:49:33 +0200 Subject: [PATCH] Safety commit, resolved 14/10 second telegram API timeout, password governer builder & tower governer .key_extrator() error. --- crates/cgcx-bot/src/main.rs | 8 +++-- crates/cgcx-server/src/main.rs | 59 +++++++++++++++++++++++++++++---- frontend/src/routes/Home.svelte | 11 ++++-- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/crates/cgcx-bot/src/main.rs b/crates/cgcx-bot/src/main.rs index 67bf652..c0aa138 100644 --- a/crates/cgcx-bot/src/main.rs +++ b/crates/cgcx-bot/src/main.rs @@ -182,7 +182,11 @@ async fn run_bot() { master_key: Arc::new(master_key), moderation, pipeline, sem, }; - let bot = Bot::new(&config.telegram.bot_token); + let client = teloxide::net::default_reqwest_settings() + .timeout(std::time::Duration::from_secs(60)) + .build() + .expect("reqwest client build failed"); + let bot = Bot::with_client(&config.telegram.bot_token, client); info!("Bot started"); let handler = dptree::entry() @@ -684,7 +688,7 @@ async fn refresh_options_message( let keyboard = InlineKeyboardMarkup::new(vec![ vec![ InlineKeyboardButton::callback("[ Toggle Destroy ]", "v1:opt:toggle_destroy"), - InlineKeyboardButton::callback("[ Toggle Download ]", "v1:opt:toggle_download"), + InlineKeyboardButton::callback("[ Toggle DL ]", "v1:opt:toggle_download"), ], vec![ InlineKeyboardButton::callback("[ Set Password ]", "v1:opt:set_password"), diff --git a/crates/cgcx-server/src/main.rs b/crates/cgcx-server/src/main.rs index df68d34..94b24d7 100644 --- a/crates/cgcx-server/src/main.rs +++ b/crates/cgcx-server/src/main.rs @@ -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(&self, req: &axum::http::Request) -> Result { + // 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::() { + return Ok(ip.to_string()); + } + } + } + // 3. Try ConnectInfo extension (direct connections) + if let Some(addr) = req.extensions().get::>() { + 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()) + } +} diff --git a/frontend/src/routes/Home.svelte b/frontend/src/routes/Home.svelte index b3f4281..0f5299d 100644 --- a/frontend/src/routes/Home.svelte +++ b/frontend/src/routes/Home.svelte @@ -44,11 +44,11 @@
-

CG.CX

-

-- cannibal girls --

+

CG.CX

+

-- cannibal girls --

@@ -86,12 +86,17 @@ .hero { text-align: center; } + .hero a { + text-decoration: none; + color: inherit; + } .cg-subtitle { font-family: 'Press Start 2P', cursive; font-size: 0.9rem; text-align: center; letter-spacing: 2px; - margin-top: 8px; + margin-top: 0; + margin-bottom: 8px; background: linear-gradient(90deg, var(--retro-green), var(--retro-green-light)); -webkit-background-clip: text; -webkit-text-fill-color: transparent;