Safety commit, resolved 14/10 second telegram API timeout, password governer builder & tower governer .key_extrator() error.

This commit is contained in:
unknown
2026-05-22 12:49:33 +02:00
parent a0f7efcd34
commit 6d004f2a65
3 changed files with 66 additions and 12 deletions

View File

@@ -182,7 +182,11 @@ async fn run_bot() {
master_key: Arc::new(master_key), moderation, pipeline, sem, 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"); info!("Bot started");
let handler = dptree::entry() let handler = dptree::entry()
@@ -684,7 +688,7 @@ async fn refresh_options_message(
let keyboard = InlineKeyboardMarkup::new(vec![ let keyboard = InlineKeyboardMarkup::new(vec![
vec![ vec![
InlineKeyboardButton::callback("[ Toggle Destroy ]", "v1:opt:toggle_destroy"), 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![ vec![
InlineKeyboardButton::callback("[ Set Password ]", "v1:opt:set_password"), InlineKeyboardButton::callback("[ Set Password ]", "v1:opt:set_password"),

View File

@@ -13,6 +13,7 @@ use cgcx_crypto::{unwrap_content_key, DecryptStream, MasterKey};
use cgcx_db::{Database, ContentRepo, ContentFileRepo}; use cgcx_db::{Database, ContentRepo, ContentFileRepo};
use cgcx_storage::Storage; use cgcx_storage::Storage;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net::IpAddr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
@@ -168,15 +169,19 @@ async fn main() -> cgcx_core::Result<()> {
allowed_roots, allowed_roots,
}; };
let governor_conf = tower_governor::governor::GovernorConfigBuilder::default() let mut governor_builder = tower_governor::governor::GovernorConfigBuilder::default();
.period(Duration::from_secs(60) / config.rate_limiting.requests_per_minute) let mut governor_builder = governor_builder.key_extractor(CgcxKeyExtractor);
.burst_size(config.rate_limiting.burst) 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() .finish()
.expect("invalid general rate limit config"); .expect("invalid general rate limit config");
let password_governor_conf = tower_governor::governor::GovernorConfigBuilder::default() let mut password_governor_builder = tower_governor::governor::GovernorConfigBuilder::default();
.period(Duration::from_secs(60) / config.rate_limiting.password_attempts_per_minute) let mut password_governor_builder = password_governor_builder.key_extractor(CgcxKeyExtractor);
.burst_size(3) 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() .finish()
.expect("invalid password rate limit config"); .expect("invalid password rate limit config");
@@ -568,7 +573,7 @@ async fn serve_file(
return Ok(Response::builder() return Ok(Response::builder()
.status(StatusCode::GONE) .status(StatusCode::GONE)
.body(Body::empty()) .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; use subtle::ConstantTimeEq;
mac_bytes.ct_eq(&expected).into() 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())
}
}

View File

@@ -44,11 +44,11 @@
<main class="home"> <main class="home">
<div class="hero"> <div class="hero">
<h1 class="retro-heading">CG.CX</h1> <h1 class="retro-heading"><a href="https://cg.cx/">CG.CX</a></h1>
<p class="cg-subtitle">-- cannibal girls --</p>
</div> </div>
<div class="panel"> <div class="panel">
<p class="cg-subtitle">-- cannibal girls --</p>
<label for="cxid">Content ID</label> <label for="cxid">Content ID</label>
<input id="cxid" type="text" bind:value={cxidInput} placeholder="Enter content ID..." onkeydown={onKeydown} /> <input id="cxid" type="text" bind:value={cxidInput} placeholder="Enter content ID..." onkeydown={onKeydown} />
@@ -86,12 +86,17 @@
.hero { .hero {
text-align: center; text-align: center;
} }
.hero a {
text-decoration: none;
color: inherit;
}
.cg-subtitle { .cg-subtitle {
font-family: 'Press Start 2P', cursive; font-family: 'Press Start 2P', cursive;
font-size: 0.9rem; font-size: 0.9rem;
text-align: center; text-align: center;
letter-spacing: 2px; letter-spacing: 2px;
margin-top: 8px; margin-top: 0;
margin-bottom: 8px;
background: linear-gradient(90deg, var(--retro-green), var(--retro-green-light)); background: linear-gradient(90deg, var(--retro-green), var(--retro-green-light));
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;