Resume session upon disconnect
This commit is contained in:
parent
9c0ba04b31
commit
89d9429648
12 changed files with 252 additions and 182 deletions
BIN
app/bun.lockb
Executable file → Normal file
BIN
app/bun.lockb
Executable file → Normal file
Binary file not shown.
|
@ -18,6 +18,7 @@
|
||||||
<div id="dropupContent" class="dropup-content">
|
<div id="dropupContent" class="dropup-content">
|
||||||
<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>
|
<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>
|
||||||
<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>
|
<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>
|
||||||
|
<a id="restartBtn"><i class="fas fa-rotate-right fa-fw"></i> Restart Session</a>
|
||||||
<a id="reauthBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Switch User</a>
|
<a id="reauthBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Switch User</a>
|
||||||
<a id="credentialsBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Credentials</a>
|
<a id="credentialsBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Credentials</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -341,7 +341,7 @@ body, html {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #f1f1f1;
|
background-color: #f1f1f1;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
min-width: 160px;
|
min-width: 180px;
|
||||||
bottom: 18px;
|
bottom: 18px;
|
||||||
z-index: 101;
|
z-index: 101;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<div id="dropupContent" class="dropup-content">
|
<div id="dropupContent" class="dropup-content">
|
||||||
<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>
|
<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>
|
||||||
<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>
|
<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>
|
||||||
|
<a id="restartBtn"><i class="fas fa-rotate-right fa-fw"></i> Restart Session</a>
|
||||||
<a id="reauthBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Switch User</a>
|
<a id="reauthBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Switch User</a>
|
||||||
<a id="credentialsBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Credentials</a>
|
<a id="credentialsBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Credentials</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -122,7 +122,7 @@ body, html {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #f1f1f1;
|
background-color: #f1f1f1;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
min-width: 160px;
|
min-width: 180px;
|
||||||
bottom: 18px;
|
bottom: 18px;
|
||||||
z-index: 101;
|
z-index: 101;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@ import { io } from 'socket.io-client';
|
||||||
import { Terminal } from '@xterm/xterm';
|
import { Terminal } from '@xterm/xterm';
|
||||||
import { FitAddon } from '@xterm/addon-fit';
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
import { library, dom } from '@fortawesome/fontawesome-svg-core';
|
import { library, dom } from '@fortawesome/fontawesome-svg-core';
|
||||||
import { faBars, faClipboard, faDownload, faKey, faCog } from '@fortawesome/free-solid-svg-icons';
|
import { faBars, faClipboard, faDownload, faRotateRight, faKey, faCog } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
library.add(faBars, faClipboard, faDownload, faKey, faCog);
|
library.add(faBars, faClipboard, faDownload, faRotateRight, faKey, faCog);
|
||||||
dom.watch();
|
dom.watch();
|
||||||
|
|
||||||
const debug = require('debug')('WebSSH2');
|
const debug = require('debug')('WebSSH2');
|
||||||
|
@ -35,6 +35,7 @@ const term = new Terminal();
|
||||||
const logBtn = document.getElementById('logBtn');
|
const logBtn = document.getElementById('logBtn');
|
||||||
const credentialsBtn = document.getElementById('credentialsBtn');
|
const credentialsBtn = document.getElementById('credentialsBtn');
|
||||||
const reauthBtn = document.getElementById('reauthBtn');
|
const reauthBtn = document.getElementById('reauthBtn');
|
||||||
|
const restartBtn = document.getElementById('restartBtn');
|
||||||
const downloadLogBtn = document.getElementById('downloadLogBtn');
|
const downloadLogBtn = document.getElementById('downloadLogBtn');
|
||||||
const status = document.getElementById('status');
|
const status = document.getElementById('status');
|
||||||
const header = document.getElementById('header');
|
const header = document.getElementById('header');
|
||||||
|
@ -59,12 +60,17 @@ function reauthSession () { // eslint-disable-line
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function restartSession() { // eslint-disable-line
|
||||||
|
debug('restarting');
|
||||||
|
socket.emit('control', 'reauth');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// cross browser method to "download" an element to the local system
|
// cross browser method to "download" an element to the local system
|
||||||
// used for our client-side logging feature
|
// used for our client-side logging feature
|
||||||
function downloadLog() { // eslint-disable-line
|
function downloadLog() { // eslint-disable-line
|
||||||
if (loggedData === true) {
|
if (loggedData === true) {
|
||||||
myFile = `WebSSH2-${logDate.getFullYear()}${
|
myFile = `WebSSH2-${logDate.getFullYear()}${logDate.getMonth() + 1
|
||||||
logDate.getMonth() + 1
|
|
||||||
}${logDate.getDate()}_${logDate.getHours()}${logDate.getMinutes()}${logDate.getSeconds()}.log`;
|
}${logDate.getDate()}_${logDate.getHours()}${logDate.getMinutes()}${logDate.getSeconds()}.log`;
|
||||||
// regex should eliminate escape sequences from being logged.
|
// regex should eliminate escape sequences from being logged.
|
||||||
const blob = new Blob(
|
const blob = new Blob(
|
||||||
|
@ -97,8 +103,7 @@ function toggleLog () { // eslint-disable-line
|
||||||
loggedData = true;
|
loggedData = true;
|
||||||
logBtn.innerHTML = '<i class="fas fa-clipboard fa-fw"></i> Start Log';
|
logBtn.innerHTML = '<i class="fas fa-clipboard fa-fw"></i> Start Log';
|
||||||
currentDate = new Date();
|
currentDate = new Date();
|
||||||
sessionLog = `${sessionLog}\r\n\r\nLog End for ${sessionFooter}: ${currentDate.getFullYear()}/${
|
sessionLog = `${sessionLog}\r\n\r\nLog End for ${sessionFooter}: ${currentDate.getFullYear()}/${currentDate.getMonth() + 1
|
||||||
currentDate.getMonth() + 1
|
|
||||||
}/${currentDate.getDate()} @ ${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}\r\n`;
|
}/${currentDate.getDate()} @ ${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}\r\n`;
|
||||||
logDate = currentDate;
|
logDate = currentDate;
|
||||||
term.focus();
|
term.focus();
|
||||||
|
@ -110,8 +115,7 @@ function toggleLog () { // eslint-disable-line
|
||||||
downloadLogBtn.style.color = '#000';
|
downloadLogBtn.style.color = '#000';
|
||||||
downloadLogBtn.addEventListener('click', downloadLog);
|
downloadLogBtn.addEventListener('click', downloadLog);
|
||||||
currentDate = new Date();
|
currentDate = new Date();
|
||||||
sessionLog = `Log Start for ${sessionFooter}: ${currentDate.getFullYear()}/${
|
sessionLog = `Log Start for ${sessionFooter}: ${currentDate.getFullYear()}/${currentDate.getMonth() + 1
|
||||||
currentDate.getMonth() + 1
|
|
||||||
}/${currentDate.getDate()} @ ${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}\r\n\r\n`;
|
}/${currentDate.getDate()} @ ${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}\r\n\r\n`;
|
||||||
logDate = currentDate;
|
logDate = currentDate;
|
||||||
term.focus();
|
term.focus();
|
||||||
|
@ -130,6 +134,7 @@ function replayCredentials () { // eslint-disable-line
|
||||||
// when dom is changed, listeners are abandonded
|
// when dom is changed, listeners are abandonded
|
||||||
function drawMenu() {
|
function drawMenu() {
|
||||||
logBtn.addEventListener('click', toggleLog);
|
logBtn.addEventListener('click', toggleLog);
|
||||||
|
restartBtn.addEventListener('click', restartSession)
|
||||||
if (allowreauth) {
|
if (allowreauth) {
|
||||||
reauthBtn.addEventListener('click', reauthSession);
|
reauthBtn.addEventListener('click', reauthSession);
|
||||||
reauthBtn.style.display = 'block';
|
reauthBtn.style.display = 'block';
|
||||||
|
@ -144,19 +149,27 @@ function drawMenu() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resizeScreen() {
|
function resizeScreen(warmup = false) {
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
|
if (warmup) {
|
||||||
|
socket.emit('resize', { cols: term.cols - 1, rows: term.rows });
|
||||||
|
}
|
||||||
socket.emit('resize', { cols: term.cols, rows: term.rows });
|
socket.emit('resize', { cols: term.cols, rows: term.rows });
|
||||||
debug(`resize: ${JSON.stringify({ cols: term.cols, rows: term.rows })}`);
|
debug(`resize: ${JSON.stringify({ cols: term.cols, rows: term.rows })}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', resizeScreen, false);
|
window.addEventListener('resize', () => resizeScreen(), false);
|
||||||
|
|
||||||
|
var hasResize = false;
|
||||||
term.onData((data) => {
|
term.onData((data) => {
|
||||||
socket.emit('data', data);
|
socket.emit('data', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('data', (data: string | Uint8Array) => {
|
socket.on('data', (data: string | Uint8Array) => {
|
||||||
|
if (!hasResize) {
|
||||||
|
hasResize = true;
|
||||||
|
setTimeout(() => resizeScreen(true), 1000);
|
||||||
|
}
|
||||||
term.write(data);
|
term.write(data);
|
||||||
if (sessionLogEnable) {
|
if (sessionLogEnable) {
|
||||||
sessionLog += data;
|
sessionLog += data;
|
||||||
|
@ -252,7 +265,13 @@ socket.on('disconnect', (err: any) => {
|
||||||
status.style.backgroundColor = 'red';
|
status.style.backgroundColor = 'red';
|
||||||
status.innerHTML = `WEBSOCKET SERVER DISCONNECTED: ${err}`;
|
status.innerHTML = `WEBSOCKET SERVER DISCONNECTED: ${err}`;
|
||||||
}
|
}
|
||||||
socket.io.reconnection(false);
|
var i = setInterval(() => {
|
||||||
|
if (!socket.connected) {
|
||||||
|
socket.connect()
|
||||||
|
} else {
|
||||||
|
clearInterval(i);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
countdown.classList.remove('active');
|
countdown.classList.remove('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"user": {
|
"user": {
|
||||||
"name": null,
|
"name": null,
|
||||||
"password": null,
|
"password": null,
|
||||||
"privatekey": null,
|
"privateKey": null,
|
||||||
"overridebasic": false
|
"overridebasic": false
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
|
|
|
@ -43,7 +43,7 @@ const configDefault = {
|
||||||
user: {
|
user: {
|
||||||
name: null,
|
name: null,
|
||||||
password: null,
|
password: null,
|
||||||
privatekey: null,
|
privateKey: null,
|
||||||
overridebasic: false,
|
overridebasic: false,
|
||||||
},
|
},
|
||||||
ssh: {
|
ssh: {
|
||||||
|
|
|
@ -69,43 +69,31 @@ async function checkSubnet(socket) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// public
|
/**
|
||||||
module.exports = function appSocket(socket) {
|
* @type {Map<string, {
|
||||||
let login = false;
|
conn: SSH;
|
||||||
|
isLogin: () => boolean;
|
||||||
socket.once('disconnecting', (reason) => {
|
changeSocket: (newSocket: any) => void;
|
||||||
webssh2debug(socket, `SOCKET DISCONNECTING: ${reason}`);
|
}>}
|
||||||
if (login === true) {
|
*/
|
||||||
auditLog(
|
const sshMap = new Map();
|
||||||
socket,
|
|
||||||
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
|
||||||
);
|
|
||||||
login = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function setupConnection() {
|
|
||||||
// if websocket connection arrives without an express session, kill it
|
|
||||||
if (!socket.request.session) {
|
|
||||||
socket.emit('401 UNAUTHORIZED');
|
|
||||||
webssh2debug(socket, 'SOCKET: No Express Session / REJECTED');
|
|
||||||
socket.disconnect(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If configured, check that requsted host is in a permitted subnet
|
|
||||||
if (socket.request.session?.ssh?.allowedSubnets?.length > 0) {
|
|
||||||
checkSubnet(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import ("socket.io-client").Socket} socket
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function setupNewConnection(socket) {
|
||||||
const conn = new SSH();
|
const conn = new SSH();
|
||||||
|
let login = false;
|
||||||
|
let stream;
|
||||||
|
|
||||||
conn.on('banner', (data) => {
|
conn.on('banner', (data) => {
|
||||||
// need to convert to cr/lf for proper formatting
|
// need to convert to cr/lf for proper formatting
|
||||||
socket.emit('data', data.replace(/\r?\n/g, '\r\n').toString('utf-8'));
|
socket.emit('data', data.replace(/\r?\n/g, '\r\n').toString('utf-8'));
|
||||||
});
|
});
|
||||||
|
|
||||||
conn.on('handshake', () => {
|
function handshake() {
|
||||||
socket.emit('setTerminalOpts', socket.request.session.ssh.terminal);
|
socket.emit('setTerminalOpts', socket.request.session.ssh.terminal);
|
||||||
socket.emit('menu');
|
socket.emit('menu');
|
||||||
socket.emit('allowreauth', socket.request.session.ssh.allowreauth);
|
socket.emit('allowreauth', socket.request.session.ssh.allowreauth);
|
||||||
|
@ -118,7 +106,34 @@ module.exports = function appSocket(socket) {
|
||||||
'footer',
|
'footer',
|
||||||
`ssh://${socket.request.session.username}@${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
`ssh://${socket.request.session.username}@${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
socket.on('control', (controlData) => {
|
||||||
|
if (!stream) return;
|
||||||
|
if (controlData === 'replayCredentials' && socket.request.session.ssh.allowreplay) {
|
||||||
|
stream.write(`${socket.request.session.userpassword}\n`);
|
||||||
|
}
|
||||||
|
if (controlData === 'reauth' && socket.request.session.username && login === true) {
|
||||||
|
auditLog(
|
||||||
|
socket,
|
||||||
|
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
||||||
|
);
|
||||||
|
login = false;
|
||||||
|
socket.disconnect(true);
|
||||||
|
conn.end()
|
||||||
|
}
|
||||||
|
webssh2debug(socket, `SOCKET CONTROL: ${controlData}`);
|
||||||
});
|
});
|
||||||
|
socket.on('resize', (data) => {
|
||||||
|
if (!stream) return;
|
||||||
|
stream.setWindow(data.rows, data.cols);
|
||||||
|
webssh2debug(socket, `SOCKET RESIZE: ${JSON.stringify([data.rows, data.cols])}`);
|
||||||
|
});
|
||||||
|
socket.on('data', (data) => {
|
||||||
|
if (!stream) return;
|
||||||
|
stream.write(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
conn.on('handshake', handshake);
|
||||||
|
|
||||||
conn.on('ready', () => {
|
conn.on('ready', () => {
|
||||||
webssh2debug(
|
webssh2debug(
|
||||||
|
@ -134,7 +149,7 @@ module.exports = function appSocket(socket) {
|
||||||
socket.emit('statusBackground', 'green');
|
socket.emit('statusBackground', 'green');
|
||||||
socket.emit('allowreplay', socket.request.session.ssh.allowreplay);
|
socket.emit('allowreplay', socket.request.session.ssh.allowreplay);
|
||||||
const { term, cols, rows } = socket.request.session.ssh;
|
const { term, cols, rows } = socket.request.session.ssh;
|
||||||
conn.shell({ term, cols, rows }, (err, stream) => {
|
conn.shell({ term, cols, rows }, (err, s) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logError(socket, `EXEC ERROR`, err);
|
logError(socket, `EXEC ERROR`, err);
|
||||||
conn.end();
|
conn.end();
|
||||||
|
@ -143,53 +158,32 @@ module.exports = function appSocket(socket) {
|
||||||
}
|
}
|
||||||
socket.once('disconnect', (reason) => {
|
socket.once('disconnect', (reason) => {
|
||||||
webssh2debug(socket, `CLIENT SOCKET DISCONNECT: ${util.inspect(reason)}`);
|
webssh2debug(socket, `CLIENT SOCKET DISCONNECT: ${util.inspect(reason)}`);
|
||||||
conn.end();
|
|
||||||
socket.request.session.destroy();
|
|
||||||
});
|
});
|
||||||
socket.on('error', (errMsg) => {
|
socket.on('error', (errMsg) => {
|
||||||
|
if (!socket) return;
|
||||||
webssh2debug(socket, `SOCKET ERROR: ${errMsg}`);
|
webssh2debug(socket, `SOCKET ERROR: ${errMsg}`);
|
||||||
logError(socket, 'SOCKET ERROR', errMsg);
|
logError(socket, 'SOCKET ERROR', errMsg);
|
||||||
conn.end();
|
conn.end();
|
||||||
socket.disconnect(true);
|
|
||||||
});
|
|
||||||
socket.on('control', (controlData) => {
|
|
||||||
if (controlData === 'replayCredentials' && socket.request.session.ssh.allowreplay) {
|
|
||||||
stream.write(`${socket.request.session.userpassword}\n`);
|
|
||||||
}
|
|
||||||
if (controlData === 'reauth' && socket.request.session.username && login === true) {
|
|
||||||
auditLog(
|
|
||||||
socket,
|
|
||||||
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
|
||||||
);
|
|
||||||
login = false;
|
login = false;
|
||||||
conn.end();
|
|
||||||
socket.disconnect(true);
|
socket.disconnect(true);
|
||||||
}
|
|
||||||
webssh2debug(socket, `SOCKET CONTROL: ${controlData}`);
|
|
||||||
});
|
|
||||||
socket.on('resize', (data) => {
|
|
||||||
stream.setWindow(data.rows, data.cols);
|
|
||||||
webssh2debug(socket, `SOCKET RESIZE: ${JSON.stringify([data.rows, data.cols])}`);
|
|
||||||
});
|
|
||||||
socket.on('data', (data) => {
|
|
||||||
stream.write(data);
|
|
||||||
});
|
});
|
||||||
|
stream = s;
|
||||||
stream.on('data', (data) => {
|
stream.on('data', (data) => {
|
||||||
|
if (!socket) return;
|
||||||
socket.emit('data', data.toString('utf-8'));
|
socket.emit('data', data.toString('utf-8'));
|
||||||
});
|
});
|
||||||
stream.on('close', (code, signal) => {
|
stream.on('close', (code, signal) => {
|
||||||
|
if (!socket) return;
|
||||||
webssh2debug(socket, `STREAM CLOSE: ${util.inspect([code, signal])}`);
|
webssh2debug(socket, `STREAM CLOSE: ${util.inspect([code, signal])}`);
|
||||||
if (socket.request.session?.username && login === true) {
|
if (socket.request.session?.username) {
|
||||||
auditLog(
|
auditLog(
|
||||||
socket,
|
socket,
|
||||||
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
||||||
);
|
);
|
||||||
login = false;
|
|
||||||
}
|
}
|
||||||
if (code !== 0 && typeof code !== 'undefined')
|
if (code !== 0 && typeof code !== 'undefined')
|
||||||
logError(socket, 'STREAM CLOSE', util.inspect({ message: [code, signal] }));
|
logError(socket, 'STREAM CLOSE', util.inspect({ message: [code, signal] }));
|
||||||
socket.disconnect(true);
|
socket.disconnect(true);
|
||||||
conn.end();
|
|
||||||
});
|
});
|
||||||
stream.stderr.on('data', (data) => {
|
stream.stderr.on('data', (data) => {
|
||||||
console.error(`STDERR: ${data}`);
|
console.error(`STDERR: ${data}`);
|
||||||
|
@ -213,19 +207,51 @@ module.exports = function appSocket(socket) {
|
||||||
webssh2debug(socket, 'CONN keyboard-interactive');
|
webssh2debug(socket, 'CONN keyboard-interactive');
|
||||||
finish([socket.request.session.userpassword]);
|
finish([socket.request.session.userpassword]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
conn, isLogin: () => login, changeSocket: (newSocket) => {
|
||||||
|
if (!socket.disconnected) {
|
||||||
|
socket.disconnect();
|
||||||
|
}
|
||||||
|
socket = newSocket;
|
||||||
|
|
||||||
|
// to display after resuming connection
|
||||||
|
if (login && stream) {
|
||||||
|
handshake();
|
||||||
|
stream.write("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// public
|
||||||
|
module.exports = function appSocket(socket) {
|
||||||
|
let login = false;
|
||||||
|
|
||||||
|
socket.once('disconnecting', (reason) => {
|
||||||
|
webssh2debug(socket, `SOCKET DISCONNECTING: ${reason}`);
|
||||||
|
if (login === true) {
|
||||||
|
auditLog(
|
||||||
|
socket,
|
||||||
|
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
||||||
|
);
|
||||||
|
login = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// if websocket connection arrives without an express session, kill it
|
||||||
|
if (!socket.request.session) {
|
||||||
|
socket.emit('401 UNAUTHORIZED');
|
||||||
|
webssh2debug(socket, 'SOCKET: No Express Session / REJECTED');
|
||||||
|
socket.disconnect(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
socket.request.session.username &&
|
!socket.request.session.username ||
|
||||||
(socket.request.session.userpassword || socket.request.session.privatekey) &&
|
!(socket.request.session.userpassword || socket.request.session.privateKey) ||
|
||||||
socket.request.session.ssh
|
!socket.request.session.ssh
|
||||||
) {
|
) {
|
||||||
// console.log('hostkeys: ' + hostkeys[0].[0])
|
|
||||||
const { ssh } = socket.request.session;
|
|
||||||
ssh.username = socket.request.session.username;
|
|
||||||
ssh.password = socket.request.session.userpassword;
|
|
||||||
ssh.tryKeyboard = true;
|
|
||||||
ssh.debug = debug('ssh2');
|
|
||||||
conn.connect(ssh);
|
|
||||||
} else {
|
|
||||||
webssh2debug(
|
webssh2debug(
|
||||||
socket,
|
socket,
|
||||||
`CONN CONNECT: Attempt to connect without session.username/password or session varialbles defined, potentially previously abandoned client session. disconnecting websocket client.\r\nHandshake information: \r\n ${util.inspect(
|
`CONN CONNECT: Attempt to connect without session.username/password or session varialbles defined, potentially previously abandoned client session. disconnecting websocket client.\r\nHandshake information: \r\n ${util.inspect(
|
||||||
|
@ -235,7 +261,30 @@ module.exports = function appSocket(socket) {
|
||||||
socket.emit('ssherror', 'WEBSOCKET ERROR - Refresh the browser and try again');
|
socket.emit('ssherror', 'WEBSOCKET ERROR - Refresh the browser and try again');
|
||||||
socket.request.session.destroy();
|
socket.request.session.destroy();
|
||||||
socket.disconnect(true);
|
socket.disconnect(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If configured, check that requsted host is in a permitted subnet
|
||||||
|
if (socket.request.session?.ssh?.allowedSubnets?.length > 0) {
|
||||||
|
checkSubnet(socket);
|
||||||
}
|
}
|
||||||
setupConnection();
|
|
||||||
|
var connMap = sshMap.get(socket.request.session.username);
|
||||||
|
if (!connMap) {
|
||||||
|
connMap = setupNewConnection(socket);
|
||||||
|
sshMap.set(socket.request.session.username, connMap);
|
||||||
|
} else {
|
||||||
|
connMap.changeSocket(socket);
|
||||||
|
}
|
||||||
|
const { conn, isLogin } = connMap;
|
||||||
|
|
||||||
|
|
||||||
|
const { ssh } = socket.request.session;
|
||||||
|
ssh.username = socket.request.session.username;
|
||||||
|
ssh.password = socket.request.session.userpassword;
|
||||||
|
ssh.tryKeyboard = true;
|
||||||
|
ssh.debug = debug('ssh2');
|
||||||
|
if (!isLogin())
|
||||||
|
conn.connect(ssh);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,15 +5,15 @@
|
||||||
const debug = require('debug')('WebSSH2');
|
const debug = require('debug')('WebSSH2');
|
||||||
const Auth = require('basic-auth');
|
const Auth = require('basic-auth');
|
||||||
|
|
||||||
let defaultCredentials = { username: null, password: null, privatekey: null };
|
let defaultCredentials = { username: null, password: null, privateKey: null };
|
||||||
|
|
||||||
exports.setDefaultCredentials = function setDefaultCredentials({
|
exports.setDefaultCredentials = function setDefaultCredentials({
|
||||||
name: username,
|
name: username,
|
||||||
password,
|
password,
|
||||||
privatekey,
|
privateKey,
|
||||||
overridebasic,
|
overridebasic,
|
||||||
}) {
|
}) {
|
||||||
defaultCredentials = { username, password, privatekey, overridebasic };
|
defaultCredentials = { username, password, privateKey, overridebasic };
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.basicAuth = function basicAuth(req, res, next) {
|
exports.basicAuth = function basicAuth(req, res, next) {
|
||||||
|
@ -21,7 +21,7 @@ exports.basicAuth = function basicAuth(req, res, next) {
|
||||||
// If Authorize: Basic header exists and the password isn't blank
|
// If Authorize: Basic header exists and the password isn't blank
|
||||||
// AND config.user.overridebasic is false, extract basic credentials
|
// AND config.user.overridebasic is false, extract basic credentials
|
||||||
// from client]
|
// from client]
|
||||||
const { username, password, privatekey, overridebasic } = defaultCredentials;
|
const { username, password, privateKey, overridebasic } = defaultCredentials;
|
||||||
if (myAuth && myAuth.pass !== '' && !overridebasic) {
|
if (myAuth && myAuth.pass !== '' && !overridebasic) {
|
||||||
req.session.username = myAuth.name;
|
req.session.username = myAuth.name;
|
||||||
req.session.userpassword = myAuth.pass;
|
req.session.userpassword = myAuth.pass;
|
||||||
|
@ -29,9 +29,9 @@ exports.basicAuth = function basicAuth(req, res, next) {
|
||||||
} else {
|
} else {
|
||||||
req.session.username = username;
|
req.session.username = username;
|
||||||
req.session.userpassword = password;
|
req.session.userpassword = password;
|
||||||
req.session.privatekey = privatekey;
|
req.session.privateKey = privateKey;
|
||||||
}
|
}
|
||||||
if (!req.session.userpassword && !req.session.privatekey) {
|
if (!req.session.userpassword && !req.session.privateKey) {
|
||||||
res.statusCode = 401;
|
res.statusCode = 401;
|
||||||
debug('basicAuth credential request (401)');
|
debug('basicAuth credential request (401)');
|
||||||
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"');
|
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"');
|
||||||
|
|
0
bun.lockb
Executable file → Normal file
0
bun.lockb
Executable file → Normal file
Loading…
Reference in a new issue