Update ssh.ts
This commit is contained in:
@@ -10,19 +10,13 @@ const formatMessage = (level: string, colorFn: chalk.Chalk, message: string): st
|
|||||||
return `${getTimeStamp()} ${colorFn(`[${level.toUpperCase()}]`)} ${chalk.hex('#1e3a8a')(`[${sshIconSymbol}]`)} ${message}`;
|
return `${getTimeStamp()} ${colorFn(`[${level.toUpperCase()}]`)} ${chalk.hex('#1e3a8a')(`[${sshIconSymbol}]`)} ${message}`;
|
||||||
};
|
};
|
||||||
const logger = {
|
const logger = {
|
||||||
info: (msg: string): void => {
|
info: (msg: string): void => console.log(formatMessage('info', chalk.cyan, msg)),
|
||||||
console.log(formatMessage('info', chalk.cyan, msg));
|
warn: (msg: string): void => console.warn(formatMessage('warn', chalk.yellow, msg)),
|
||||||
},
|
|
||||||
warn: (msg: string): void => {
|
|
||||||
console.warn(formatMessage('warn', chalk.yellow, msg));
|
|
||||||
},
|
|
||||||
error: (msg: string, err?: unknown): void => {
|
error: (msg: string, err?: unknown): void => {
|
||||||
console.error(formatMessage('error', chalk.redBright, msg));
|
console.error(formatMessage('error', chalk.redBright, msg));
|
||||||
if (err) console.error(err);
|
if (err) console.error(err);
|
||||||
},
|
},
|
||||||
success: (msg: string): void => {
|
success: (msg: string): void => console.log(formatMessage('success', chalk.greenBright, msg)),
|
||||||
console.log(formatMessage('success', chalk.greenBright, msg));
|
|
||||||
},
|
|
||||||
debug: (msg: string): void => {
|
debug: (msg: string): void => {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
console.debug(formatMessage('debug', chalk.magenta, msg));
|
console.debug(formatMessage('debug', chalk.magenta, msg));
|
||||||
@@ -33,6 +27,7 @@ const logger = {
|
|||||||
wss.on('connection', (ws: WebSocket) => {
|
wss.on('connection', (ws: WebSocket) => {
|
||||||
let sshConn: Client | null = null;
|
let sshConn: Client | null = null;
|
||||||
let sshStream: ClientChannel | null = null;
|
let sshStream: ClientChannel | null = null;
|
||||||
|
let keepAliveTimer: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
ws.on('close', () => {
|
ws.on('close', () => {
|
||||||
cleanupSSH();
|
cleanupSSH();
|
||||||
@@ -54,38 +49,21 @@ wss.on('connection', (ws: WebSocket) => {
|
|||||||
case 'connectToHost':
|
case 'connectToHost':
|
||||||
handleConnectToHost(data);
|
handleConnectToHost(data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'resize':
|
case 'resize':
|
||||||
handleResize(data);
|
handleResize(data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'disconnect':
|
case 'disconnect':
|
||||||
cleanupSSH();
|
cleanupSSH();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'input':
|
case 'input':
|
||||||
if (sshStream) sshStream.write(data);
|
if (sshStream) sshStream.write(data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.warn('Unknown message type: ' + type);
|
logger.warn('Unknown message type: ' + type);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleConnectToHost(data: {
|
function handleConnectToHost(data: any) {
|
||||||
cols: number;
|
|
||||||
rows: number;
|
|
||||||
hostConfig: {
|
|
||||||
ip: string;
|
|
||||||
port: number;
|
|
||||||
username: string;
|
|
||||||
password?: string;
|
|
||||||
key?: string;
|
|
||||||
keyPassword?: string;
|
|
||||||
keyType?: string;
|
|
||||||
authType?: string;
|
|
||||||
};
|
|
||||||
}) {
|
|
||||||
const { cols, rows, hostConfig } = data;
|
const { cols, rows, hostConfig } = data;
|
||||||
const { ip, port, username, password, key, keyPassword, keyType, authType } = hostConfig;
|
const { ip, port, username, password, key, keyPassword, keyType, authType } = hostConfig;
|
||||||
|
|
||||||
@@ -94,13 +72,11 @@ wss.on('connection', (ws: WebSocket) => {
|
|||||||
ws.send(JSON.stringify({ type: 'error', message: 'Invalid username provided' }));
|
ws.send(JSON.stringify({ type: 'error', message: 'Invalid username provided' }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ip || typeof ip !== 'string' || ip.trim() === '') {
|
if (!ip || typeof ip !== 'string' || ip.trim() === '') {
|
||||||
logger.error('Invalid IP provided');
|
logger.error('Invalid IP provided');
|
||||||
ws.send(JSON.stringify({ type: 'error', message: 'Invalid IP provided' }));
|
ws.send(JSON.stringify({ type: 'error', message: 'Invalid IP provided' }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!port || typeof port !== 'number' || port <= 0) {
|
if (!port || typeof port !== 'number' || port <= 0) {
|
||||||
logger.error('Invalid port provided');
|
logger.error('Invalid port provided');
|
||||||
ws.send(JSON.stringify({ type: 'error', message: 'Invalid port provided' }));
|
ws.send(JSON.stringify({ type: 'error', message: 'Invalid port provided' }));
|
||||||
@@ -130,17 +106,12 @@ wss.on('connection', (ws: WebSocket) => {
|
|||||||
|
|
||||||
sshStream = stream;
|
sshStream = stream;
|
||||||
|
|
||||||
const keepaliveTimer = setInterval(() => {
|
|
||||||
if (sshStream && sshStream.writable) {
|
|
||||||
sshStream.write('\x00');
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
stream.on('data', (chunk: Buffer) => {
|
stream.on('data', (chunk: Buffer) => {
|
||||||
ws.send(JSON.stringify({ type: 'data', data: chunk.toString() }));
|
ws.send(JSON.stringify({ type: 'data', data: chunk.toString() }));
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.on('close', () => {
|
stream.on('close', () => {
|
||||||
|
ws.send(JSON.stringify({ type: 'disconnected', message: 'SSH session closed' }));
|
||||||
cleanupSSH();
|
cleanupSSH();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -150,32 +121,37 @@ wss.on('connection', (ws: WebSocket) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ws.send(JSON.stringify({ type: 'connected', message: 'SSH connected' }));
|
ws.send(JSON.stringify({ type: 'connected', message: 'SSH connected' }));
|
||||||
|
|
||||||
|
keepAliveTimer = setInterval(() => {
|
||||||
|
if (sshStream && sshStream.writable) {
|
||||||
|
sshStream.write(''); // keepalive
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
sshConn.on('error', (err: Error) => {
|
sshConn.on('error', (err: Error) => {
|
||||||
logger.error('SSH connection error: ' + err.message);
|
logger.error('SSH connection error: ' + err.message);
|
||||||
|
|
||||||
let errorMessage = 'SSH error: ' + err.message;
|
let errorMessage = 'SSH error: ' + err.message;
|
||||||
|
|
||||||
if (err.message.includes('No matching key exchange algorithm')) {
|
if (err.message.includes('No matching key exchange algorithm')) {
|
||||||
errorMessage = 'SSH error: No compatible key exchange algorithm found. This may be due to an older SSH server or network device.';
|
errorMessage = 'SSH error: No compatible key exchange algorithm found.';
|
||||||
} else if (err.message.includes('No matching cipher')) {
|
} else if (err.message.includes('No matching cipher')) {
|
||||||
errorMessage = 'SSH error: No compatible cipher found. This may be due to an older SSH server or network device.';
|
errorMessage = 'SSH error: No compatible cipher found.';
|
||||||
} else if (err.message.includes('No matching MAC')) {
|
|
||||||
errorMessage = 'SSH error: No compatible MAC algorithm found. This may be due to an older SSH server or network device.';
|
|
||||||
} else if (err.message.includes('ENOTFOUND') || err.message.includes('ENOENT')) {
|
} else if (err.message.includes('ENOTFOUND') || err.message.includes('ENOENT')) {
|
||||||
errorMessage = 'SSH error: Could not resolve hostname or connect to server.';
|
errorMessage = 'SSH error: Could not resolve hostname or connect to server.';
|
||||||
} else if (err.message.includes('ECONNREFUSED')) {
|
} else if (err.message.includes('ECONNREFUSED')) {
|
||||||
errorMessage = 'SSH error: Connection refused. The server may not be running or the port may be incorrect.';
|
errorMessage = 'SSH error: Connection refused.';
|
||||||
} else if (err.message.includes('ETIMEDOUT')) {
|
} else if (err.message.includes('ETIMEDOUT')) {
|
||||||
errorMessage = 'SSH error: Connection timed out. Check your network connection and server availability.';
|
errorMessage = 'SSH error: Connection timed out.';
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.send(JSON.stringify({ type: 'error', message: errorMessage }));
|
ws.send(JSON.stringify({ type: 'error', message: errorMessage }));
|
||||||
cleanupSSH();
|
cleanupSSH();
|
||||||
});
|
});
|
||||||
|
|
||||||
sshConn.on('close', () => {
|
sshConn.on('close', () => {
|
||||||
|
ws.send(JSON.stringify({ type: 'disconnected', message: 'SSH connection closed' }));
|
||||||
cleanupSSH();
|
cleanupSSH();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -186,7 +162,6 @@ wss.on('connection', (ws: WebSocket) => {
|
|||||||
keepaliveInterval: 10000,
|
keepaliveInterval: 10000,
|
||||||
keepaliveCountMax: 60,
|
keepaliveCountMax: 60,
|
||||||
readyTimeout: 10000,
|
readyTimeout: 10000,
|
||||||
|
|
||||||
algorithms: {
|
algorithms: {
|
||||||
kex: [
|
kex: [
|
||||||
'diffie-hellman-group14-sha256',
|
'diffie-hellman-group14-sha256',
|
||||||
@@ -209,30 +184,18 @@ wss.on('connection', (ws: WebSocket) => {
|
|||||||
'aes256-cbc',
|
'aes256-cbc',
|
||||||
'3des-cbc'
|
'3des-cbc'
|
||||||
],
|
],
|
||||||
hmac: [
|
hmac: ['hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1', 'hmac-md5'],
|
||||||
'hmac-sha2-256',
|
compress: ['none', 'zlib@openssh.com', 'zlib']
|
||||||
'hmac-sha2-512',
|
|
||||||
'hmac-sha1',
|
|
||||||
'hmac-md5'
|
|
||||||
],
|
|
||||||
compress: [
|
|
||||||
'none',
|
|
||||||
'zlib@openssh.com',
|
|
||||||
'zlib'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (authType === 'key' && key) {
|
if (authType === 'key' && key) {
|
||||||
connectConfig.privateKey = key;
|
connectConfig.privateKey = key;
|
||||||
if (keyPassword) {
|
if (keyPassword) connectConfig.passphrase = keyPassword;
|
||||||
connectConfig.passphrase = keyPassword;
|
if (keyType && keyType !== 'auto') connectConfig.privateKeyType = keyType;
|
||||||
}
|
|
||||||
if (keyType && keyType !== 'auto') {
|
|
||||||
connectConfig.privateKeyType = keyType;
|
|
||||||
}
|
|
||||||
} else if (authType === 'key') {
|
} else if (authType === 'key') {
|
||||||
logger.error('SSH key authentication requested but no key provided');
|
logger.error('SSH key auth requested but no key provided');
|
||||||
ws.send(JSON.stringify({ type: 'error', message: 'SSH key authentication requested but no key provided' }));
|
ws.send(JSON.stringify({ type: 'error', message: 'SSH key auth requested but no key provided' }));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
connectConfig.password = password;
|
connectConfig.password = password;
|
||||||
@@ -249,6 +212,10 @@ wss.on('connection', (ws: WebSocket) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanupSSH() {
|
function cleanupSSH() {
|
||||||
|
if (keepAliveTimer) {
|
||||||
|
clearInterval(keepAliveTimer);
|
||||||
|
keepAliveTimer = null;
|
||||||
|
}
|
||||||
if (sshStream) {
|
if (sshStream) {
|
||||||
try {
|
try {
|
||||||
sshStream.end();
|
sshStream.end();
|
||||||
@@ -257,7 +224,6 @@ wss.on('connection', (ws: WebSocket) => {
|
|||||||
}
|
}
|
||||||
sshStream = null;
|
sshStream = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sshConn) {
|
if (sshConn) {
|
||||||
try {
|
try {
|
||||||
sshConn.end();
|
sshConn.end();
|
||||||
|
|||||||
Reference in New Issue
Block a user