import * as SshKeyRepository from '../repositories/ssh_key.repository'; import { encrypt, decrypt } from '../utils/crypto'; import { SshKeyDbRow, CreateSshKeyData, UpdateSshKeyData } from '../repositories/ssh_key.repository'; // 定义 Service 层返回给 Controller 的基本密钥信息 (不含加密内容) export interface SshKeyBasicInfo { id: number; name: string; } // 定义 Service 层创建密钥时的输入类型 export interface CreateSshKeyInput { name: string; private_key: string; // 明文私钥 passphrase?: string; // 明文密码短语 (可选) } // 定义 Service 层更新密钥时的输入类型 (名称必选,凭证可选) export interface UpdateSshKeyInput { name?: string; // 名称可选,但通常会提供 private_key?: string; // 明文私钥 (可选,表示要更新) passphrase?: string; // 明文密码短语 (可选,如果提供了私钥,则此项也可能需要更新) } // 定义包含解密后凭证的密钥详情 export interface DecryptedSshKeyDetails extends SshKeyBasicInfo { privateKey: string; // 解密后的私钥 passphrase?: string; // 解密后的密码短语 } /** * 创建新的 SSH 密钥 * @param input 包含名称和明文凭证的对象 * @returns Promise 新创建密钥的基本信息 */ export const createSshKey = async (input: CreateSshKeyInput): Promise => { // 1. 验证输入 if (!input.name || !input.private_key) { throw new Error('必须提供密钥名称和私钥内容。'); } // 可选:添加更严格的私钥格式验证 // 2. 加密凭证 const encrypted_private_key = encrypt(input.private_key); const encrypted_passphrase = input.passphrase ? encrypt(input.passphrase) : null; // 3. 准备仓库数据 const dataToSave: CreateSshKeyData = { name: input.name, encrypted_private_key, encrypted_passphrase, }; // 4. 调用仓库创建记录 try { const newId = await SshKeyRepository.createSshKey(dataToSave); return { id: newId, name: input.name }; } catch (error: any) { // 处理可能的 UNIQUE constraint 错误 if (error.message && error.message.includes('UNIQUE constraint failed: ssh_keys.name')) { throw new Error(`SSH 密钥名称 "${input.name}" 已存在。`); } throw error; // 重新抛出其他错误 } }; /** * 获取所有 SSH 密钥的基本信息 (ID 和 Name) * @returns Promise 密钥列表 */ export const getAllSshKeyNames = async (): Promise => { return SshKeyRepository.findAllSshKeyNames(); }; /** * 根据 ID 获取 SSH 密钥的完整数据库行 (包含加密凭证) * 供内部服务使用,例如需要解密的场景 * @param id 密钥 ID * @returns Promise 密钥数据库行或 null */ export const getSshKeyDbRowById = async (id: number): Promise => { return SshKeyRepository.findSshKeyById(id); }; /** * 根据 ID 获取解密后的 SSH 密钥详情 * @param id 密钥 ID * @returns Promise 解密后的密钥详情或 null */ export const getDecryptedSshKeyById = async (id: number): Promise => { const dbRow = await SshKeyRepository.findSshKeyById(id); if (!dbRow) { return null; } try { const privateKey = decrypt(dbRow.encrypted_private_key); const passphrase = dbRow.encrypted_passphrase ? decrypt(dbRow.encrypted_passphrase) : undefined; return { id: dbRow.id, name: dbRow.name, privateKey, passphrase, }; } catch (error: any) { console.error(`Service: 解密 SSH 密钥 ${id} 失败:`, error); // 根据策略决定是抛出错误还是返回 null/部分信息 throw new Error(`解密 SSH 密钥 ${id} 失败。`); } }; /** * 更新 SSH 密钥 * @param id 要更新的密钥 ID * @param input 包含要更新字段的对象 (明文凭证) * @returns Promise 更新后的密钥基本信息或 null (如果未找到) */ export const updateSshKey = async (id: number, input: UpdateSshKeyInput): Promise => { // 1. 检查密钥是否存在 const existingKey = await SshKeyRepository.findSshKeyById(id); if (!existingKey) { return null; // 未找到 } // 2. 准备要更新的数据 const dataToUpdate: UpdateSshKeyData = {}; let finalName = existingKey.name; // 保留现有名称,除非输入中提供了新名称 if (input.name !== undefined) { if (!input.name) { throw new Error('密钥名称不能为空。'); } dataToUpdate.name = input.name; finalName = input.name; // 更新最终名称 } // 只有当提供了新的私钥时,才更新私钥和密码短语 if (input.private_key !== undefined) { if (!input.private_key) { throw new Error('私钥内容不能为空。'); } dataToUpdate.encrypted_private_key = encrypt(input.private_key); // 如果更新了私钥,则密码短语也需要更新(即使是设为 null) dataToUpdate.encrypted_passphrase = input.passphrase ? encrypt(input.passphrase) : null; } else if (input.passphrase !== undefined && existingKey.encrypted_private_key) { // 如果只提供了密码短语,且当前存在私钥,则只更新密码短语 dataToUpdate.encrypted_passphrase = input.passphrase ? encrypt(input.passphrase) : null; } // 3. 如果有数据需要更新,则调用仓库 if (Object.keys(dataToUpdate).length > 0) { try { const updated = await SshKeyRepository.updateSshKey(id, dataToUpdate); if (!updated) { // 理论上不应发生,因为我们已经检查过存在性 throw new Error('更新 SSH 密钥记录失败。'); } } catch (error: any) { // 处理可能的 UNIQUE constraint 错误 if (error.message && error.message.includes('UNIQUE constraint failed: ssh_keys.name')) { throw new Error(`SSH 密钥名称 "${input.name}" 已存在。`); } throw error; // 重新抛出其他错误 } } // 4. 返回更新后的基本信息 return { id: id, name: finalName }; }; /** * 删除 SSH 密钥 * @param id 要删除的密钥 ID * @returns Promise 是否删除成功 */ 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; };