security(music): ensure read permission via authz

This commit is contained in:
Edouard Vanbelle
2026-07-04 23:31:10 +02:00
parent 2cda8e7e22
commit b95e740b2f
7 changed files with 280 additions and 65 deletions
@@ -6,7 +6,7 @@ use axum::{
};
use serde::Deserialize;
use std::sync::Arc;
use tracing::{error, info};
use tracing::info;
use utoipa::ToSchema;
use crate::application::dtos::display_helpers::{
@@ -66,7 +66,8 @@ pub async fn add_favorite(
Json(serde_json::json!({
"error": "Item type must be 'file' or 'folder'"
})),
);
)
.into_response();
}
match favorites_service
@@ -81,16 +82,14 @@ pub async fn add_favorite(
"message": "Item added to favorites"
})),
)
.into_response()
}
Err(err) => {
error!("Error adding to favorites: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": "Failed to add to favorites"
})),
)
}
// Route through AppError so the `DomainError::kind` maps to the
// right status code (NotFound → 404 anti-enum for the pre-write
// authz gate, InvalidInput → 400 for a malformed UUID, etc.).
// A hardcoded 500 here would mask the 404 the Round 1 AuthZ
// fix relies on.
Err(err) => AppError::from(err).into_response(),
}
}
@@ -129,6 +128,7 @@ pub async fn remove_favorite(
"message": "Item removed from favorites"
})),
)
.into_response()
} else {
info!("Item {} '{}' was not in favorites", item_type, item_id);
(
@@ -137,17 +137,12 @@ pub async fn remove_favorite(
"message": "Item was not in favorites"
})),
)
.into_response()
}
}
Err(err) => {
error!("Error removing from favorites: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": "Failed to remove from favorites"
})),
)
}
// Same rationale as `add_favorite` — preserve DomainError→HTTP
// status mapping instead of collapsing every error to 500.
Err(err) => AppError::from(err).into_response(),
}
}
@@ -347,15 +342,10 @@ pub async fn batch_add_favorites(
);
(StatusCode::OK, Json(serde_json::json!(result))).into_response()
}
Err(err) => {
error!("Error in batch add favorites: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": "Failed to batch add favorites"
})),
)
.into_response()
}
// Preserve DomainError→HTTP status mapping — the Round 1
// AuthZ fix relies on a per-item NotFound propagating out
// of the batch. A hardcoded 500 would mask the 404 that
// signals a cross-tenant probe.
Err(err) => AppError::from(err).into_response(),
}
}
+11 -31
View File
@@ -5,7 +5,7 @@ use axum::{
response::IntoResponse,
};
use std::sync::Arc;
use tracing::{error, info};
use tracing::info;
use crate::application::dtos::display_helpers::{
category_for, format_file_size, icon_class_for, icon_special_class_for,
@@ -70,16 +70,10 @@ pub async fn record_item_access(
)
.into_response()
}
Err(err) => {
error!("Error recording access in recents: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": "Failed to record access"
})),
)
.into_response()
}
// Preserve DomainError→HTTP status mapping — the Round 1
// AuthZ fix relies on the NotFound from `authz.require`
// propagating as 404 (anti-enum), not being masked as 500.
Err(err) => AppError::from(err).into_response(),
}
}
@@ -130,16 +124,9 @@ pub async fn remove_from_recent(
.into_response()
}
}
Err(err) => {
error!("Error removing from recents: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": "Failed to remove from recents"
})),
)
.into_response()
}
// Same rationale as `record_item_access` — preserve the
// DomainError→HTTP mapping instead of collapsing to 500.
Err(err) => AppError::from(err).into_response(),
}
}
@@ -170,16 +157,9 @@ pub async fn clear_recent_items(
)
.into_response()
}
Err(err) => {
error!("Error clearing recent items: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": "Failed to clear recent items"
})),
)
.into_response()
}
// Same rationale as `record_item_access` — preserve the
// DomainError→HTTP mapping instead of collapsing to 500.
Err(err) => AppError::from(err).into_response(),
}
}