diff --git a/crates/erp-server/src/main.rs b/crates/erp-server/src/main.rs index a10d408..198c0a7 100644 --- a/crates/erp-server/src/main.rs +++ b/crates/erp-server/src/main.rs @@ -556,6 +556,14 @@ async fn main() -> anyhow::Result<()> { async move { jwt_auth_middleware_fn(secret, Some(db), req, next).await } }) }) + // Tenant RLS — 在 JWT 之后执行,SET app.current_tenant_id + .layer({ + let db = state.db.clone(); + axum_middleware::from_fn(move |req, next| { + let db = db.clone(); + async move { middleware::tenant_rls::tenant_rls_middleware(db, req, next).await } + }) + }) .with_state(state.clone()); // Merge public + protected into the final application router diff --git a/crates/erp-server/src/middleware/mod.rs b/crates/erp-server/src/middleware/mod.rs index 382585d..9b7d07e 100644 --- a/crates/erp-server/src/middleware/mod.rs +++ b/crates/erp-server/src/middleware/mod.rs @@ -1 +1,2 @@ pub mod rate_limit; +pub mod tenant_rls; diff --git a/crates/erp-server/src/middleware/tenant_rls.rs b/crates/erp-server/src/middleware/tenant_rls.rs new file mode 100644 index 0000000..318cd75 --- /dev/null +++ b/crates/erp-server/src/middleware/tenant_rls.rs @@ -0,0 +1,49 @@ +use axum::body::Body; +use axum::http::Request; +use axum::middleware::Next; +use axum::response::Response; +use erp_core::types::TenantContext; +use sea_orm::ConnectionTrait; + +/// Tenant RLS 中间件。 +/// +/// 从 request extensions 中提取 `TenantContext`,在数据库连接上设置 +/// `app.current_tenant_id`,使 PostgreSQL RLS 策略自动按租户过滤。 +/// +/// 请求处理完成后自动 RESET 设置,防止连接池复用时泄漏。 +/// +/// SET 失败时仅 warn 不阻断请求(RLS 是安全网,主隔离仍在应用层)。 +pub async fn tenant_rls_middleware( + db: sea_orm::DatabaseConnection, + req: Request
, + next: Next, +) -> Response { + let tenant_id = req.extensions().get::