chore: client / server bifurcation mostly complete

This commit is contained in:
Bill Church 2024-07-11 21:07:29 +00:00
parent b6e5089ee6
commit bf50fca786
No known key found for this signature in database
9 changed files with 4610 additions and 270 deletions

View file

@ -1,88 +1,35 @@
// app/app.js // app/app.js
"use strict"; 'use strict'
/* jshint esversion: 6, asi: true, node: true */
const path = require("path"); const http = require('http')
const express = require("express"); const socketIo = require('socket.io')
const session = require("express-session"); const config = require('./config')
const logger = require("morgan"); const socketHandler = require('./socket')
const socketIo = require("socket.io");
const myutil = require("./util");
const config = require("./config");
const socketHandler = require("./socket");
const app = express(); const server = http.createServer()
const server = require("http").Server(app);
// Session middleware
const sessionMiddleware = session({
secret: config.session.secret,
name: config.session.name,
resave: false,
saveUninitialized: true,
cookie: {
secure: process.env.NODE_ENV === "production",
maxAge: 24 * 60 * 60 * 1000,
},
});
// Express middleware
app.use(sessionMiddleware);
if (config.accesslog) app.use(logger("common"));
app.disable("x-powered-by");
// Socket.IO setup
const io = socketIo(server, { const io = socketIo(server, {
path: "/ssh/socket.io", path: '/ssh/socket.io',
cors: { cors: {
origin: "http://localhost:8080", origin: 'http://localhost:8080',
methods: ["GET", "POST"], methods: ['GET', 'POST'],
credentials: true, credentials: true
},
});
// Socket.io middleware
io.use((socket, next) => {
sessionMiddleware(socket.request, socket.request.res || {}, next);
});
// WebSocket handling
io.on("connection", (socket) => {
console.log(
"New connection:",
socket.id,
"Transport:",
socket.conn.transport.name
);
// Call the imported socket handler function only once per connection
if (!socket.handled) {
socketHandler(io, socket);
socket.handled = true;
} }
})
socket.on("disconnect", (reason) => { io.on('connection', (socket) => {
console.log("Client disconnected:", socket.id, reason); console.log(
}); 'New connection:',
}); socket.id,
'Transport:',
socket.conn.transport.name
)
// Error handling socketHandler(io, socket)
app.use((req, res, next) => {
res.status(404).send("Sorry can't find that!");
});
app.use((err, req, res, next) => { socket.on('disconnect', (reason) => {
console.error(err.stack); console.log('Client disconnected:', socket.id, reason)
res.status(500).send("Something broke!"); })
}); })
// Graceful shutdown module.exports = { server, config, io }
process.on("SIGINT", () => {
console.log("SIGINT signal received: closing HTTP server");
server.close(() => {
console.log("HTTP server closed");
process.exit(0);
});
});
module.exports = { server, config, io };

View file

