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

156 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 任务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. 修复后重新运行审查清单中的安全项目,确保全部通过