From bd83cff58f34898469f6e5687020bd6ab4160ae7 Mon Sep 17 00:00:00 2001 From: Kayphoon <109347466+Kayphoon@users.noreply.github.com> Date: Thu, 24 Sep 2026 08:17:46 +0000 Subject: [PATCH] fix(admin): buffer request body for manual cleanup, smtp test, and system update routes --- .../src/control/tests/admin_core.rs | 43 ++++++++++++++++++- .../src/handlers/shared/request_utils.rs | 27 ++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/apps/aether-gateway/src/control/tests/admin_core.rs b/apps/aether-gateway/src/control/tests/admin_core.rs index 1b8808419..f4979763e 100644 --- a/apps/aether-gateway/src/control/tests/admin_core.rs +++ b/apps/aether-gateway/src/control/tests/admin_core.rs @@ -1,6 +1,7 @@ use http::Uri; -use crate::control::management_token_required_permission; +use crate::control::{management_token_required_permission, GatewayPublicRequestContext}; +use crate::handlers::shared::local_proxy_route_requires_buffered_body; use super::{classify_control_route, headers}; @@ -206,6 +207,10 @@ fn classifies_admin_system_maintenance_write_routes_as_admin_proxy_route() { "/api/admin/system/important-notification/test", "important_notification_test", ), + ( + "/api/admin/system/cleanup/usage/manual", + "cleanup_usage_manual", + ), ("/api/admin/system/cleanup", "cleanup"), ("/api/admin/system/purge/config", "purge_config"), ("/api/admin/system/purge/users", "purge_users"), @@ -235,6 +240,28 @@ fn classifies_admin_system_maintenance_write_routes_as_admin_proxy_route() { Some("admin:system") ); assert!(!decision.is_execution_runtime_candidate()); + + if matches!( + expected_kind, + "config_import" + | "users_import" + | "data_import" + | "smtp_test" + | "important_notification_test" + | "cleanup_usage_manual" + ) { + let context = GatewayPublicRequestContext::from_request_parts( + "trace-system-maintenance-write", + &http::Method::POST, + &uri, + &headers, + Some(decision), + ); + assert!( + local_proxy_route_requires_buffered_body(&context), + "POST {path} should buffer request body" + ); + } } } @@ -303,6 +330,20 @@ fn classifies_admin_system_update_routes_as_admin_proxy_routes() { Some("admin:system") ); assert!(!decision.is_execution_runtime_candidate()); + + if matches!(expected_kind, "prepare_update" | "apply_update") { + let context = GatewayPublicRequestContext::from_request_parts( + "trace-system-update-write", + &method, + &uri, + &headers, + Some(decision), + ); + assert!( + local_proxy_route_requires_buffered_body(&context), + "{method} {path} should buffer request body" + ); + } } } diff --git a/apps/aether-gateway/src/handlers/shared/request_utils.rs b/apps/aether-gateway/src/handlers/shared/request_utils.rs index 62bec53aa..549d3a008 100644 --- a/apps/aether-gateway/src/handlers/shared/request_utils.rs +++ b/apps/aether-gateway/src/handlers/shared/request_utils.rs @@ -275,6 +275,10 @@ pub(crate) fn admin_proxy_local_requires_buffered_body( | (Some("system_manage"), http::Method::POST, Some("config_import")) | (Some("system_manage"), http::Method::POST, Some("users_import")) | (Some("system_manage"), http::Method::POST, Some("data_import")) + | (Some("system_manage"), http::Method::POST, Some("cleanup_usage_manual")) + | (Some("system_manage"), http::Method::POST, Some("smtp_test")) + | (Some("system_manage"), http::Method::POST, Some("prepare_update")) + | (Some("system_manage"), http::Method::POST, Some("apply_update")) | (Some("system_manage"), http::Method::PUT, Some("settings_set")) | (Some("system_manage"), http::Method::PUT, Some("config_set")) | (Some("system_manage"), http::Method::PUT, Some("email_template_set")) @@ -609,4 +613,27 @@ mod tests { "/v1/chat/completions?key=passthrough" ); } + + #[test] + fn manual_cleanup_route_requires_buffered_body() { + use crate::control::GatewayPublicRequestContext; + let mut decision = GatewayControlDecision::synthetic( + "/api/admin/system/cleanup/usage/manual", + Some("admin_proxy".to_string()), + Some("system_manage".to_string()), + Some("cleanup_usage_manual".to_string()), + Some("system_manage:cleanup_usage_manual".to_string()), + ); + decision.route_class = Some("admin_proxy".to_string()); + let uri: http::Uri = "/api/admin/system/cleanup/usage/manual".parse().unwrap(); + let headers = http::HeaderMap::new(); + let context = GatewayPublicRequestContext::from_request_parts( + "trace-manual-cleanup", + &http::Method::POST, + &uri, + &headers, + Some(decision), + ); + assert!(super::admin_proxy_local_requires_buffered_body(&context)); + } }