@ -1,95 +1,95 @@
// config.js // config.js
const path = require("path"); const path = require('path')
const fs = require("fs"); const fs = require('fs')
const nodeRoot = path.dirname(require.main.filename); const nodeRoot = path.dirname(require.main.filename)
const configPath = path.join(nodeRoot, "config.json"); const configPath = path.join(nodeRoot, 'config.json')
// Default configuration // Default configuration
let config = { let config = {
listen: { listen: {
ip: "0.0.0.0", ip: '0.0.0.0',
port: 2222, port: 2222
}, },
http: { http: {
origins: ["*:*"], origins: ['*:*']
}, },
user: { user: {
name: null, name: null,
password: null, password: null
}, },
ssh: { ssh: {
host: null, host: null,
port: 22, port: 22,
term: "xterm-color", term: 'xterm-color',
readyTimeout: 20000, readyTimeout: 20000,
keepaliveInterval: 120000, keepaliveInterval: 120000,
keepaliveCountMax: 10, keepaliveCountMax: 10
}, },
terminal: { terminal: {
cursorBlink: true, cursorBlink: true,
scrollback: 10000, scrollback: 10000,
tabStopWidth: 8, tabStopWidth: 8,
bellStyle: "sound", bellStyle: 'sound'
}, },
header: { header: {
text: null, text: null,
background: "green", background: 'green'
}, },
session: { session: {
name: "WebSSH2", name: 'WebSSH2',
secret: "mysecret", secret: 'mysecret'
}, },
options: { options: {
challengeButton: true, challengeButton: true,
allowreauth: true, allowreauth: true
}, },
algorithms: { algorithms: {
kex: [ kex: [
"ecdh-sha2-nistp256", 'ecdh-sha2-nistp256',
"ecdh-sha2-nistp384", 'ecdh-sha2-nistp384',
"ecdh-sha2-nistp521", 'ecdh-sha2-nistp521',
"diffie-hellman-group-exchange-sha256", 'diffie-hellman-group-exchange-sha256',
"diffie-hellman-group14-sha1", 'diffie-hellman-group14-sha1'
], ],
cipher: [ cipher: [
"aes128-ctr", 'aes128-ctr',
"aes192-ctr", 'aes192-ctr',
"aes256-ctr", 'aes256-ctr',
"aes128-gcm", 'aes128-gcm',
"aes128-gcm@openssh.com", 'aes128-gcm@openssh.com',
"aes256-gcm", 'aes256-gcm',
"aes256-gcm@openssh.com", 'aes256-gcm@openssh.com',
"aes256-cbc", 'aes256-cbc'
], ],
hmac: ["hmac-sha2-256", "hmac-sha2-512", "hmac-sha1"], hmac: ['hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'],
compress: ["none", "zlib@openssh.com", "zlib"], compress: ['none', 'zlib@openssh.com', 'zlib']
}, },
serverlog: { serverlog: {
client: false, client: false,
server: false, server: false
}, },
accesslog: false, accesslog: false,
verify: false, verify: false
}; }
try { try {
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
console.log("WebSSH2 service reading config from: " + configPath); console.log('WebSSH2 service reading config from: ' + configPath)
config = require("read-config-ng")(configPath); config = require('read-config-ng')(configPath)
} else { } else {
console.error( console.error(
"\n\nERROR: Missing config.json for webssh. Current config: " + '\n\nERROR: Missing config.json for webssh. Current config: ' +
JSON.stringify(config) JSON.stringify(config)
); )
console.error("\n See config.json.sample for details\n\n"); console.error('\n See config.json.sample for details\n\n')
} }
} catch (err) { } catch (err) {
console.error( console.error(
"\n\nERROR: Missing config.json for webssh. Current config: " + '\n\nERROR: Missing config.json for webssh. Current config: ' +
JSON.stringify(config) JSON.stringify(config)
); )
console.error("\n See config.json.sample for details\n\n"); console.error('\n See config.json.sample for details\n\n')
console.error("ERROR:\n\n " + err); console.error('ERROR:\n\n ' + err)
} }
module.exports = config; module.exports = config

View file

@ -1,12 +0,0 @@
// app/expressOptions.js
module.exports = {
dotfiles: "ignore",
etag: false,
extensions: ["htm", "html"],
index: false,
maxAge: "1s",
redirect: false,
setHeaders: function (res, path, stat) {
res.set("x-timestamp", Date.now());
},
};

View file

