diff --git a/server/src/routes/case_actions.rs b/server/src/routes/case_actions.rs index c8ba937..b95ec98 100644 --- a/server/src/routes/case_actions.rs +++ b/server/src/routes/case_actions.rs @@ -20,7 +20,7 @@ use crate::case_id::CaseIdPath; use crate::config::Config; use crate::error::AppError; use crate::paths::{read_delete_marker, write_delete_marker, DeleteMarker, DELETE_MARKER}; -use crate::routes::user_web::locate_case; +use crate::routes::user_web::locate_case_or_404; #[derive(Template)] #[template(path = "document.html")] @@ -49,13 +49,7 @@ pub async fn handle_analyze_case( } let user_root = config.data_path.join(&user.slug); - let case_dir = match locate_case(&user_root, &case_id.to_string()).await { - Some(p) => p, - None => { - warn!(slug = %user.slug, case_id = %case_id, "analyze: case not found"); - return Err(AppError::NotFound("Case not found".into())); - } - }; + let case_dir = locate_case_or_404(&user_root, &case_id, &user.slug, "analyze").await?; // Re-analyze: delete the existing document first so the UI stops showing // the stale "ausgewertet" state while the worker re-computes. Ignore @@ -103,13 +97,7 @@ pub async fn handle_document_view( CaseIdPath(case_id): CaseIdPath, ) -> Result, AppError> { let user_root = config.data_path.join(&user.slug); - let case_dir = match locate_case(&user_root, &case_id.to_string()).await { - Some(p) => p, - None => { - warn!(slug = %user.slug, case_id = %case_id, "document view: case not found"); - return Err(AppError::NotFound("Case not found".into())); - } - }; + let case_dir = locate_case_or_404(&user_root, &case_id, &user.slug, "document view").await?; let md = read_document(&case_dir) .await @@ -272,13 +260,7 @@ pub async fn handle_delete_case( CaseIdPath(case_id): CaseIdPath, ) -> Result { let user_root = config.data_path.join(&user.slug); - let case_dir = match locate_case(&user_root, &case_id.to_string()).await { - Some(p) => p, - None => { - warn!(slug = %user.slug, case_id = %case_id, "delete: case not found"); - return Err(AppError::NotFound("Case not found".into())); - } - }; + let case_dir = locate_case_or_404(&user_root, &case_id, &user.slug, "delete").await?; let marker = DeleteMarker { batch: uuid::Uuid::new_v4(), @@ -313,13 +295,7 @@ pub async fn handle_reset_case( } let user_root = config.data_path.join(&user.slug); - let case_dir = match locate_case(&user_root, &case_id.to_string()).await { - Some(p) => p, - None => { - warn!(slug = %user.slug, case_id = %case_id, "reset: case not found"); - return Err(AppError::NotFound("Case not found".into())); - } - }; + let case_dir = locate_case_or_404(&user_root, &case_id, &user.slug, "reset").await?; reset_case_artefacts(&case_dir) .await diff --git a/server/src/routes/user_web.rs b/server/src/routes/user_web.rs index c047cf2..e136674 100644 --- a/server/src/routes/user_web.rs +++ b/server/src/routes/user_web.rs @@ -11,7 +11,7 @@ use time::{format_description::well_known::Rfc3339, Date, OffsetDateTime}; use crate::analyze::{recovery as analyze_recovery, AnalyzeSender, ANALYSIS_INPUT_FILE, DOCUMENT_FILE}; use crate::auth::AuthenticatedWebUser; -use crate::case_id::CaseIdPath; +use crate::case_id::{CaseId, CaseIdPath}; use crate::config::Config; use crate::error::AppError; use crate::routes::web::{scan_recordings, RecordingView}; @@ -278,14 +278,14 @@ pub async fn handle_case_detail( ) .await; + let case_dir = locate_case_or_404( + &user_root, + &case_id, + &user.slug, + "case detail (possible IDOR probe)", + ) + .await?; let case_id_str = case_id.to_string(); - let case_dir = match locate_case(&user_root, &case_id_str).await { - Some(p) => p, - None => { - warn!(slug = %user.slug, case_id = %case_id, "case detail: not found (possible IDOR probe)"); - return Err(AppError::NotFound("Case not found".into())); - } - }; info!(slug = %user.slug, case_id = %case_id, "case detail viewed"); let recordings = scan_recordings(&case_dir).await; @@ -333,6 +333,32 @@ pub(crate) async fn locate_case(user_root: &Path, case_id: &str) -> Option Result { + match locate_case(user_root, &case_id.to_string()).await { + Some(p) => Ok(p), + None => { + warn!( + slug = %user_slug, + case_id = %case_id, + context = %context, + "case not found", + ); + Err(AppError::NotFound("Case not found".into())) + } + } +} + /// Scan all of the given user's cases. Returned vec is sorted by most /// recent recording filename (lexicographic ≈ chronological since /// timestamps are ISO-8601), newest first. Soft-deleted cases (with