Files
opsxshell/electron/services/key-service.ts
T
Logan.Li 514d62644e v2.0.0: 主题系统+SSH私钥解析修复+会话持久化+连接池优化+UI打磨
核心修复:
- ssh-service.ts: 新增 resolvePrivateKey(),将私钥文件路径(~/.ssh/id_rsa)解析为内容后传给 ssh2,
  解决 'Cannot parse privateKey: Unsupported key format' 错误
- useTerminalConnection.ts + sessionHelper.ts: 根据 authType 过滤字段,password/agent 认证不再传 privateKey
- sessionStore.ts: addSession() 现在先调用 sessionCreate() 持久化到 SQLite,解决恢复标签'无可用连接信息'问题
- database-service.ts: updateSession() 补充 private_key_path 字段持久化
- SessionDialog.tsx: 默认 username='root'、privateKey='~/.ssh/id_rsa'

UI/UX:
- 语义化主题系统(app-theme.ts),强制深色模式,全局无边框设计
- macOS 标题栏支持双击最大化(window:toggleMaximize IPC)
- macOS 菜单栏('OpsXShell' 应用菜单 + Settings Cmd+,)
- WelcomeView 4卡片布局,移除 Settings 按钮
- TabBar/TabItem 背景色统一(bg-dark-800/900),消除白色边框线

架构优化:
- SFTP 生命周期修复(useSFTP.ts useRef + autoConnectAttempted guard)
- 连接池竞态修复,支持快速多标签连接
- IPC 接口规范化,安全错误处理
- Vite 代码分割优化

质量: lint零错误 | tsc零错误 | build成功 | 49 tests pass
2026-04-22 11:15:41 +08:00

92 lines
2.7 KiB
TypeScript

import { app, safeStorage } from 'electron'
import path from 'path'
import fs from 'fs'
import crypto from 'crypto'
import { createLogger } from '../utils/logger'
const logger = createLogger('KeyService')
const KEY_FILE = 'encryption.key.enc'
const LEGACY_KEY_FILE = 'encryption.key'
export class KeyService {
private key: string | null = null
/**
* 获取或创建主密钥
* 使用 Electron safeStorage 加密存储,替代明文存储
*/
getMasterKey(): string {
if (this.key) return this.key
const userDataPath = app.getPath('userData')
const keyPath = path.join(userDataPath, KEY_FILE)
const legacyKeyPath = path.join(userDataPath, LEGACY_KEY_FILE)
// 1. 尝试读取新的加密格式密钥
if (fs.existsSync(keyPath)) {
try {
const encrypted = fs.readFileSync(keyPath)
this.key = safeStorage.decryptString(encrypted)
logger.info('Master key loaded from encrypted storage')
return this.key
} catch (error) {
logger.error('Failed to decrypt master key, generating new one:', error)
}
}
// 2. 迁移旧的明文密钥(向后兼容)
if (fs.existsSync(legacyKeyPath)) {
try {
this.key = fs.readFileSync(legacyKeyPath, 'utf8').trim()
// 迁移到加密存储
this.saveEncryptedKey(this.key)
// 安全删除旧文件(覆盖后删除)
this.secureDelete(legacyKeyPath)
logger.info('Migrated master key from plaintext to encrypted storage')
return this.key
} catch (error) {
logger.error('Failed to migrate legacy key:', error)
}
}
// 3. 生成新密钥
this.key = crypto.randomBytes(32).toString('hex')
this.saveEncryptedKey(this.key)
logger.info('Generated new master key with encrypted storage')
return this.key
}
// 使用 safeStorage 加密保存密钥
private saveEncryptedKey(key: string): void {
const keyPath = path.join(app.getPath('userData'), KEY_FILE)
const encrypted = safeStorage.encryptString(key)
fs.writeFileSync(keyPath, encrypted, { mode: 0o600 })
}
// 安全删除文件(先覆盖再删除)
private secureDelete(filePath: string): void {
try {
const stats = fs.statSync(filePath)
const size = stats.size
// 用随机数据覆盖
const randomData = crypto.randomBytes(size)
fs.writeFileSync(filePath, randomData)
// 再用零覆盖
fs.writeFileSync(filePath, Buffer.alloc(size, 0))
// 最后删除
fs.unlinkSync(filePath)
} catch (error) {
logger.error('Failed to securely delete file:', error)
}
}
// 清除密钥(登出时)
clearKey(): void {
this.key = null
}
}
// 全局单例
export const keyService = new KeyService()