charging-cabinet/tasks/010-security-fixes.md

156 lines
4.4 KiB
Markdown
Raw Normal View 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改为参数化查询。
```rust
// 错误示例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密码存储和验证使用哈希。
```rust
// 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`,查询时自动过滤。
```rust
// 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` 调用。
```rust
// 示例
#[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:export``energy:export`
## 质量约束
1. 完成代码后执行 `cargo check` + `cargo clippy`,零报错零警告
2. 分层拆分单函数≤80行命名语义化完整注释
3. 所有外部IO/网络请求异常捕获禁止裸panic
4. 分支逻辑全覆盖,不遗漏兜底分支
5. 常量抽离不使用废弃API
6. Rust内存安全合理管理所有权禁用unsafe无合理理由
7. 修复后重新运行审查清单中的安全项目,确保全部通过