security: fix vulnerabilities 1-7 from security audit
- Fix #1: Share handler IDOR - enforce owner check on share operations - Fix #2: list_files_query IDOR - bind folder queries to authenticated user - Fix #3: Dedup handler IDOR - restrict dedup operations to file owner - Fix #4: Trash handler OptionalAuthUser - require full AuthUser - Fix #5: Error info leakage - sanitize 500 error responses - Fix #6: Chunked upload IDOR - bind upload sessions to user_id, add verify_session_owner() check on all session operations - Fix #7: CSP unsafe-inline removal - migrate all inline scripts, styles and event handlers to external files, tighten CSP to script-src 'self'; style-src 'self' New files: - static/js/core/theme-init.js (render-blocking theme init) - static/js/core/sw-register.js (service worker registration) - static/css/views/device-verify.css (extracted inline styles) - static/js/views/device-verify/device-verify.js (extracted inline script)
This commit is contained in:
@@ -22,7 +22,7 @@ use crate::application::ports::chunked_upload_ports::DEFAULT_CHUNK_SIZE;
|
||||
use crate::application::ports::file_ports::FileUploadUseCase;
|
||||
use crate::application::ports::storage_ports::StorageUsagePort;
|
||||
use crate::common::di::AppState;
|
||||
use crate::domain::errors::ErrorKind;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::middleware::auth::AuthUser;
|
||||
|
||||
/// Request body for creating an upload session
|
||||
@@ -146,6 +146,7 @@ impl ChunkedUploadHandler {
|
||||
|
||||
match chunked_service
|
||||
.create_session(
|
||||
&auth_user.id,
|
||||
request.filename,
|
||||
request.folder_id,
|
||||
content_type,
|
||||
@@ -157,12 +158,7 @@ impl ChunkedUploadHandler {
|
||||
Ok(response) => (StatusCode::CREATED, Json(response)).into_response(),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to create upload session: {}", e);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})),
|
||||
)
|
||||
AppError::internal_error(format!("Failed to create upload session: {}", e))
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
@@ -177,6 +173,7 @@ impl ChunkedUploadHandler {
|
||||
/// Body: Raw bytes of the chunk
|
||||
pub async fn upload_chunk(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
Path(upload_id): Path<String>,
|
||||
Query(params): Query<ChunkUploadParams>,
|
||||
headers: HeaderMap,
|
||||
@@ -193,7 +190,7 @@ impl ChunkedUploadHandler {
|
||||
});
|
||||
|
||||
match chunked_service
|
||||
.upload_chunk(&upload_id, params.chunk_index, body, checksum)
|
||||
.upload_chunk(&upload_id, &auth_user.id, params.chunk_index, body, checksum)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
@@ -216,22 +213,7 @@ impl ChunkedUploadHandler {
|
||||
.unwrap()
|
||||
.into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
let status = match e.kind {
|
||||
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
||||
ErrorKind::InvalidInput => StatusCode::BAD_REQUEST,
|
||||
ErrorKind::AlreadyExists => StatusCode::CONFLICT,
|
||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
|
||||
(
|
||||
status,
|
||||
Json(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
Err(e) => AppError::from(e).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,11 +222,12 @@ impl ChunkedUploadHandler {
|
||||
/// Returns upload progress and pending chunks
|
||||
pub async fn get_upload_status(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
Path(upload_id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let chunked_service = &state.core.chunked_upload_service;
|
||||
|
||||
match chunked_service.get_status(&upload_id).await {
|
||||
match chunked_service.get_status(&upload_id, &auth_user.id).await {
|
||||
Ok(status) => Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
@@ -261,13 +244,7 @@ impl ChunkedUploadHandler {
|
||||
))
|
||||
.unwrap()
|
||||
.into_response(),
|
||||
Err(e) => (
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})),
|
||||
)
|
||||
.into_response(),
|
||||
Err(e) => AppError::from(e).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +253,7 @@ impl ChunkedUploadHandler {
|
||||
/// Assembles all chunks into the final file and creates the file record
|
||||
pub async fn complete_upload(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
Path(upload_id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let chunked_service = &state.core.chunked_upload_service;
|
||||
@@ -283,22 +261,10 @@ impl ChunkedUploadHandler {
|
||||
|
||||
// Assemble chunks (hash-on-write: SHA-256 computed during assembly)
|
||||
let (assembled_path, filename, folder_id, content_type, total_size, hash) =
|
||||
match chunked_service.complete_upload(&upload_id).await {
|
||||
match chunked_service.complete_upload(&upload_id, &auth_user.id).await {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
let status = match e.kind {
|
||||
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
||||
ErrorKind::InvalidInput | ErrorKind::AlreadyExists => StatusCode::CONFLICT,
|
||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
|
||||
return (
|
||||
status,
|
||||
Json(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
return AppError::from(e).into_response();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -323,7 +289,7 @@ impl ChunkedUploadHandler {
|
||||
{
|
||||
Ok(file) => {
|
||||
// Cleanup session
|
||||
let _ = chunked_service.finalize_upload(&upload_id).await;
|
||||
let _ = chunked_service.finalize_upload(&upload_id, &auth_user.id).await;
|
||||
|
||||
tracing::info!(
|
||||
"✅ CHUNKED UPLOAD COMPLETE: {} (ID: {}, {} bytes)",
|
||||
@@ -345,12 +311,7 @@ impl ChunkedUploadHandler {
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to create file from assembled upload: {:?}", e);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({
|
||||
"error": format!("Failed to create file: {:?}", e)
|
||||
})),
|
||||
)
|
||||
AppError::internal_error(format!("Failed to create file: {}", e))
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
@@ -361,18 +322,14 @@ impl ChunkedUploadHandler {
|
||||
/// Cancels an in-progress upload and cleans up temp files
|
||||
pub async fn cancel_upload(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
Path(upload_id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let chunked_service = &state.core.chunked_upload_service;
|
||||
|
||||
match chunked_service.cancel_upload(&upload_id).await {
|
||||
match chunked_service.cancel_upload(&upload_id, &auth_user.id).await {
|
||||
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(e) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({
|
||||
"error": e.to_string()
|
||||
})),
|
||||
)
|
||||
Err(e) => AppError::internal_error(format!("Failed to cancel upload: {}", e))
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user