feat(db): RLS 策略迁移 — 80 张 tenant_id 表启用行级安全

- 所有含 tenant_id 的表(基础 34 + 健康 28 + 其他 18)启用 RLS
- 策略:未设置 app.current_tenant_id 时允许全部,设置后按 tenant_id 过滤
- down 方法完整回退(DROP POLICY + DISABLE ROW LEVEL SECURITY)
This commit is contained in:
iven
2026-04-27 18:40:07 +08:00
parent 3197dde33c
commit b7b9f50d00
2 changed files with 146 additions and 0 deletions

View File

@@ -85,6 +85,7 @@ mod m20260427_000082_seed_ai_prompts;
mod m20260427_000083_create_follow_up_template;
mod m20260427_000084_domain_events_cleanup;
mod m20260427_000085_processed_events;
mod m20260427_000086_enable_rls_all_tables;
pub struct Migrator;
@@ -177,6 +178,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260427_000083_create_follow_up_template::Migration),
Box::new(m20260427_000084_domain_events_cleanup::Migration),
Box::new(m20260427_000085_processed_events::Migration),
Box::new(m20260427_000086_enable_rls_all_tables::Migration),
]
}
}

View File

@@ -0,0 +1,144 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
/// 所有含 tenant_id 的表 — RLS 策略统一启用
const TENANT_TABLES: &[&str] = &[
// ── 基础层 (ERP Core) ──────────────────────────
"users",
"user_credentials",
"user_tokens",
"roles",
"permissions",
"role_permissions",
"user_roles",
"organizations",
"departments",
"positions",
"dictionaries",
"dictionary_items",
"menus",
"menu_roles",
"settings",
"numbering_rules",
"process_definitions",
"process_instances",
"tokens",
"tasks",
"process_variables",
"message_templates",
"messages",
"message_subscriptions",
"audit_logs",
"domain_events",
"plugins",
"plugin_entities",
"plugin_entity_columns",
"user_departments",
"plugin_user_views",
"plugin_market_reviews",
"tenant_crypto_keys",
"domain_events_archive",
// ── 健康层 (erp-health) ────────────────────────
"patient",
"patient_family_member",
"patient_tag",
"patient_tag_relation",
"doctor_profile",
"patient_doctor_relation",
"health_record",
"vital_signs",
"lab_report",
"health_trend",
"appointment",
"doctor_schedule",
"follow_up_task",
"follow_up_record",
"consultation_session",
"consultation_message",
"dialysis_record",
"daily_monitoring",
"diagnosis",
"critical_value_threshold",
"consent",
"device_readings",
"vital_signs_hourly",
"patient_devices",
"alert_rules",
"alerts",
"medication_record",
"dialysis_prescription",
// ── AI + 积分 + 内容 + 微信 ────────────────────
"ai_prompts",
"ai_analysis_results",
"ai_usage_logs",
"points_account",
"points_rule",
"points_transaction",
"points_product",
"points_order",
"points_checkin",
"offline_event",
"offline_event_registration",
"wechat_users",
"article",
"article_category",
"article_tag",
"article_revision",
"follow_up_template",
"follow_up_template_step",
];
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let conn = manager.get_connection();
// 创建自定义配置参数仅用于文档PostgreSQL 自动支持 current_setting
// 策略设计:
// - current_setting('app.current_tenant_id', true) = '' → 未设置,允许所有(兼容迁移/后台任务)
// - 设置为 UUID → 仅允许匹配 tenant_id 的行
for table in TENANT_TABLES {
// 启用 RLS
conn.execute_unprepared(&format!(
"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY"
)).await?;
// 移除旧策略(如果存在,支持重复执行迁移)
conn.execute_unprepared(&format!(
"DROP POLICY IF EXISTS tenant_isolation ON {table}"
)).await?;
// 租户隔离策略 — 未设置参数时允许所有,设置后按 tenant_id 过滤
conn.execute_unprepared(&format!(
"CREATE POLICY tenant_isolation ON {table} USING (
current_setting('app.current_tenant_id', true) = ''
OR tenant_id = current_setting('app.current_tenant_id', true)::uuid
)"
)).await?;
// 超级用户绕过策略(数据库 owner 直接绕过 RLS
// PostgreSQL 默认表 owner 绕过 RLS无需额外策略
}
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let conn = manager.get_connection();
for table in TENANT_TABLES {
conn.execute_unprepared(&format!(
"DROP POLICY IF EXISTS tenant_isolation ON {table}"
)).await?;
conn.execute_unprepared(&format!(
"ALTER TABLE {table} DISABLE ROW LEVEL SECURITY"
)).await?;
}
Ok(())
}
}