From 3db6217a562d0002ac5d1f4f6580f1b2bfdec82e Mon Sep 17 00:00:00 2001 From: dalaolala Date: Thu, 26 Feb 2026 15:57:24 +0800 Subject: [PATCH] Fix Terminal Credential Exposure --- backend/routes/servers.js | 104 +++++++++++++++++++------------- frontend/src/stores/servers.js | 84 ++++++++++++++++---------- frontend/src/views/Terminal.vue | 4 +- 3 files changed, 116 insertions(+), 76 deletions(-) diff --git a/backend/routes/servers.js b/backend/routes/servers.js index b7a03d4..52e3703 100644 --- a/backend/routes/servers.js +++ b/backend/routes/servers.js @@ -86,14 +86,14 @@ router.post('/', authenticateToken, async (req, res) => { // 处理分组信息 let groupId = null; let finalGroupName = groupName || 'Default'; - + // 查找对应的分组ID if (groupName) { const [groups] = await db.pool.execute( 'SELECT id FROM \`groups\` WHERE user_id = ? AND name = ?', [req.user.userId, groupName] ); - + if (groups.length > 0) { groupId = groups[0].id; } else { @@ -186,13 +186,13 @@ router.put('/:id', authenticateToken, async (req, res) => { if (groupName) { // 处理分组信息 let groupId = null; - + // 查找对应的分组ID const [groups] = await db.pool.execute( 'SELECT id FROM `groups` WHERE user_id = ? AND name = ?', [req.user.userId, groupName] ); - + if (groups.length > 0) { groupId = groups[0].id; } else { @@ -203,7 +203,7 @@ router.put('/:id', authenticateToken, async (req, res) => { ); groupId = groupResult.insertId; } - + updateFields.push('group_id = ?'); updateValues.push(groupId); updateFields.push('group_name = ?'); @@ -284,7 +284,7 @@ router.get('/groups', authenticateToken, async (req, res) => { ); group.serverCount = serverCount[0].count; } - + res.json({ groups }); } catch (error) { console.error('获取服务器分组错误:', error); @@ -386,7 +386,7 @@ router.put('/groups/:id', authenticateToken, async (req, res) => { [groupId, req.user.userId] ); - res.json({ + res.json({ message: '分组更新成功', group: { ...updatedGroup[0], @@ -424,8 +424,8 @@ router.delete('/groups/:id', authenticateToken, async (req, res) => { // 如果分组中有服务器,不允许删除 if (count > 0) { - return res.status(400).json({ - error: `分组中包含 ${count} 个服务器,请先删除或移出所有服务器后再删除分组` + return res.status(400).json({ + error: `分组中包含 ${count} 个服务器,请先删除或移出所有服务器后再删除分组` }); } @@ -435,7 +435,7 @@ router.delete('/groups/:id', authenticateToken, async (req, res) => { [groupId, req.user.userId] ); - res.json({ + res.json({ message: '分组删除成功' }); } catch (error) { @@ -444,6 +444,28 @@ router.delete('/groups/:id', authenticateToken, async (req, res) => { } }); +// 获取服务器基本信息(不含密钥,用于展示) +router.get('/:id', authenticateToken, async (req, res) => { + try { + const serverId = req.params.id; + + const [servers] = await db.pool.execute( + `SELECT id, name, host, port, username, auth_type, group_name + FROM servers WHERE id = ? AND user_id = ?`, + [serverId, req.user.userId] + ); + + if (servers.length === 0) { + return res.status(404).json({ error: '服务器不存在' }); + } + + res.json({ server: servers[0] }); + } catch (error) { + console.error('获取服务器信息错误:', error); + res.status(500).json({ error: '服务器内部错误' }); + } +}); + // 获取服务器详情(包含解密后的认证信息) router.get('/:id/credentials', authenticateToken, async (req, res) => { try { @@ -466,12 +488,12 @@ router.get('/:id/credentials', authenticateToken, async (req, res) => { // 解密认证信息 if (server.auth_type === 'password' && server.password_encrypted) { credentials.password = encryptionService.decryptPassword( - server.password_encrypted, + server.password_encrypted, req.user.userId ); } else if (server.auth_type === 'key' && server.private_key_encrypted) { credentials.privateKey = encryptionService.decryptPrivateKey( - server.private_key_encrypted, + server.private_key_encrypted, req.user.userId ); } @@ -497,35 +519,35 @@ router.get('/:id/credentials', authenticateToken, async (req, res) => { // 测试服务器连接(使用当前表单数据) router.post('/test-connection', authenticateToken, async (req, res) => { try { - const { - host, - port = 22, - username, - password, - privateKey, - authType = 'password' + const { + host, + port = 22, + username, + password, + privateKey, + authType = 'password' } = req.body; // 验证必填字段 if (!host || !username) { - return res.status(400).json({ - success: false, - error: '主机地址和用户名是必填项' + return res.status(400).json({ + success: false, + error: '主机地址和用户名是必填项' }); } // 验证认证信息 if (authType === 'password' && !password) { - return res.status(400).json({ - success: false, - error: '密码认证需要提供密码' + return res.status(400).json({ + success: false, + error: '密码认证需要提供密码' }); } if (authType === 'key' && !privateKey) { - return res.status(400).json({ - success: false, - error: '密钥认证需要提供私钥' + return res.status(400).json({ + success: false, + error: '密钥认证需要提供私钥' }); } @@ -546,13 +568,13 @@ router.post('/test-connection', authenticateToken, async (req, res) => { // 测试连接 const conn = new Client(); - + return new Promise((resolve) => { const timeout = setTimeout(() => { conn.end(); - res.status(408).json({ - success: false, - error: '连接超时' + res.status(408).json({ + success: false, + error: '连接超时' }); resolve(); }, 10000); @@ -560,9 +582,9 @@ router.post('/test-connection', authenticateToken, async (req, res) => { conn.on('ready', () => { clearTimeout(timeout); conn.end(); - res.json({ - success: true, - message: '连接测试成功' + res.json({ + success: true, + message: '连接测试成功' }); resolve(); }); @@ -570,9 +592,9 @@ router.post('/test-connection', authenticateToken, async (req, res) => { conn.on('error', (err) => { clearTimeout(timeout); console.error('SSH连接测试错误:', err); - res.status(500).json({ - success: false, - error: `连接失败: ${err.message || '未知错误'}` + res.status(500).json({ + success: false, + error: `连接失败: ${err.message || '未知错误'}` }); resolve(); }); @@ -582,9 +604,9 @@ router.post('/test-connection', authenticateToken, async (req, res) => { } catch (error) { console.error('测试连接错误:', error); - res.status(500).json({ - success: false, - error: '服务器内部错误' + res.status(500).json({ + success: false, + error: '服务器内部错误' }); } }); diff --git a/frontend/src/stores/servers.js b/frontend/src/stores/servers.js index 3b850ac..d16fad5 100644 --- a/frontend/src/stores/servers.js +++ b/frontend/src/stores/servers.js @@ -23,9 +23,9 @@ export const useServersStore = defineStore('servers', () => { await fetchGroups() return { success: true } } catch (error) { - return { - success: false, - error: error.response?.data?.error || '获取服务器列表失败' + return { + success: false, + error: error.response?.data?.error || '获取服务器列表失败' } } finally { loading.value = false @@ -54,13 +54,13 @@ export const useServersStore = defineStore('servers', () => { 'Authorization': `Bearer ${authStore.token}` } }) - + await fetchServers() return { success: true, data: response.data } } catch (error) { - return { - success: false, - error: error.response?.data?.error || '添加服务器失败' + return { + success: false, + error: error.response?.data?.error || '添加服务器失败' } } } @@ -73,13 +73,13 @@ export const useServersStore = defineStore('servers', () => { 'Authorization': `Bearer ${authStore.token}` } }) - + await fetchServers() return { success: true } } catch (error) { - return { - success: false, - error: error.response?.data?.error || '更新服务器失败' + return { + success: false, + error: error.response?.data?.error || '更新服务器失败' } } } @@ -92,13 +92,30 @@ export const useServersStore = defineStore('servers', () => { 'Authorization': `Bearer ${authStore.token}` } }) - + await fetchServers() return { success: true } } catch (error) { - return { - success: false, - error: error.response?.data?.error || '删除服务器失败' + return { + success: false, + error: error.response?.data?.error || '删除服务器失败' + } + } + } + + // 获取服务器基本信息(不含密钥,用于终端连接时的界面显示) + const getServerInfo = async (serverId) => { + try { + const response = await axios.get(`/api/servers/${serverId}`, { + headers: { + 'Authorization': `Bearer ${authStore.token}` + } + }) + return { success: true, data: response.data } + } catch (error) { + return { + success: false, + error: error.response?.data?.error || '获取服务器信息失败' } } } @@ -113,9 +130,9 @@ export const useServersStore = defineStore('servers', () => { }) return { success: true, data: response.data } } catch (error) { - return { - success: false, - error: error.response?.data?.error || '获取认证信息失败' + return { + success: false, + error: error.response?.data?.error || '获取认证信息失败' } } } @@ -130,9 +147,9 @@ export const useServersStore = defineStore('servers', () => { }) return { success: true, data: response.data } } catch (error) { - return { - success: false, - error: error.response?.data?.error || '连接测试失败' + return { + success: false, + error: error.response?.data?.error || '连接测试失败' } } } @@ -150,13 +167,13 @@ export const useServersStore = defineStore('servers', () => { 'Authorization': `Bearer ${authStore.token}` } }) - + await fetchGroups() return { success: true, data: response.data } } catch (error) { - return { - success: false, - error: error.response?.data?.error || '添加分组失败' + return { + success: false, + error: error.response?.data?.error || '添加分组失败' } } } @@ -169,13 +186,13 @@ export const useServersStore = defineStore('servers', () => { 'Authorization': `Bearer ${authStore.token}` } }) - + await fetchGroups() return { success: true } } catch (error) { - return { - success: false, - error: error.response?.data?.error || '更新分组失败' + return { + success: false, + error: error.response?.data?.error || '更新分组失败' } } } @@ -188,14 +205,14 @@ export const useServersStore = defineStore('servers', () => { 'Authorization': `Bearer ${authStore.token}` } }) - + await fetchGroups() await fetchServers() return { success: true } } catch (error) { - return { - success: false, - error: error.response?.data?.error || '删除分组失败' + return { + success: false, + error: error.response?.data?.error || '删除分组失败' } } } @@ -213,6 +230,7 @@ export const useServersStore = defineStore('servers', () => { addServer, updateServer, deleteServer, + getServerInfo, getServerCredentials, testConnection, getServersByGroup, diff --git a/frontend/src/views/Terminal.vue b/frontend/src/views/Terminal.vue index cdeda1f..d3e6ba9 100644 --- a/frontend/src/views/Terminal.vue +++ b/frontend/src/views/Terminal.vue @@ -494,8 +494,8 @@ const connectToServer = async () => { const serverId = route.params.serverId if (serverId) { - // 获取服务器信息 - const result = await serversStore.getServerCredentials(serverId) + // 获取服务器基本信息(不含密钥,仅用于界面显示) + const result = await serversStore.getServerInfo(serverId) if (result.success) { currentServer.value = result.data.server