From d37158144e62f6325411846f387b2b1f5d0a5a87 Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Sat, 10 May 2025 17:51:32 +0800 Subject: [PATCH] update --- .../src/repositories/ssh_key.repository.ts | 15 +++++++++++ .../src/services/import-export.service.ts | 27 +++++++++---------- .../backend/src/services/ssh_key.service.ts | 27 +++++++++++++++++++ .../src/settings/settings.controller.ts | 2 +- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/packages/backend/src/repositories/ssh_key.repository.ts b/packages/backend/src/repositories/ssh_key.repository.ts index 7b1a6f9..e6b4b9c 100644 --- a/packages/backend/src/repositories/ssh_key.repository.ts +++ b/packages/backend/src/repositories/ssh_key.repository.ts @@ -75,6 +75,21 @@ export const findAllSshKeyNames = async (): Promise<{ id: number; name: string } } }; +/** + * 查找所有 SSH 密钥的完整记录 + * @returns Promise 密钥列表 + */ +export const findAllSshKeys = async (): Promise => { + const sql = `SELECT * FROM ssh_keys ORDER BY name ASC`; + try { + const db = await getDbInstance(); + const rows = await allDb(db, sql); + return rows; + } catch (err: any) { + console.error('Repository: 查找所有 SSH 密钥记录失败:', err.message); + throw new Error(`查找所有 SSH 密钥记录失败: ${err.message}`); + } +}; /** * 更新 SSH 密钥记录 diff --git a/packages/backend/src/services/import-export.service.ts b/packages/backend/src/services/import-export.service.ts index ed54b88..bfa371b 100644 --- a/packages/backend/src/services/import-export.service.ts +++ b/packages/backend/src/services/import-export.service.ts @@ -3,13 +3,10 @@ import * as ConnectionRepository from '../repositories/connection.repository'; import * as ProxyRepository from '../repositories/proxy.repository'; import { getDbInstance, runDb, getDb as getDbRow, allDb } from '../database/connection'; import { decrypt, getEncryptionKeyBuffer as getCryptoKeyBuffer } from '../utils/crypto'; // For decrypting connection details +import { getAllDecryptedSshKeys } from '../services/ssh_key.service'; // 静态导入 import archiver from 'archiver'; archiver.registerFormat('zip-encrypted', require("archiver-zip-encrypted")); -// We might still need fs, path, os if easyzip requires writing to a temp file for password protection, -// but let's try to do it in memory first. -// import fs from 'fs'; -// import path from 'path'; -// import os from 'os'; + @@ -205,19 +202,13 @@ const getPlaintextConnectionsData = async (): Promise => { +export const exportConnectionsAsEncryptedZip = async (includeSshKeys: boolean = false): Promise => { try { const connections = await getPlaintextConnectionsData(); - const jsonContent = JSON.stringify(connections, null, 2); - - // 注意:当前版本的 adm-zip 不支持在内存中设置密码 - // const zipPassword = process.env.ENCRYPTION_KEY; - // if (!zipPassword || zipPassword.trim() === '') { - // console.error('错误:ENCRYPTION_KEY 环境变量未设置或为空!无法为ZIP文件设置密码。'); - // throw new Error('ENCRYPTION_KEY is not set or is empty, cannot password-protect the ZIP file.'); - // } + const connectionsJsonContent = JSON.stringify(connections, null, 2); const zipPassword = process.env.ENCRYPTION_KEY; if (!zipPassword || zipPassword.trim() === '') { @@ -241,7 +232,13 @@ export const exportConnectionsAsEncryptedZip = async (): Promise => { throw new Error(`使用 archiver 创建加密 ZIP buffer 失败: ${err.message}`); }); - archive.append(jsonContent, { name: 'connections.json' }); + archive.append(connectionsJsonContent, { name: 'connections.json' }); + + if (includeSshKeys) { + const sshKeys = await getAllDecryptedSshKeys(); + const sshKeysJsonContent = JSON.stringify(sshKeys, null, 2); + archive.append(sshKeysJsonContent, { name: 'ssh_keys.json' }); + } await archive.finalize(); return Buffer.concat(buffer); diff --git a/packages/backend/src/services/ssh_key.service.ts b/packages/backend/src/services/ssh_key.service.ts index ff54c5a..e9d3376 100644 --- a/packages/backend/src/services/ssh_key.service.ts +++ b/packages/backend/src/services/ssh_key.service.ts @@ -180,4 +180,31 @@ export const updateSshKey = async (id: number, input: UpdateSshKeyInput): Promis export const deleteSshKey = async (id: number): Promise => { // 注意:删除密钥前,相关的 connections 表中的 ssh_key_id 会被设为 NULL (ON DELETE SET NULL) return SshKeyRepository.deleteSshKey(id); +}; +/** + * 获取所有解密后的 SSH 密钥详情 + * @returns Promise 解密后的密钥详情列表 + */ +export const getAllDecryptedSshKeys = async (): Promise => { + const dbRows = await SshKeyRepository.findAllSshKeys(); + const decryptedKeys: DecryptedSshKeyDetails[] = []; + + for (const dbRow of dbRows) { + try { + const privateKey = decrypt(dbRow.encrypted_private_key); + const passphrase = dbRow.encrypted_passphrase ? decrypt(dbRow.encrypted_passphrase) : undefined; + decryptedKeys.push({ + id: dbRow.id, + name: dbRow.name, + privateKey, + passphrase, + }); + } catch (error: any) { + console.error(`Service: 解密 SSH 密钥 ${dbRow.id} 失败:`, error); + // 继续处理其他密钥,不因单个密钥解密失败而中断整个过程 + // 可以选择记录错误或通知管理员,但这里我们只记录日志 + } + } + + return decryptedKeys; }; \ No newline at end of file diff --git a/packages/backend/src/settings/settings.controller.ts b/packages/backend/src/settings/settings.controller.ts index 95da488..71b2b3e 100644 --- a/packages/backend/src/settings/settings.controller.ts +++ b/packages/backend/src/settings/settings.controller.ts @@ -511,7 +511,7 @@ async setCaptchaConfig(req: Request, res: Response): Promise { async exportAllConnections(req: Request, res: Response): Promise { try { console.log('[控制器] 收到导出所有连接的请求。'); - const encryptedZipBuffer = await exportConnectionsAsEncryptedZip(); + const encryptedZipBuffer = await exportConnectionsAsEncryptedZip(true); res.setHeader('Content-Type', 'application/zip'); res.setHeader('Content-Disposition', 'attachment; filename="nexus_connections_export.zip"');