fix(config): resolve critical audit findings from Phase 1-3 review
- C-1: Add tenant_id to settings unique index to prevent cross-tenant conflicts - C-2: Move pg_advisory_xact_lock inside the transaction for correct concurrency (previously lock was released before the numbering transaction started) - H-5: Add CORS middleware (permissive for dev, TODO: restrict in production)
This commit is contained in:
@@ -204,8 +204,8 @@ impl NumberingService {
|
|||||||
/// 线程安全地生成编号。
|
/// 线程安全地生成编号。
|
||||||
///
|
///
|
||||||
/// 使用 PostgreSQL advisory lock 保证并发安全:
|
/// 使用 PostgreSQL advisory lock 保证并发安全:
|
||||||
/// 1. 获取 pg_advisory_xact_lock
|
/// 1. 在事务内获取 pg_advisory_xact_lock
|
||||||
/// 2. 在事务内读取规则、检查重置周期、递增序列、更新数据库
|
/// 2. 在同一事务内读取规则、检查重置周期、递增序列、更新数据库
|
||||||
/// 3. 拼接编号字符串返回
|
/// 3. 拼接编号字符串返回
|
||||||
pub async fn generate_number(
|
pub async fn generate_number(
|
||||||
rule_id: Uuid,
|
rule_id: Uuid,
|
||||||
@@ -223,22 +223,23 @@ impl NumberingService {
|
|||||||
let rule_code = rule.code.clone();
|
let rule_code = rule.code.clone();
|
||||||
let tenant_id_str = tenant_id.to_string();
|
let tenant_id_str = tenant_id.to_string();
|
||||||
|
|
||||||
// 获取 PostgreSQL advisory lock(事务级别,事务结束自动释放)
|
// 在同一个事务内获取 advisory lock 并执行编号生成
|
||||||
db.execute(Statement::from_sql_and_values(
|
// pg_advisory_xact_lock 是事务级别的,锁会在事务结束时自动释放
|
||||||
|
let number = db
|
||||||
|
.transaction(|txn| {
|
||||||
|
let rule_code = rule_code.clone();
|
||||||
|
let tenant_id_str = tenant_id_str.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
// 在事务内获取 advisory lock
|
||||||
|
txn.execute(Statement::from_sql_and_values(
|
||||||
DatabaseBackend::Postgres,
|
DatabaseBackend::Postgres,
|
||||||
"SELECT pg_advisory_xact_lock(abs(hashtext($1)), abs(hashtext($2))::int)",
|
"SELECT pg_advisory_xact_lock(abs(hashtext($1)), abs(hashtext($2))::int)",
|
||||||
[
|
[rule_code.into(), tenant_id_str.into()],
|
||||||
rule_code.into(),
|
|
||||||
tenant_id_str.into(),
|
|
||||||
],
|
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ConfigError::Validation(format!("获取编号锁失败: {e}")))?;
|
.map_err(|e| ConfigError::Validation(format!("获取编号锁失败: {e}")))?;
|
||||||
|
|
||||||
// 在事务内执行序列递增和更新
|
// 在同一个事务内执行编号生成
|
||||||
let number = db
|
|
||||||
.transaction(|txn| {
|
|
||||||
Box::pin(async move {
|
|
||||||
Self::generate_number_in_txn(rule_id, tenant_id, txn).await
|
Self::generate_number_in_txn(rule_id, tenant_id, txn).await
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ impl MigrationTrait for Migration {
|
|||||||
|
|
||||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||||
sea_orm::DatabaseBackend::Postgres,
|
sea_orm::DatabaseBackend::Postgres,
|
||||||
"CREATE UNIQUE INDEX idx_settings_scope_key ON settings (scope, scope_id, setting_key) WHERE deleted_at IS NULL".to_string(),
|
"CREATE UNIQUE INDEX idx_settings_scope_key ON settings (tenant_id, scope, scope_id, setting_key) WHERE deleted_at IS NULL".to_string(),
|
||||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -153,7 +153,11 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.with_state(state.clone());
|
.with_state(state.clone());
|
||||||
|
|
||||||
// Merge public + protected into the final application router
|
// Merge public + protected into the final application router
|
||||||
let app = Router::new().merge(public_routes).merge(protected_routes);
|
let cors = tower_http::cors::CorsLayer::permissive(); // TODO: restrict origins in production
|
||||||
|
let app = Router::new()
|
||||||
|
.merge(public_routes)
|
||||||
|
.merge(protected_routes)
|
||||||
|
.layer(cors);
|
||||||
|
|
||||||
let addr = format!("{}:{}", host, port);
|
let addr = format!("{}:{}", host, port);
|
||||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||||
|
|||||||
Reference in New Issue
Block a user