feat: implement upload error handling and rate limiting improvements

This commit is contained in:
2026-01-16 11:23:14 +01:00
parent e90c4576a5
commit 1d75df2d41
5 changed files with 94 additions and 27 deletions

View File

@@ -1,6 +1,8 @@
use actix_web::http::header;
use actix_web::{HttpRequest, HttpResponse, get, post, web};
use base64::{Engine, engine::general_purpose};
use chrono::Utc;
use serde::Deserialize;
use serde_json::json;
@@ -9,6 +11,7 @@ use crate::{
data_mgt::{AppState, Asset},
logs::{LogEvent, LogEventType, log_event},
};
use crate::{MAX_ASSET_DURATION, MIN_ASSET_DURATION};
#[derive(Deserialize, Debug)]
pub struct UploadRequest {
@@ -24,6 +27,8 @@ async fn api_upload(
app_state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> {
// Check for rate limiting
let now = Utc::now().timestamp_millis();
let connection_info = req.connection_info();
let uploader_ip = connection_info
@@ -32,25 +37,34 @@ async fn api_upload(
.or_else(|| connection_info.peer_addr().map(|value| value.to_string()))
.ok_or_else(|| actix_web::error::ErrorBadRequest("Cannot determine client ip"))?;
match app_state.connection_tracker.is_allowed(&uploader_ip).await {
true => {}
false => {
return Ok(HttpResponse::TooManyRequests().body("Upload limit exceeded"));
}
}
// Convert to bytes
let content_bytes = if body.content_type == "text/plain" {
body.content.as_bytes().to_vec()
} else {
match general_purpose::STANDARD.decode(&body.content) {
Ok(bytes) => bytes,
Err(_) => return Ok(HttpResponse::BadRequest().body("Invalid base64 payload")),
Err(_) => return Ok(HttpResponse::BadRequest().json(json!({ "error": "Invalid base64 content" }))),
}
};
let clamped_duration = body.duration.clamp(MIN_ASSET_DURATION, MAX_ASSET_DURATION);
let asset_expiration_time = now + (clamped_duration as i64 * 60 * 1000);
let (allowed, retry_after_ms) = app_state
.connection_tracker
.check(&uploader_ip, asset_expiration_time)
.await;
if !allowed {
let retry_after_seconds = retry_after_ms.map(|ms| ((ms + 999) / 1000).max(1));
let response_body = match retry_after_seconds {
Some(seconds) => json!({ "error": "Upload limit exceeded", "retry_after_seconds": seconds }),
None => json!({ "error": "Upload limit exceeded" }),
};
// return Ok(HttpResponse::TooManyRequests().body("Upload limit exceeded"));
return Ok(HttpResponse::TooManyRequests().json(response_body));
}
let asset = crate::data_mgt::Asset::new(
body.duration,
clamped_duration,
body.content_type.clone(),
content_bytes,
Some(uploader_ip.clone()),