charging-cabinet/tasks/010-security-fixes.md
2026-07-02 05:38:01 +08:00

4.4 KiB
Raw Blame History

任务010安全修复

目标

修复代码审查发现的4个严重安全问题。

修复清单

1. SQL注入修复 高优先级)

问题位置:

  • src/routes/operation_logs.rs:49-63
  • src/routes/charge_records.rs:130
  • src/routes/energy_stats.rs:341

修复方案: 将所有 format! 拼接的SQL改为参数化查询。

// 错误示例operation_logs.rs
let sql = format!(
    "SELECT * FROM operation_logs WHERE action = '{}' AND created_at >= '{}'",
    action.replace("'", "''"), start_time
);

// 正确示例
let sql = "SELECT * FROM operation_logs WHERE action = $1 AND created_at >= $2";
sqlx::query_as(sql)
    .bind(&action)
    .bind(&start_time)

涉及文件:

  • src/routes/operation_logs.rs
  • src/routes/charge_records.rs
  • src/routes/energy_stats.rs
  • src/routes/downloads.rs

2. 密码哈希(🔴 高优先级)

问题位置: src/routes/auth.rs:51

修复方案: 集成 argon2 crate密码存储和验证使用哈希。

// Cargo.toml 添加
argon2 = "0.5"

// 密码哈希(创建/重置用户时)
use argon2::{
    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, SaltString},
    Argon2,
};

fn hash_password(password: &str) -> Result<String> {
    let salt = SaltString::generate(&mut OsRng);
    let argon2 = Argon2::default();
    let hash = argon2.hash_password(password.as_bytes(), &salt)?;
    Ok(hash.to_string())
}

// 密码验证(登录时)
fn verify_password(password: &str, hash: &str) -> Result<bool> {
    let parsed_hash = PasswordHash::new(hash)?;
    let argon2 = Argon2::default();
    Ok(argon2.verify_password(password.as_bytes(), &parsed_hash).is_ok())
}

涉及文件:

  • src/routes/auth.rs — 登录验证改用 verify_password
  • src/routes/users.rs — 创建/重置密码改用 hash_password
  • src/db/migrate.rs — users表password字段长度改为255argon2哈希较长

3. 组织级数据隔离(🔴 高优先级)

问题位置:

  • src/routes/organizations.rs 全部路由
  • src/routes/charge_records.rs
  • src/routes/device_logs.rs
  • src/routes/energy_stats.rs

修复方案:CurrentUser 中携带 organization_id,查询时自动过滤。

// middleware/auth.rs 修改
pub struct CurrentUser {
    pub user_id: i64,
    pub role_level: i32,  // 0普通 1企业管理员 2总管理员
    pub organization_id: Option<i64>,  // 新增
    pub permissions: HashSet<String>,
}

// 查询时根据角色自动过滤
pub fn apply_org_filter(query: &str, user: &CurrentUser) -> String {
    if user.role_level >= 2 {
        // 总管理员看全部
        query.to_string()
    } else if let Some(org_id) = user.organization_id {
        // 企业管理员只看本组织
        format!("{} WHERE organization_id = {}", query, org_id)
    } else {
        format!("{} WHERE 1=0", query)  // 无组织用户看不了
    }
}

涉及文件:

  • src/middleware/auth.rs — CurrentUser增加organization_id
  • src/routes/organizations.rs — 所有查询加组织过滤
  • src/routes/charge_records.rs — 加组织过滤
  • src/routes/device_logs.rs — 加组织过滤
  • src/routes/energy_stats.rs — 加组织过滤

4. 补充权限校验(🟡 中优先级)

问题位置:

  • src/routes/charge_records.rs:158
  • src/routes/device_logs.rs:62
  • src/routes/energy_stats.rs:81-283
  • src/routes/downloads.rs:87

修复方案: 为每个路由添加对应的 check_permission 调用。

// 示例
#[get("/api/charge-records")]
async fn list_charge_records(
    user: CurrentUser,
    pool: State<MySqlPool>,
    query: Query<ChargeRecordQuery>,
) -> Result<Json<Vec<ChargeRecord>>> {
    auth::check_permission(&user, "charge_record:view")?;  // 新增
    // ... 原有逻辑
}

权限码对应:

  • 充电记录 → charge_record:view
  • 设备日志 → device_log:view
  • 能耗统计 → energy:view
  • 下载中心 → charge_record:exportenergy:export

质量约束

  1. 完成代码后执行 cargo check + cargo clippy,零报错零警告
  2. 分层拆分单函数≤80行命名语义化完整注释
  3. 所有外部IO/网络请求异常捕获禁止裸panic
  4. 分支逻辑全覆盖,不遗漏兜底分支
  5. 常量抽离不使用废弃API
  6. Rust内存安全合理管理所有权禁用unsafe无合理理由
  7. 修复后重新运行审查清单中的安全项目,确保全部通过