262 lines
7.2 KiB
JavaScript
262 lines
7.2 KiB
JavaScript
const { Client } = require('ssh2');
|
|
const jwt = require('jsonwebtoken');
|
|
const db = require('../config/database');
|
|
const encryptionService = require('../utils/encryption');
|
|
|
|
// 存储活跃的SSH连接
|
|
const activeConnections = new Map();
|
|
|
|
const socketHandler = (io) => {
|
|
io.on('connection', (socket) => {
|
|
console.log('用户连接:', socket.id);
|
|
|
|
// 验证用户身份
|
|
socket.on('authenticate', async (token) => {
|
|
try {
|
|
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'webssh-secret-key');
|
|
socket.userId = decoded.userId;
|
|
socket.emit('authenticated', { success: true });
|
|
console.log('用户认证成功:', decoded.userId);
|
|
} catch (error) {
|
|
socket.emit('authenticated', { success: false, error: '认证失败' });
|
|
socket.disconnect();
|
|
}
|
|
});
|
|
|
|
// 建立SSH连接
|
|
socket.on('connect-ssh', async (serverId) => {
|
|
if (!socket.userId) {
|
|
socket.emit('ssh-error', { error: '未认证' });
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 获取服务器信息和认证信息
|
|
const [servers] = await db.pool.execute(
|
|
`SELECT id, name, host, port, username, password_encrypted,
|
|
private_key_encrypted, auth_type
|
|
FROM servers WHERE id = ? AND user_id = ?`,
|
|
[serverId, socket.userId]
|
|
);
|
|
|
|
if (servers.length === 0) {
|
|
socket.emit('ssh-error', { error: '服务器不存在' });
|
|
return;
|
|
}
|
|
|
|
const server = servers[0];
|
|
|
|
// 解密认证信息
|
|
let credentials = {};
|
|
if (server.auth_type === 'password' && server.password_encrypted) {
|
|
credentials.password = encryptionService.decryptPassword(
|
|
server.password_encrypted,
|
|
socket.userId
|
|
);
|
|
} else if (server.auth_type === 'key' && server.private_key_encrypted) {
|
|
credentials.privateKey = encryptionService.decryptPrivateKey(
|
|
server.private_key_encrypted,
|
|
socket.userId
|
|
);
|
|
}
|
|
|
|
// 创建SSH连接
|
|
const conn = new Client();
|
|
|
|
conn.on('ready', () => {
|
|
console.log('SSH连接已建立:', server.host);
|
|
socket.emit('ssh-connected');
|
|
|
|
// 存储连接
|
|
activeConnections.set(socket.id, {
|
|
conn,
|
|
serverId: server.id,
|
|
userId: socket.userId
|
|
});
|
|
|
|
// 创建shell会话
|
|
conn.shell({}, (err, stream) => {
|
|
if (err) {
|
|
socket.emit('ssh-error', { error: err.message });
|
|
return;
|
|
}
|
|
|
|
// 转发终端输出到前端
|
|
stream.on('data', (data) => {
|
|
socket.emit('ssh-data', data.toString());
|
|
});
|
|
|
|
// 处理来自前端的输入
|
|
socket.on('ssh-input', (input) => {
|
|
stream.write(input);
|
|
});
|
|
|
|
// 处理窗口大小调整
|
|
socket.on('resize', (size) => {
|
|
stream.setWindow(size.rows, size.cols, size.height, size.width);
|
|
});
|
|
|
|
// 处理连接关闭
|
|
stream.on('close', () => {
|
|
socket.emit('ssh-closed');
|
|
activeConnections.delete(socket.id);
|
|
});
|
|
|
|
socket.sshStream = stream;
|
|
});
|
|
});
|
|
|
|
conn.on('error', (err) => {
|
|
console.error('SSH连接错误:', err.message);
|
|
socket.emit('ssh-error', { error: err.message });
|
|
});
|
|
|
|
conn.on('close', () => {
|
|
console.log('SSH连接已关闭:', server.host);
|
|
socket.emit('ssh-closed');
|
|
activeConnections.delete(socket.id);
|
|
});
|
|
|
|
// 连接配置
|
|
const connConfig = {
|
|
host: server.host,
|
|
port: server.port || 22,
|
|
username: server.username,
|
|
readyTimeout: 20000,
|
|
keepaliveInterval: 30000
|
|
};
|
|
|
|
// 添加认证信息
|
|
if (server.auth_type === 'password') {
|
|
connConfig.password = credentials.password;
|
|
} else if (server.auth_type === 'key') {
|
|
connConfig.privateKey = credentials.privateKey;
|
|
}
|
|
|
|
// 开始连接
|
|
conn.connect(connConfig);
|
|
|
|
} catch (error) {
|
|
console.error('SSH连接准备错误:', error);
|
|
socket.emit('ssh-error', { error: '连接失败' });
|
|
}
|
|
});
|
|
|
|
// 快速连接(不保存服务器信息)
|
|
socket.on('quick-connect', (connectionInfo) => {
|
|
if (!socket.userId) {
|
|
socket.emit('ssh-error', { error: '未认证' });
|
|
return;
|
|
}
|
|
|
|
const { host, port, username, password, privateKey } = connectionInfo;
|
|
|
|
const conn = new Client();
|
|
|
|
conn.on('ready', () => {
|
|
console.log('快速SSH连接已建立:', host);
|
|
socket.emit('ssh-connected');
|
|
|
|
// 存储连接
|
|
activeConnections.set(socket.id, {
|
|
conn,
|
|
quickConnect: true,
|
|
userId: socket.userId
|
|
});
|
|
|
|
// 创建shell会话
|
|
conn.shell({}, (err, stream) => {
|
|
if (err) {
|
|
socket.emit('ssh-error', { error: err.message });
|
|
return;
|
|
}
|
|
|
|
stream.on('data', (data) => {
|
|
socket.emit('ssh-data', data.toString());
|
|
});
|
|
|
|
socket.on('ssh-input', (input) => {
|
|
stream.write(input);
|
|
});
|
|
|
|
socket.on('resize', (size) => {
|
|
stream.setWindow(size.rows, size.cols, size.height, size.width);
|
|
});
|
|
|
|
stream.on('close', () => {
|
|
socket.emit('ssh-closed');
|
|
activeConnections.delete(socket.id);
|
|
});
|
|
|
|
socket.sshStream = stream;
|
|
});
|
|
});
|
|
|
|
conn.on('error', (err) => {
|
|
console.error('快速SSH连接错误:', err.message);
|
|
socket.emit('ssh-error', { error: err.message });
|
|
});
|
|
|
|
conn.on('close', () => {
|
|
console.log('快速SSH连接已关闭:', host);
|
|
socket.emit('ssh-closed');
|
|
activeConnections.delete(socket.id);
|
|
});
|
|
|
|
// 连接配置
|
|
const connConfig = {
|
|
host,
|
|
port: port || 22,
|
|
username,
|
|
readyTimeout: 20000,
|
|
keepaliveInterval: 30000
|
|
};
|
|
|
|
if (password) {
|
|
connConfig.password = password;
|
|
} else if (privateKey) {
|
|
connConfig.privateKey = privateKey;
|
|
}
|
|
|
|
conn.connect(connConfig);
|
|
});
|
|
|
|
// 发送命令到终端
|
|
socket.on('send-command', (command) => {
|
|
if (socket.sshStream) {
|
|
socket.sshStream.write(command + '\n');
|
|
}
|
|
});
|
|
|
|
// 断开SSH连接
|
|
socket.on('disconnect-ssh', () => {
|
|
const connection = activeConnections.get(socket.id);
|
|
if (connection) {
|
|
connection.conn.end();
|
|
activeConnections.delete(socket.id);
|
|
}
|
|
});
|
|
|
|
// 处理连接断开
|
|
socket.on('disconnect', () => {
|
|
console.log('用户断开连接:', socket.id);
|
|
const connection = activeConnections.get(socket.id);
|
|
if (connection) {
|
|
connection.conn.end();
|
|
activeConnections.delete(socket.id);
|
|
}
|
|
});
|
|
|
|
// 心跳检测
|
|
socket.on('ping', () => {
|
|
socket.emit('pong');
|
|
});
|
|
});
|
|
|
|
// 全局错误处理
|
|
io.on('error', (error) => {
|
|
console.error('Socket.IO错误:', error);
|
|
});
|
|
};
|
|
|
|
module.exports = socketHandler; |