From 90340725a43d5fe01df4057eb8423f9751fb539f Mon Sep 17 00:00:00 2001 From: iven Date: Fri, 17 Apr 2026 11:45:55 +0800 Subject: [PATCH] =?UTF-8?q?fix(saas):=20admin=5Fguard=5Fmiddleware=20?= =?UTF-8?q?=E2=80=94=20=E9=9D=9E=20admin=20=E7=94=A8=E6=88=B7=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E8=BF=94=E5=9B=9E=20403?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BUG-M4 修复: 之前非 admin 用户发送 malformed body 到 admin 端点时, Axum 先反序列化 body 返回 422,绕过了权限检查。 - 新增 admin_guard_middleware (auth/mod.rs) 在中间件层拦截 - account::admin_routes() 拆分 (dashboard 独立) - billing::admin_routes() + account::admin_routes() 加 guard layer - 非 admin 用户无论 body 是否合法,统一返回 403 --- crates/zclaw-saas/src/account/mod.rs | 7 ++++++- crates/zclaw-saas/src/auth/mod.rs | 21 +++++++++++++++++++++ crates/zclaw-saas/src/main.rs | 9 ++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/crates/zclaw-saas/src/account/mod.rs b/crates/zclaw-saas/src/account/mod.rs index 95e7311..857fd6c 100644 --- a/crates/zclaw-saas/src/account/mod.rs +++ b/crates/zclaw-saas/src/account/mod.rs @@ -16,8 +16,13 @@ pub fn routes() -> axum::Router { .route("/api/v1/tokens", post(handlers::create_token)) .route("/api/v1/tokens/:id", delete(handlers::revoke_token)) .route("/api/v1/logs/operations", get(handlers::list_operation_logs)) - .route("/api/v1/admin/dashboard", get(handlers::dashboard_stats)) .route("/api/v1/devices", get(handlers::list_devices)) .route("/api/v1/devices/register", post(handlers::register_device)) .route("/api/v1/devices/heartbeat", post(handlers::device_heartbeat)) } + +/// Admin-only 路由 (需 admin_guard_middleware 保护) +pub fn admin_routes() -> axum::Router { + axum::Router::new() + .route("/api/v1/admin/dashboard", get(handlers::dashboard_stats)) +} diff --git a/crates/zclaw-saas/src/auth/mod.rs b/crates/zclaw-saas/src/auth/mod.rs index 67f36dc..80fbfbb 100644 --- a/crates/zclaw-saas/src/auth/mod.rs +++ b/crates/zclaw-saas/src/auth/mod.rs @@ -203,6 +203,27 @@ pub async fn auth_middleware( } } +/// Admin 路由守卫中间件: 确保 AuthContext 具有 admin/super_admin 角色 +/// 必须在 auth_middleware 之后使用(依赖 Extension) +pub async fn admin_guard_middleware( + mut req: Request, + next: Next, +) -> Response { + use crate::auth::handlers::check_permission; + + let ctx = req.extensions().get::().cloned(); + match ctx { + Some(ctx) => { + if let Err(e) = check_permission(&ctx, "account:admin") { + e.into_response() + } else { + next.run(req).await + } + } + None => SaasError::Unauthorized.into_response(), + } +} + /// 路由 (无需认证的端点) pub fn routes() -> axum::Router { use axum::routing::post; diff --git a/crates/zclaw-saas/src/main.rs b/crates/zclaw-saas/src/main.rs index 92eca44..d4328c4 100644 --- a/crates/zclaw-saas/src/main.rs +++ b/crates/zclaw-saas/src/main.rs @@ -352,6 +352,10 @@ async fn build_router(state: AppState) -> axum::Router { let protected_routes = zclaw_saas::auth::protected_routes() .merge(zclaw_saas::account::routes()) + .merge( + zclaw_saas::account::admin_routes() + .layer(middleware::from_fn(zclaw_saas::auth::admin_guard_middleware)) + ) .merge(zclaw_saas::model_config::routes()) // relay::routes() 不在此合并 — SSE 端点需要更长超时,在最终 Router 单独合并 .merge(zclaw_saas::migration::routes()) @@ -361,7 +365,10 @@ async fn build_router(state: AppState) -> axum::Router { .merge(zclaw_saas::scheduled_task::routes()) .merge(zclaw_saas::telemetry::routes()) .merge(zclaw_saas::billing::routes()) - .merge(zclaw_saas::billing::admin_routes()) + .merge( + zclaw_saas::billing::admin_routes() + .layer(middleware::from_fn(zclaw_saas::auth::admin_guard_middleware)) + ) .merge(zclaw_saas::knowledge::routes()) .merge(zclaw_saas::industry::routes()) .layer(middleware::from_fn_with_state(