mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-11 14:56:57 +08:00
Dev Crypto (#730)
* feat: remove admin mode * feat: bugfix * feat(crypto): 添加RSA-OAEP + AES-GCM混合加密服务 - 实现CryptoService加密服务,支持RSA-OAEP-2048 + AES-256-GCM混合加密 - 集成数据库层加密,自动加密存储敏感字段(API密钥、私钥等) - 支持环境变量DATA_ENCRYPTION_KEY配置数据加密密钥 - 适配SQLite数据库加密存储(从PostgreSQL移植) - 保持Hyperliquid代理钱包处理兼容性 - 更新.gitignore以正确处理crypto模块代码 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(scripts): 添加加密环境一键设置脚本 - setup_encryption.sh: 一键生成RSA密钥对+数据加密密钥+JWT密钥 - generate_rsa_keys.sh: 专业的RSA-2048密钥对生成工具 - generate_data_key.sh: 生成AES-256数据加密密钥和JWT认证密钥 - ENCRYPTION_README.md: 详细的加密系统说明文档 - 支持自动检测现有密钥并只生成缺失的密钥 - 完善的权限管理和安全验证 - 兼容macOS和Linux的跨平台支持 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(api): 添加加密API端点和Gin框架集成 - 新增CryptoHandler处理加密相关API请求 - 提供/api/crypto/public-key端点获取RSA公钥 - 提供/api/crypto/decrypt端点解密敏感数据 - 适配Gin框架的HTTP处理器格式 - 集成CryptoService到API服务器 - 支持前端加密数据传输和解密 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(web): 添加前端加密服务和两阶段密钥输入组件 - CryptoService: Web Crypto API集成,支持RSA-OAEP加密 - TwoStageKeyModal: 安全的两阶段私钥输入组件,支持剪贴板混淆 - 完善国际化翻译支持加密相关UI文本 - 修复TypeScript类型错误和编译问题 - 支持前端敏感数据加密传输到后端 - 增强用户隐私保护和数据安全 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(auth): 增强JWT认证安全性 - 优先使用环境变量JWT_SECRET而不是数据库配置 - 支持通过.env文件安全配置JWT认证密钥 - 保留数据库配置作为回退机制 - 改进JWT密钥来源日志显示 - 增强系统启动时的安全配置检查 - 支持运行时动态JWT密钥切换 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(docker): 集成加密环境变量到Docker部署 - 添加DATA_ENCRYPTION_KEY环境变量传递到容器 - 添加JWT_SECRET环境变量支持 - 挂载secrets目录使容器可访问RSA密钥文件 - 确保容器内加密服务正常工作 - 解决容器启动失败和加密初始化问题 - 完善Docker Compose加密环境配置 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(start): 集成自动加密环境检测和设置 - 增强check_encryption()函数检测JWT_SECRET和DATA_ENCRYPTION_KEY - 自动运行setup_encryption.sh当检测到缺失密钥时 - 改进加密状态显示,包含RSA+AES+JWT全套加密信息 - 优化用户体验,提供清晰的加密配置反馈 - 支持一键设置完整加密环境 - 确保容器启动前加密环境就绪 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: format fix * fix(security): 修复前端模型和交易所配置敏感数据明文传输 - 在handleSaveModelConfig中对API密钥进行RSA-OAEP加密 - 在handleSaveExchangeConfig中对API密钥、Secret密钥和Aster私钥进行加密 - 只有非空敏感数据才进行加密处理 - 添加加密失败错误处理和用户友好提示 - 增加encryptionFailed翻译键的中英文支持 - 使用用户ID和会话ID作为加密上下文增强安全性 这修复了之前敏感数据在网络传输中以明文形式发送的安全漏洞。 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(crypto): 修复后端加密服务集成和缺失的加密端点 - 添加Server结构体缺少的cryptoService字段 - 实现handleUpdateModelConfigsEncrypted处理器用于模型配置加密传输 - 修复handleUpdateExchangeConfigsEncrypted中的函数调用 - 在前端API中添加updateModelConfigsEncrypted方法 - 统一RSA密钥路径从secrets/rsa_key改为keys/rsa_private.key - 确保前端可以使用加密端点安全传输敏感数据 - 兼容原有加密通信模式和二段输入私钥功能 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: icy <icyoung520@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"nofx/crypto"
|
||||
"nofx/market"
|
||||
"os"
|
||||
"slices"
|
||||
@@ -16,9 +17,45 @@ import (
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// DatabaseInterface 定义了数据库实现需要提供的方法集合
|
||||
type DatabaseInterface interface {
|
||||
SetCryptoService(cs *crypto.CryptoService)
|
||||
CreateUser(user *User) error
|
||||
GetUserByEmail(email string) (*User, error)
|
||||
GetUserByID(userID string) (*User, error)
|
||||
GetAllUsers() ([]string, error)
|
||||
UpdateUserOTPVerified(userID string, verified bool) error
|
||||
GetAIModels(userID string) ([]*AIModelConfig, error)
|
||||
UpdateAIModel(userID, id string, enabled bool, apiKey, customAPIURL, customModelName string) error
|
||||
GetExchanges(userID string) ([]*ExchangeConfig, error)
|
||||
UpdateExchange(userID, id string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error
|
||||
CreateAIModel(userID, id, name, provider string, enabled bool, apiKey, customAPIURL string) error
|
||||
CreateExchange(userID, id, name, typ string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error
|
||||
CreateTrader(trader *TraderRecord) error
|
||||
GetTraders(userID string) ([]*TraderRecord, error)
|
||||
UpdateTraderStatus(userID, id string, isRunning bool) error
|
||||
UpdateTrader(trader *TraderRecord) error
|
||||
UpdateTraderInitialBalance(userID, id string, newBalance float64) error
|
||||
UpdateTraderCustomPrompt(userID, id string, customPrompt string, overrideBase bool) error
|
||||
DeleteTrader(userID, id string) error
|
||||
GetTraderConfig(userID, traderID string) (*TraderRecord, *AIModelConfig, *ExchangeConfig, error)
|
||||
GetSystemConfig(key string) (string, error)
|
||||
SetSystemConfig(key, value string) error
|
||||
CreateUserSignalSource(userID, coinPoolURL, oiTopURL string) error
|
||||
GetUserSignalSource(userID string) (*UserSignalSource, error)
|
||||
UpdateUserSignalSource(userID, coinPoolURL, oiTopURL string) error
|
||||
GetCustomCoins() []string
|
||||
LoadBetaCodesFromFile(filePath string) error
|
||||
ValidateBetaCode(code string) (bool, error)
|
||||
UseBetaCode(code, userEmail string) error
|
||||
GetBetaCodeStats() (total, used int, err error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Database 配置数据库
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
db *sql.DB
|
||||
cryptoService *crypto.CryptoService
|
||||
}
|
||||
|
||||
// NewDatabase 创建配置数据库
|
||||
@@ -582,6 +619,8 @@ func (d *Database) GetAIModels(userID string) ([]*AIModelConfig, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 解密API Key
|
||||
model.APIKey = d.decryptSensitiveData(model.APIKey)
|
||||
models = append(models, &model)
|
||||
}
|
||||
|
||||
@@ -598,10 +637,11 @@ func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, custom
|
||||
|
||||
if err == nil {
|
||||
// 找到了现有配置(精确匹配 ID),更新它
|
||||
encryptedAPIKey := d.encryptSensitiveData(apiKey)
|
||||
_, err = d.db.Exec(`
|
||||
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, enabled, apiKey, customAPIURL, customModelName, existingID, userID)
|
||||
`, enabled, encryptedAPIKey, customAPIURL, customModelName, existingID, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -614,10 +654,11 @@ func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, custom
|
||||
if err == nil {
|
||||
// 找到了现有配置(通过 provider 匹配,兼容旧版),更新它
|
||||
log.Printf("⚠️ 使用旧版 provider 匹配更新模型: %s -> %s", provider, existingID)
|
||||
encryptedAPIKey := d.encryptSensitiveData(apiKey)
|
||||
_, err = d.db.Exec(`
|
||||
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, enabled, apiKey, customAPIURL, customModelName, existingID, userID)
|
||||
`, enabled, encryptedAPIKey, customAPIURL, customModelName, existingID, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -661,10 +702,11 @@ func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, custom
|
||||
}
|
||||
|
||||
log.Printf("✓ 创建新的 AI 模型配置: ID=%s, Provider=%s, Name=%s", newModelID, provider, name)
|
||||
encryptedAPIKey := d.encryptSensitiveData(apiKey)
|
||||
_, err = d.db.Exec(`
|
||||
INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||
`, newModelID, userID, name, provider, enabled, apiKey, customAPIURL, customModelName)
|
||||
`, newModelID, userID, name, provider, enabled, encryptedAPIKey, customAPIURL, customModelName)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -699,6 +741,12 @@ func (d *Database) GetExchanges(userID string) ([]*ExchangeConfig, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 解密敏感字段
|
||||
exchange.APIKey = d.decryptSensitiveData(exchange.APIKey)
|
||||
exchange.SecretKey = d.decryptSensitiveData(exchange.SecretKey)
|
||||
exchange.AsterPrivateKey = d.decryptSensitiveData(exchange.AsterPrivateKey)
|
||||
|
||||
exchanges = append(exchanges, &exchange)
|
||||
}
|
||||
|
||||
@@ -709,12 +757,17 @@ func (d *Database) GetExchanges(userID string) ([]*ExchangeConfig, error) {
|
||||
func (d *Database) UpdateExchange(userID, id string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error {
|
||||
log.Printf("🔧 UpdateExchange: userID=%s, id=%s, enabled=%v", userID, id, enabled)
|
||||
|
||||
// 加密敏感字段
|
||||
encryptedAPIKey := d.encryptSensitiveData(apiKey)
|
||||
encryptedSecretKey := d.encryptSensitiveData(secretKey)
|
||||
encryptedAsterPrivateKey := d.encryptSensitiveData(asterPrivateKey)
|
||||
|
||||
// 首先尝试更新现有的用户配置
|
||||
result, err := d.db.Exec(`
|
||||
UPDATE exchanges SET enabled = ?, api_key = ?, secret_key = ?, testnet = ?,
|
||||
hyperliquid_wallet_addr = ?, aster_user = ?, aster_signer = ?, aster_private_key = ?, updated_at = datetime('now')
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey, id, userID)
|
||||
`, enabled, encryptedAPIKey, encryptedSecretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, encryptedAsterPrivateKey, id, userID)
|
||||
if err != nil {
|
||||
log.Printf("❌ UpdateExchange: 更新失败: %v", err)
|
||||
return err
|
||||
@@ -781,10 +834,15 @@ func (d *Database) CreateAIModel(userID, id, name, provider string, enabled bool
|
||||
|
||||
// CreateExchange 创建交易所配置
|
||||
func (d *Database) CreateExchange(userID, id, name, typ string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error {
|
||||
// 加密敏感字段
|
||||
encryptedAPIKey := d.encryptSensitiveData(apiKey)
|
||||
encryptedSecretKey := d.encryptSensitiveData(secretKey)
|
||||
encryptedAsterPrivateKey := d.encryptSensitiveData(asterPrivateKey)
|
||||
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR IGNORE INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, id, userID, name, typ, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey)
|
||||
`, id, userID, name, typ, enabled, encryptedAPIKey, encryptedSecretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, encryptedAsterPrivateKey)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1116,3 +1174,43 @@ func (d *Database) GetBetaCodeStats() (total, used int, err error) {
|
||||
|
||||
return total, used, nil
|
||||
}
|
||||
|
||||
// SetCryptoService 设置加密服务
|
||||
func (d *Database) SetCryptoService(cs *crypto.CryptoService) {
|
||||
d.cryptoService = cs
|
||||
}
|
||||
|
||||
// encryptSensitiveData 加密敏感数据用于存储
|
||||
func (d *Database) encryptSensitiveData(plaintext string) string {
|
||||
if d.cryptoService == nil || plaintext == "" {
|
||||
return plaintext
|
||||
}
|
||||
|
||||
encrypted, err := d.cryptoService.EncryptForStorage(plaintext)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 加密失败: %v", err)
|
||||
return plaintext // 返回明文作为降级处理
|
||||
}
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// decryptSensitiveData 解密敏感数据
|
||||
func (d *Database) decryptSensitiveData(encrypted string) string {
|
||||
if d.cryptoService == nil || encrypted == "" {
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// 如果不是加密格式,直接返回
|
||||
if !d.cryptoService.IsEncryptedStorageValue(encrypted) {
|
||||
return encrypted
|
||||
}
|
||||
|
||||
decrypted, err := d.cryptoService.DecryptFromStorage(encrypted)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 解密失败: %v", err)
|
||||
return encrypted // 返回加密文本作为降级处理
|
||||
}
|
||||
|
||||
return decrypted
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user