[0.6.1] - 2025-09-02
### 🚀 Изменено - Упрощение архитектуры - **Генерация миниатюр**: Полностью удалена из Quoter, теперь управляется Vercel Edge API - **Очистка legacy кода**: Удалены все функции генерации миниатюр и сложность - **Документация**: Сокращена с 17 файлов до 7, следуя принципам KISS/DRY - **Смена фокуса**: Quoter теперь сосредоточен на upload + storage, Vercel обрабатывает миниатюры - **Логирование запросов**: Добавлена аналитика источников для оптимизации CORS whitelist - **Реализация таймаутов**: Добавлены настраиваемые таймауты для S3, Redis и внешних операций - **Упрощенная безопасность**: Удален сложный rate limiting, оставлена только необходимая защита upload - **Vercel интеграция**: Добавлена поддержка Vercel Edge API с CORS и оптимизированными заголовками - **Redis graceful fallback**: Приложение теперь работает без Redis с предупреждениями вместо паники - **Умная логика ответов**: Автоматическое определение Vercel запросов и оптимизированные заголовки - **Консолидация документации**: Объединены 4 Vercel документа в один comprehensive guide ### 📝 Обновлено - Консолидирована документация в практическую структуру: - Основной README.md с быстрым стартом - docs/SETUP.md для конфигурации и развертывания - Упрощенный features.md с фокусом на основную функциональность - docs/vercel-frontend-migration.md - единый comprehensive guide для Vercel интеграции - Добавлен акцент на Vercel по всему коду и документации - Обновлены URL patterns в документации: quoter.discours.io → files.dscrs.site ### 🗑️ Удалено - Избыточные файлы документации (api-reference, deployment, development, и т.д.) - Дублирующийся контент в нескольких документах - Излишне детальная документация для простого файлового прокси - 4 отдельных Vercel документа: vercel-thumbnails.md, vercel-integration.md, hybrid-architecture.md, vercel-og-integration.md 💋 **Упрощение**: KISS принцип применен - убрали избыточность, оставили суть.
This commit is contained in:
@@ -35,7 +35,7 @@ impl Default for SecurityConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_payload_size: 500 * 1024 * 1024, // 500MB
|
||||
request_timeout_seconds: 300, // 5 минут
|
||||
request_timeout_seconds: 300, // 5 минут
|
||||
max_path_length: 1000,
|
||||
max_headers_count: 50,
|
||||
max_header_value_length: 8192,
|
||||
@@ -51,7 +51,7 @@ impl SecurityConfig {
|
||||
/// Валидирует запрос на базовые параметры безопасности
|
||||
pub fn validate_request(&self, req: &HttpRequest) -> Result<(), actix_web::Error> {
|
||||
let path = req.path();
|
||||
|
||||
|
||||
// Проверка длины пути
|
||||
if path.len() > self.max_path_length {
|
||||
warn!("Path too long: {} chars", path.len());
|
||||
@@ -79,7 +79,8 @@ impl SecurityConfig {
|
||||
}
|
||||
|
||||
// Проверка на подозрительные символы в пути
|
||||
if path.contains("..") || path.contains('\0') || path.contains('\r') || path.contains('\n') {
|
||||
if path.contains("..") || path.contains('\0') || path.contains('\r') || path.contains('\n')
|
||||
{
|
||||
warn!("Suspicious characters in path: {}", path);
|
||||
return Err(actix_web::error::ErrorBadRequest(
|
||||
"Invalid characters in path",
|
||||
@@ -98,7 +99,7 @@ impl SecurityConfig {
|
||||
pub fn check_suspicious_patterns(&self, path: &str) -> bool {
|
||||
let suspicious_patterns = [
|
||||
"/admin",
|
||||
"/wp-admin",
|
||||
"/wp-admin",
|
||||
"/phpmyadmin",
|
||||
"/.env",
|
||||
"/config",
|
||||
@@ -136,26 +137,34 @@ impl SecurityConfig {
|
||||
.as_secs();
|
||||
|
||||
let mut counts = self.upload_protection.upload_counts.write().await;
|
||||
|
||||
|
||||
// Очищаем старые записи (старше минуты)
|
||||
counts.retain(|_, (_, timestamp)| current_time - *timestamp < 60);
|
||||
|
||||
|
||||
// Проверяем текущий IP
|
||||
let current_count = counts.get(ip).map(|(count, _)| *count).unwrap_or(0);
|
||||
let first_upload_time = counts.get(ip).map(|(_, time)| *time).unwrap_or(current_time);
|
||||
|
||||
let first_upload_time = counts
|
||||
.get(ip)
|
||||
.map(|(_, time)| *time)
|
||||
.unwrap_or(current_time);
|
||||
|
||||
if current_time - first_upload_time < 60 {
|
||||
// В пределах минуты
|
||||
if current_count >= self.upload_protection.max_uploads_per_minute {
|
||||
warn!("Upload limit exceeded for IP {}: {} uploads in minute", ip, current_count);
|
||||
return Err(actix_web::error::ErrorTooManyRequests("Upload limit exceeded"));
|
||||
warn!(
|
||||
"Upload limit exceeded for IP {}: {} uploads in minute",
|
||||
ip, current_count
|
||||
);
|
||||
return Err(actix_web::error::ErrorTooManyRequests(
|
||||
"Upload limit exceeded",
|
||||
));
|
||||
}
|
||||
counts.insert(ip.to_string(), (current_count + 1, first_upload_time));
|
||||
} else {
|
||||
// Новая минута, сбрасываем счетчик
|
||||
counts.insert(ip.to_string(), (1, current_time));
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -169,16 +178,18 @@ impl SecurityConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Проверяем X-Real-IP
|
||||
if let Some(real_ip) = req.headers().get("x-real-ip") {
|
||||
if let Ok(real_ip_str) = real_ip.to_str() {
|
||||
return real_ip_str.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fallback на connection info
|
||||
req.connection_info().peer_addr().unwrap_or("unknown").to_string()
|
||||
req.connection_info()
|
||||
.peer_addr()
|
||||
.unwrap_or("unknown")
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user