@ -1,77 +1,77 @@
// app/socket.js // app/socket.js
"use strict"; 'use strict'
const debug = require("debug"); const debug = require('debug')
const debugWebSSH2 = require("debug")("WebSSH2"); const debugWebSSH2 = require('debug')('WebSSH2')
const SSH = require("ssh2").Client; const SSH = require('ssh2').Client
module.exports = function (io) { module.exports = function (io) {
io.on("connection", (socket) => { io.on('connection', (socket) => {
let conn = null; let conn = null
let stream = null; let stream = null
console.log(`SOCKET CONNECT: ${socket.id}`); console.log(`SOCKET CONNECT: ${socket.id}`)
// Remove existing listeners to prevent duplicates // Remove existing listeners to prevent duplicates
socket.removeAllListeners("authenticate"); socket.removeAllListeners('authenticate')
socket.removeAllListeners("data"); socket.removeAllListeners('data')
socket.removeAllListeners("resize"); socket.removeAllListeners('resize')
socket.removeAllListeners("disconnect"); socket.removeAllListeners('disconnect')
// Authenticate user // Authenticate user
socket.on("authenticate", (credentials) => { socket.on('authenticate', (credentials) => {
console.log(`SOCKET AUTHENTICATE: ${socket.id}`); console.log(`SOCKET AUTHENTICATE: ${socket.id}`)
if (isValidCredentials(credentials)) { if (isValidCredentials(credentials)) {
console.log(`SOCKET AUTHENTICATE SUCCESS: ${socket.id}`); console.log(`SOCKET AUTHENTICATE SUCCESS: ${socket.id}`)
initializeConnection(socket, credentials); initializeConnection(socket, credentials)
} else { } else {
console.log(`SOCKET AUTHENTICATE FAILED: ${socket.id}`); console.log(`SOCKET AUTHENTICATE FAILED: ${socket.id}`)
socket.emit("auth_result", { socket.emit('auth_result', {
success: false, success: false,
message: "Invalid credentials", message: 'Invalid credentials'
}); })
} }
}); })
socket.on("disconnect", (reason) => { socket.on('disconnect', (reason) => {
debugWebSSH2(`SOCKET DISCONNECT: ${socket.id}, Reason: ${reason}`); debugWebSSH2(`SOCKET DISCONNECT: ${socket.id}, Reason: ${reason}`)
if (conn) { if (conn) {
conn.end(); conn.end()
} }
// Clean up listeners // Clean up listeners
socket.removeAllListeners(); socket.removeAllListeners()
}); })
socket.on("data", (data) => { socket.on('data', (data) => {
if (stream) { if (stream) {
stream.write(data); stream.write(data)
} }
}); })
socket.on("resize", (data) => { socket.on('resize', (data) => {
if (stream) { if (stream) {
stream.setWindow(data.rows, data.cols); stream.setWindow(data.rows, data.cols)
} }
}); })
function initializeConnection(socket, credentials) { function initializeConnection (socket, credentials) {
if (conn) { if (conn) {
// If there's an existing connection, end it before creating a new one // If there's an existing connection, end it before creating a new one
conn.end(); conn.end()
} }
conn = new SSH(); conn = new SSH()
conn.on("ready", () => { conn.on('ready', () => {
console.log( console.log(
`WebSSH2 Login: user=${credentials.username} from=${socket.handshake.address} host=${credentials.host} port=${credentials.port} sessionID=${socket.id}` `WebSSH2 Login: user=${credentials.username} from=${socket.handshake.address} host=${credentials.host} port=${credentials.port} sessionID=${socket.id}`
); )
socket.emit("auth_result", { success: true }); socket.emit('auth_result', { success: true })
socket.emit("allowreauth", true); socket.emit('allowreauth', true)
socket.emit("allowreplay", true); socket.emit('allowreplay', true)
socket.emit("title", `ssh://${credentials.host}`); socket.emit('title', `ssh://${credentials.host}`)
socket.emit("status", "SSH CONNECTION ESTABLISHED"); socket.emit('status', 'SSH CONNECTION ESTABLISHED')
socket.emit("statusBackground", "green"); socket.emit('statusBackground', 'green')
conn.shell( conn.shell(
{ {
@ -81,37 +81,37 @@ module.exports = function (io) {
}, },
(err, str) => { (err, str) => {
if (err) { if (err) {
return SSHerror("EXEC ERROR", err); return SSHerror('EXEC ERROR', err)
} }
stream = str; stream = str
stream.on("data", (data) => { stream.on('data', (data) => {
socket.emit("data", data.toString("utf-8")); socket.emit('data', data.toString('utf-8'))
}); })
stream.on("close", (code, signal) => { stream.on('close', (code, signal) => {
SSHerror("STREAM CLOSE", { SSHerror('STREAM CLOSE', {
message: message:
code || signal code || signal
? `CODE: ${code} SIGNAL: ${signal}` ? `CODE: ${code} SIGNAL: ${signal}`
: undefined, : undefined
}); })
}); })
stream.stderr.on("data", (data) => { stream.stderr.on('data', (data) => {
console.log("STDERR: " + data); console.log('STDERR: ' + data)
}); })
} }
); )
}); })
conn.on("banner", (data) => { conn.on('banner', (data) => {
socket.emit("data", data.replace(/\r?\n/g, "\r\n")); socket.emit('data', data.replace(/\r?\n/g, '\r\n'))
}); })
conn.on("end", () => SSHerror("CONN END BY HOST")); conn.on('end', () => SSHerror('CONN END BY HOST'))
conn.on("close", () => SSHerror("CONN CLOSE")); conn.on('close', () => SSHerror('CONN CLOSE'))
conn.on("error", (err) => SSHerror("CONN ERROR", err)); conn.on('error', (err) => SSHerror('CONN ERROR', err))
conn.connect({ conn.connect({
host: credentials.host, host: credentials.host,
@ -123,24 +123,24 @@ module.exports = function (io) {
readyTimeout: credentials.readyTimeout, readyTimeout: credentials.readyTimeout,
keepaliveInterval: credentials.keepaliveInterval, keepaliveInterval: credentials.keepaliveInterval,
keepaliveCountMax: credentials.keepaliveCountMax, keepaliveCountMax: credentials.keepaliveCountMax,
debug: debug("ssh2") debug: debug('ssh2')
}); })
} }
function SSHerror(myFunc, err) { function SSHerror (myFunc, err) {
const errorMessage = err ? `: ${err.message}` : ""; const errorMessage = err ? `: ${err.message}` : ''
console.log(`WebSSH2 error: ${myFunc}${errorMessage}`); console.log(`WebSSH2 error: ${myFunc}${errorMessage}`)
socket.emit("ssherror", `SSH ${myFunc}${errorMessage}`); socket.emit('ssherror', `SSH ${myFunc}${errorMessage}`)
if (conn) { if (conn) {
conn.end(); conn.end()
} }
// Don't disconnect the socket here, let the client handle reconnection if necessary // Don't disconnect the socket here, let the client handle reconnection if necessary
// socket.disconnect(true); // socket.disconnect(true);
} }
function isValidCredentials(credentials) { function isValidCredentials (credentials) {
// Implement your credential validation logic here // Implement your credential validation logic here
return credentials && credentials.username && credentials.password; return credentials && credentials.username && credentials.password
} }
}); })
}; }

View file

@ -1,13 +0,0 @@
"use strict";
// app/util.js
/* jshint esversion: 6, asi: true, node: true */
// private
require("colors"); // allow for color property extensions in log messages
var debug = require("debug")("WebSSH2");
var Auth = require("basic-auth");
// takes a string, makes it boolean (true if the string is true, false otherwise)
exports.parseBool = function parseBool(str) {
return str.toLowerCase() === "true";
};

View file

@ -1,4 +1,4 @@
"use strict"; 'use strict'
/* jshint esversion: 6, asi: true, node: true */ /* jshint esversion: 6, asi: true, node: true */
/* /*
* index.js * index.js
@ -7,24 +7,14 @@
* Bill Church - https://github.com/billchurch/WebSSH2 - May 2017 * Bill Church - https://github.com/billchurch/WebSSH2 - May 2017
* *
*/ */
const { server, config } = require("./app/app"); const { server, config } = require('./app/app')
server.listen(config.listen.port, config.listen.ip, () => { server.listen(config.listen.port, config.listen.ip, () => {
console.log( console.log(
`WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}` `WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}`
); )
}); })
server.on("error", function (err) { server.on('error', function (err) {
if (err.code === "EADDRINUSE") { console.log('WebSSH2 server.listen ERROR: ' + err.code)
config.listen.port++; })
console.warn(
"WebSSH2 Address in use, retrying on port " + config.listen.port
);
setTimeout(function () {
server.listen(config.listen.port);
}, 250);
} else {
console.log("WebSSH2 server.listen ERROR: " + err.code);
}
});

4434
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "webssh2-server", "name": "webssh2-server",
"version": "0.2.13", "version": "0.2.14",
"ignore": [ "ignore": [
".gitignore" ".gitignore"
], ],
@ -32,16 +32,10 @@
"url": "https://github.com/billchurch/WebSSH2/issues" "url": "https://github.com/billchurch/WebSSH2/issues"
}, },
"dependencies": { "dependencies": {
"basic-auth": "~2.0.1",
"debug": "~4.1.0", "debug": "~4.1.0",
"express": "~4.16.4",
"express-session": "~1.15.6",
"morgan": "~1.9.1",
"read-config-ng": "~3.0.7", "read-config-ng": "~3.0.7",
"serve-favicon": "~2.5.0",
"socket.io": "~2.2.0", "socket.io": "~2.2.0",
"ssh2": "~0.8.9", "ssh2": "~0.8.9"
"validator": "~10.9.0"
}, },
"scripts": { "scripts": {
"start": "node index.js", "start": "node index.js",

View file

@ -1,38 +1,38 @@
var express = require("express"); var express = require('express')
var app = express(); var app = express()
var server = require("http").createServer(app); var server = require('http').createServer(app)
var io = require("socket.io")(server, { var io = require('socket.io')(server, {
path: "/ssh/socket.io", path: '/ssh/socket.io',
cors: { cors: {
origin: "http://localhost:8080", origin: 'http://localhost:8080',
methods: ["GET", "POST"], methods: ['GET', 'POST'],
credentials: true, credentials: true
}, }
}); })
var PORT = 3000; var PORT = 3000
io.on("connection", function (socket) { io.on('connection', function (socket) {
console.log("A client connected"); console.log('A client connected')
socket.on("authenticate", function (credentials) { socket.on('authenticate', function (credentials) {
console.log("Received credentials:", credentials); console.log('Received credentials:', credentials)
// Here you would typically validate the credentials // Here you would typically validate the credentials
// For this example, we'll just echo back a success message // For this example, we'll just echo back a success message
var authResult = { var authResult = {
success: true, success: true,
message: "Authentication successful", message: 'Authentication successful'
}; }
socket.emit("auth_result", authResult); socket.emit('auth_result', authResult)
}); })
socket.on("disconnect", function () { socket.on('disconnect', function () {
console.log("A client disconnected"); console.log('A client disconnected')
}); })
}); })
server.listen(PORT, function () { server.listen(PORT, function () {
console.log("Server running on port " + PORT); console.log('Server running on port ' + PORT)
}); })