fix(saas): P0 安全修复 + P1 功能补全 — 角色提升、Admin 引导、IP 记录、密码修改
P0 安全修复: - 修复 account update 自角色提升漏洞: 非 admin 用户更新自己时剥离 role 字段 - 添加 Admin 引导机制: accounts 表为空时自动从环境变量创建 super_admin P1 功能补全: - 所有 17 个 log_operation 调用点传入真实客户端 IP (ConnectInfo + X-Forwarded-For) - AuthContext 新增 client_ip 字段, middleware 层自动提取 - main.rs 使用 into_make_service_with_connect_info 启用 SocketAddr 注入 - 新增 PUT /api/v1/auth/password 密码修改端点 (验证旧密码 + argon2 哈希) - 桌面端 SaaS 设置页添加密码修改 UI (折叠式表单) - SaaSClient 添加 changePassword() 方法 - 集成测试修复: 注入模拟 ConnectInfo 适配 onshot 测试模式
This commit is contained in:
@@ -228,6 +228,7 @@ pub async fn init_db(database_url: &str) -> SaasResult<SqlitePool> {
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
sqlx::query(SEED_ROLES).execute(&pool).await?;
|
||||
seed_admin_account(&pool).await?;
|
||||
tracing::info!("Database initialized (schema v{})", SCHEMA_VERSION);
|
||||
Ok(pool)
|
||||
}
|
||||
@@ -244,6 +245,58 @@ pub async fn init_memory_db() -> SaasResult<SqlitePool> {
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
/// 如果 accounts 表为空且环境变量已设置,自动创建 super_admin 账号
|
||||
async fn seed_admin_account(pool: &SqlitePool) -> SaasResult<()> {
|
||||
let has_accounts: (bool,) = sqlx::query_as(
|
||||
"SELECT EXISTS(SELECT 1 FROM accounts LIMIT 1) as has"
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
if has_accounts.0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let admin_username = std::env::var("ZCLAW_ADMIN_USERNAME")
|
||||
.unwrap_or_else(|_| "admin".to_string());
|
||||
let admin_password = match std::env::var("ZCLAW_ADMIN_PASSWORD") {
|
||||
Ok(pwd) => pwd,
|
||||
Err(_) => {
|
||||
tracing::warn!(
|
||||
"accounts 表为空但未设置 ZCLAW_ADMIN_PASSWORD 环境变量。\
|
||||
请通过 POST /api/v1/auth/register 注册首个用户,然后手动将其 role 改为 super_admin。\
|
||||
或设置 ZCLAW_ADMIN_USERNAME 和 ZCLAW_ADMIN_PASSWORD 环境变量后重启服务。"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
use crate::auth::password::hash_password;
|
||||
|
||||
let password_hash = hash_password(&admin_password)?;
|
||||
let account_id = uuid::Uuid::new_v4().to_string();
|
||||
let email = format!("{}@zclaw.local", admin_username);
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO accounts (id, username, email, password_hash, display_name, role, status, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, 'super_admin', 'active', ?6, ?6)"
|
||||
)
|
||||
.bind(&account_id)
|
||||
.bind(&admin_username)
|
||||
.bind(&email)
|
||||
.bind(&password_hash)
|
||||
.bind(&admin_username)
|
||||
.bind(&now)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
tracing::info!(
|
||||
"自动创建 super_admin 账号: username={}, email={}", admin_username, email
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user