chore: 提交所有工作进度 — SaaS 后端增强、Admin UI、桌面端集成
包含大量 SaaS 平台改进、Admin 管理后台更新、桌面端集成完善、 文档同步、测试文件重构等内容。为 QA 测试准备干净工作树。
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use secrecy::SecretString;
|
||||
use secrecy::{ExposeSecret, SecretString};
|
||||
use sha2::Digest;
|
||||
|
||||
/// SaaS 服务器完整配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -40,6 +41,9 @@ pub struct AuthConfig {
|
||||
pub jwt_expiration_hours: i64,
|
||||
#[serde(default = "default_totp_issuer")]
|
||||
pub totp_issuer: String,
|
||||
/// Refresh Token 有效期 (小时), 默认 168 小时 = 7 天
|
||||
#[serde(default = "default_refresh_hours")]
|
||||
pub refresh_token_hours: i64,
|
||||
}
|
||||
|
||||
/// 中转服务配置
|
||||
@@ -59,9 +63,10 @@ pub struct RelayConfig {
|
||||
|
||||
fn default_host() -> String { "0.0.0.0".into() }
|
||||
fn default_port() -> u16 { 8080 }
|
||||
fn default_db_url() -> String { "sqlite:./saas-data.db".into() }
|
||||
fn default_db_url() -> String { "postgres://localhost:5432/zclaw".into() }
|
||||
fn default_jwt_hours() -> i64 { 24 }
|
||||
fn default_totp_issuer() -> String { "ZCLAW SaaS".into() }
|
||||
fn default_refresh_hours() -> i64 { 168 }
|
||||
fn default_max_queue() -> usize { 1000 }
|
||||
fn default_max_concurrent() -> usize { 5 }
|
||||
fn default_batch_window() -> u64 { 50 }
|
||||
@@ -124,6 +129,7 @@ impl Default for AuthConfig {
|
||||
Self {
|
||||
jwt_expiration_hours: default_jwt_hours(),
|
||||
totp_issuer: default_totp_issuer(),
|
||||
refresh_token_hours: default_refresh_hours(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,7 +153,7 @@ impl SaaSConfig {
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("saas-config.toml"));
|
||||
|
||||
let config = if config_path.exists() {
|
||||
let mut config = if config_path.exists() {
|
||||
let content = std::fs::read_to_string(&config_path)?;
|
||||
toml::from_str(&content)?
|
||||
} else {
|
||||
@@ -155,6 +161,11 @@ impl SaaSConfig {
|
||||
SaaSConfig::default()
|
||||
};
|
||||
|
||||
// 环境变量覆盖数据库 URL (避免在配置文件中存储密码)
|
||||
if let Ok(db_url) = std::env::var("ZCLAW_DATABASE_URL") {
|
||||
config.database.url = db_url;
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -181,4 +192,47 @@ impl SaaSConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取 API Key 加密密钥 (复用 TOTP 加密密钥)
|
||||
pub fn api_key_encryption_key(&self) -> anyhow::Result<[u8; 32]> {
|
||||
self.totp_encryption_key()
|
||||
}
|
||||
|
||||
/// 获取 TOTP 加密密钥 (AES-256-GCM, 32 字节)
|
||||
/// 从 ZCLAW_TOTP_ENCRYPTION_KEY 环境变量加载 (hex 编码的 64 字符)
|
||||
/// 开发环境使用默认值 (不安全)
|
||||
pub fn totp_encryption_key(&self) -> anyhow::Result<[u8; 32]> {
|
||||
let is_dev = std::env::var("ZCLAW_SAAS_DEV")
|
||||
.map(|v| v == "true" || v == "1")
|
||||
.unwrap_or(false);
|
||||
|
||||
match std::env::var("ZCLAW_TOTP_ENCRYPTION_KEY") {
|
||||
Ok(hex_key) => {
|
||||
if hex_key.len() != 64 {
|
||||
anyhow::bail!("ZCLAW_TOTP_ENCRYPTION_KEY 必须是 64 个十六进制字符 (32 字节)");
|
||||
}
|
||||
let mut key = [0u8; 32];
|
||||
for i in 0..32 {
|
||||
key[i] = u8::from_str_radix(&hex_key[i*2..i*2+2], 16)
|
||||
.map_err(|_| anyhow::anyhow!("ZCLAW_TOTP_ENCRYPTION_KEY 包含无效的十六进制字符"))?;
|
||||
}
|
||||
Ok(key)
|
||||
}
|
||||
Err(_) => {
|
||||
if is_dev {
|
||||
tracing::warn!("ZCLAW_TOTP_ENCRYPTION_KEY not set, using development default (INSECURE)");
|
||||
// 开发环境使用固定密钥
|
||||
let mut key = [0u8; 32];
|
||||
key.copy_from_slice(b"zclaw-dev-totp-encrypt-key-32b!x");
|
||||
Ok(key)
|
||||
} else {
|
||||
// 生产环境: 使用 JWT 密钥的 SHA-256 哈希作为加密密钥
|
||||
tracing::warn!("ZCLAW_TOTP_ENCRYPTION_KEY not set, deriving from JWT secret");
|
||||
let jwt = self.jwt_secret()?;
|
||||
let hash = sha2::Sha256::digest(jwt.expose_secret().as_bytes());
|
||||
Ok(hash.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user