From aa633aef0bfab75c70dac6f46c047e1f1b0c345e Mon Sep 17 00:00:00 2001 From: Bill Church Date: Tue, 13 Aug 2024 12:38:00 +0000 Subject: [PATCH] chore: formatting --- app/app.js | 83 ++++++++------ app/config.js | 222 ++++++++++++++++++++----------------- app/connectionHandler.js | 89 +++++++++------ app/routes.js | 42 +++---- app/socket.js | 230 +++++++++++++++++++++------------------ index.js | 6 +- 6 files changed, 374 insertions(+), 298 deletions(-) diff --git a/app/app.js b/app/app.js index 7582c50..69be127 100644 --- a/app/app.js +++ b/app/app.js @@ -1,50 +1,57 @@ // server // app/app.js -'use strict' +"use strict" -const createDebug = require('debug') -const debug = createDebug('webssh2') -const http = require('http') -const express = require('express') -const socketIo = require('socket.io') -const path = require('path') -const bodyParser = require('body-parser') -const session = require('express-session') +const createDebug = require("debug") +const debug = createDebug("webssh2") +const http = require("http") +const express = require("express") +const socketIo = require("socket.io") +const path = require("path") +const bodyParser = require("body-parser") +const session = require("express-session") const sharedsession = require("express-socket.io-session") -const config = require('./config') -const socketHandler = require('./socket') -const sshRoutes = require('./routes') +const config = require("./config") +const socketHandler = require("./socket") +const sshRoutes = require("./routes") /** * Creates and configures the Express application * @returns {express.Application} The Express application instance */ function createApp() { - const app = express(); + const app = express() // Resolve the correct path to the webssh2_client module - const clientPath = path.resolve(__dirname, '..', 'node_modules', 'webssh2_client', 'client', 'public'); + const clientPath = path.resolve( + __dirname, + "..", + "node_modules", + "webssh2_client", + "client", + "public" + ) // Set up session middleware const sessionMiddleware = session({ - secret: config.session.secret || 'webssh2_secret', + secret: config.session.secret || "webssh2_secret", resave: false, saveUninitialized: true, - name: config.session.name || 'webssh2.sid' - }); - app.use(sessionMiddleware); + name: config.session.name || "webssh2.sid" + }) + app.use(sessionMiddleware) // Handle POST and GET parameters - app.use(bodyParser.urlencoded({ extended: true })); - app.use(bodyParser.json()); + app.use(bodyParser.urlencoded({ extended: true })) + app.use(bodyParser.json()) // Serve static files from the webssh2_client module with a custom prefix - app.use('/ssh/assets', express.static(clientPath)); + app.use("/ssh/assets", express.static(clientPath)) // Use the SSH routes - app.use('/ssh', sshRoutes); + app.use("/ssh", sshRoutes) - return { app, sessionMiddleware }; + return { app, sessionMiddleware } } /** @@ -67,18 +74,20 @@ function createServer(app) { function configureSocketIO(server, sessionMiddleware) { const io = socketIo(server, { serveClient: false, - path: '/ssh/socket.io', - pingTimeout: 60000, // 1 minute + path: "/ssh/socket.io", + pingTimeout: 60000, // 1 minute pingInterval: 25000, // 25 seconds cors: getCorsConfig() - }); + }) // Share session with io sockets - io.use(sharedsession(sessionMiddleware, { - autoSave: true - })); + io.use( + sharedsession(sessionMiddleware, { + autoSave: true + }) + ) - return io; + return io } /** @@ -87,8 +96,8 @@ function configureSocketIO(server, sessionMiddleware) { */ function getCorsConfig() { return { - origin: config.origin || ['*.*'], - methods: ['GET', 'POST'], + origin: config.origin || ["*.*"], + methods: ["GET", "POST"], credentials: true } } @@ -115,10 +124,12 @@ function startServer() { // Start the server server.listen(config.listen.port, config.listen.ip, () => { - console.log(`WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}`) + console.log( + `WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}` + ) }) - server.on('error', handleServerError) + server.on("error", handleServerError) return { server, io, app } } @@ -128,8 +139,8 @@ function startServer() { * @param {Error} err - The error object */ function handleServerError(err) { - console.error('WebSSH2 server.listen ERROR:', err.code) + console.error("WebSSH2 server.listen ERROR:", err.code) } // Don't start the server immediately, export the function instead -module.exports = { startServer, config } \ No newline at end of file +module.exports = { startServer, config } diff --git a/app/config.js b/app/config.js index 092249f..5594e06 100644 --- a/app/config.js +++ b/app/config.js @@ -1,12 +1,12 @@ -// server +// server // app/config.js -'use strict' +"use strict" -const path = require('path') -const fs = require('fs') -const readConfig = require('read-config-ng') -const Ajv = require('ajv') -const crypto = require('crypto') +const path = require("path") +const fs = require("fs") +const readConfig = require("read-config-ng") +const Ajv = require("ajv") +const crypto = require("crypto") /** * @typedef {Object} Config @@ -54,11 +54,11 @@ const crypto = require('crypto') */ const defaultConfig = { listen: { - ip: '0.0.0.0', + ip: "0.0.0.0", port: 2222 }, http: { - origins: ['*:*'] + origins: ["*:*"] }, user: { name: null, @@ -67,7 +67,7 @@ const defaultConfig = { ssh: { host: null, port: 22, - term: 'xterm-color', + term: "xterm-color", readyTimeout: 20000, keepaliveInterval: 120000, keepaliveCountMax: 10 @@ -76,11 +76,11 @@ const defaultConfig = { cursorBlink: true, scrollback: 10000, tabStopWidth: 8, - bellStyle: 'sound' + bellStyle: "sound" }, header: { text: null, - background: 'green' + background: "green" }, options: { challengeButton: true, @@ -89,28 +89,28 @@ const defaultConfig = { }, algorithms: { kex: [ - 'ecdh-sha2-nistp256', - 'ecdh-sha2-nistp384', - 'ecdh-sha2-nistp521', - 'diffie-hellman-group-exchange-sha256', - 'diffie-hellman-group14-sha1' + "ecdh-sha2-nistp256", + "ecdh-sha2-nistp384", + "ecdh-sha2-nistp521", + "diffie-hellman-group-exchange-sha256", + "diffie-hellman-group14-sha1" ], cipher: [ - 'aes128-ctr', - 'aes192-ctr', - 'aes256-ctr', - 'aes128-gcm', - 'aes128-gcm@openssh.com', - 'aes256-gcm', - 'aes256-gcm@openssh.com', - 'aes256-cbc' + "aes128-ctr", + "aes192-ctr", + "aes256-ctr", + "aes128-gcm", + "aes128-gcm@openssh.com", + "aes256-gcm", + "aes256-gcm@openssh.com", + "aes256-cbc" ], - hmac: ['hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'], - compress: ['none', 'zlib@openssh.com', 'zlib'] + hmac: ["hmac-sha2-256", "hmac-sha2-512", "hmac-sha1"], + compress: ["none", "zlib@openssh.com", "zlib"] }, session: { secret: generateSecureSecret(), - name: 'webssh2.sid' + name: "webssh2.sid" }, serverlog: { client: false, @@ -124,115 +124,134 @@ const defaultConfig = { * Schema for validating the config */ const configSchema = { - type: 'object', + type: "object", properties: { listen: { - type: 'object', + type: "object", properties: { - ip: { type: 'string', format: 'ipv4' }, - port: { type: 'integer', minimum: 1, maximum: 65535 } + ip: { type: "string", format: "ipv4" }, + port: { type: "integer", minimum: 1, maximum: 65535 } }, - required: ['ip', 'port'] + required: ["ip", "port"] }, http: { - type: 'object', + type: "object", properties: { origins: { - type: 'array', - items: { type: 'string' } + type: "array", + items: { type: "string" } } }, - required: ['origins'] + required: ["origins"] }, user: { - type: 'object', + type: "object", properties: { - name: { type: ['string', 'null'] }, - password: { type: ['string', 'null'] } + name: { type: ["string", "null"] }, + password: { type: ["string", "null"] } }, - required: ['name', 'password'] + required: ["name", "password"] }, ssh: { - type: 'object', + type: "object", properties: { - host: { type: ['string', 'null'] }, - port: { type: 'integer', minimum: 1, maximum: 65535 }, - term: { type: 'string' }, - readyTimeout: { type: 'integer' }, - keepaliveInterval: { type: 'integer' }, - keepaliveCountMax: { type: 'integer' } + host: { type: ["string", "null"] }, + port: { type: "integer", minimum: 1, maximum: 65535 }, + term: { type: "string" }, + readyTimeout: { type: "integer" }, + keepaliveInterval: { type: "integer" }, + keepaliveCountMax: { type: "integer" } }, - required: ['host', 'port', 'term', 'readyTimeout', 'keepaliveInterval', 'keepaliveCountMax'] + required: [ + "host", + "port", + "term", + "readyTimeout", + "keepaliveInterval", + "keepaliveCountMax" + ] }, terminal: { - type: 'object', + type: "object", properties: { - cursorBlink: { type: 'boolean' }, - scrollback: { type: 'integer' }, - tabStopWidth: { type: 'integer' }, - bellStyle: { type: 'string' } + cursorBlink: { type: "boolean" }, + scrollback: { type: "integer" }, + tabStopWidth: { type: "integer" }, + bellStyle: { type: "string" } }, - required: ['cursorBlink', 'scrollback', 'tabStopWidth', 'bellStyle'] + required: ["cursorBlink", "scrollback", "tabStopWidth", "bellStyle"] }, header: { - type: 'object', + type: "object", properties: { - text: { type: ['string', 'null'] }, - background: { type: 'string' } + text: { type: ["string", "null"] }, + background: { type: "string" } }, - required: ['text', 'background'] + required: ["text", "background"] }, options: { - type: 'object', + type: "object", properties: { - challengeButton: { type: 'boolean' }, - allowReauth: { type: 'boolean' }, - allowReplay: { type: 'boolean' } + challengeButton: { type: "boolean" }, + allowReauth: { type: "boolean" }, + allowReplay: { type: "boolean" } }, - required: ['challengeButton', 'allowReauth', 'allowReplay'] + required: ["challengeButton", "allowReauth", "allowReplay"] }, algorithms: { - type: 'object', + type: "object", properties: { kex: { - type: 'array', - items: { type: 'string' } + type: "array", + items: { type: "string" } }, cipher: { - type: 'array', - items: { type: 'string' } + type: "array", + items: { type: "string" } }, hmac: { - type: 'array', - items: { type: 'string' } + type: "array", + items: { type: "string" } }, compress: { - type: 'array', - items: { type: 'string' } + type: "array", + items: { type: "string" } } }, - required: ['kex', 'cipher', 'hmac', 'compress'] + required: ["kex", "cipher", "hmac", "compress"] }, session: { - type: 'object', + type: "object", properties: { - secret: { type: 'string' }, - name: { type: 'string' } + secret: { type: "string" }, + name: { type: "string" } }, - required: ['secret', 'name'] + required: ["secret", "name"] }, serverlog: { - type: 'object', + type: "object", properties: { - client: { type: 'boolean' }, - server: { type: 'boolean' } + client: { type: "boolean" }, + server: { type: "boolean" } }, - required: ['client', 'server'] + required: ["client", "server"] }, - accesslog: { type: 'boolean' }, - verify: { type: 'boolean' } + accesslog: { type: "boolean" }, + verify: { type: "boolean" } }, - required: ['listen', 'http', 'user', 'ssh', 'terminal', 'header', 'options', 'algorithms', 'serverlog', 'accesslog', 'verify'] + required: [ + "listen", + "http", + "user", + "ssh", + "terminal", + "header", + "options", + "algorithms", + "serverlog", + "accesslog", + "verify" + ] } /** @@ -241,7 +260,7 @@ const configSchema = { */ function getConfigPath() { const nodeRoot = path.dirname(require.main.filename) - return path.join(nodeRoot, 'config.json') + return path.join(nodeRoot, "config.json") } /** @@ -250,7 +269,7 @@ function getConfigPath() { * @returns {Config} The configuration object */ function readConfigFile(configPath) { - console.log('WebSSH2 service reading config from: ' + configPath) + console.log("WebSSH2 service reading config from: " + configPath) return readConfig(configPath) } @@ -264,9 +283,11 @@ function validateConfig(config) { const ajv = new Ajv() const validate = ajv.compile(configSchema) const valid = validate(config) - console.log('WebSSH2 service validating config') + console.log("WebSSH2 service validating config") if (!valid) { - throw new Error('Config validation error: ' + ajv.errorsText(validate.errors)) + throw new Error( + "Config validation error: " + ajv.errorsText(validate.errors) + ) } return config } @@ -279,7 +300,7 @@ function validateConfig(config) { function logError(message, error) { console.error(message) if (error) { - console.error('ERROR:\n\n ' + error) + console.error("ERROR:\n\n " + error) } } @@ -293,26 +314,29 @@ function loadConfig() { try { if (fs.existsSync(configPath)) { const providedConfig = readConfigFile(configPath) - + // Deep merge the provided config with the default config - const mergedConfig = deepMerge(JSON.parse(JSON.stringify(defaultConfig)), providedConfig) - + const mergedConfig = deepMerge( + JSON.parse(JSON.stringify(defaultConfig)), + providedConfig + ) + const validatedConfig = validateConfig(mergedConfig) - console.log('Merged and validated configuration') + console.log("Merged and validated configuration") return validatedConfig } else { logError( - '\n\nERROR: Missing config.json for webssh. Using default config: ' + + "\n\nERROR: Missing config.json for webssh. Using default config: " + JSON.stringify(defaultConfig) + - '\n\n See config.json.sample for details\n\n' + "\n\n See config.json.sample for details\n\n" ) return defaultConfig } } catch (err) { logError( - '\n\nERROR: Problem loading config.json for webssh. Using default config: ' + + "\n\nERROR: Problem loading config.json for webssh. Using default config: " + JSON.stringify(defaultConfig) + - '\n\n See config.json.sample for details\n\n', + "\n\n See config.json.sample for details\n\n", err ) return defaultConfig @@ -324,7 +348,7 @@ function loadConfig() { * @returns {string} A random 32-byte hex string */ function generateSecureSecret() { - return crypto.randomBytes(32).toString('hex') + return crypto.randomBytes(32).toString("hex") } /** diff --git a/app/connectionHandler.js b/app/connectionHandler.js index 7e2349d..d649418 100644 --- a/app/connectionHandler.js +++ b/app/connectionHandler.js @@ -1,48 +1,65 @@ // server // app/connectionHandler.js -var path = require('path'); -var fs = require('fs'); -var extend = require('util')._extend; +var path = require("path") +var fs = require("fs") +var extend = require("util")._extend function handleConnection(req, res, urlParams) { - urlParams = urlParams || {}; - - // The path to the client directory of the webssh2 module. - var clientPath = path.resolve(__dirname, '..', 'node_modules', 'webssh2_client', 'client', 'public'); + urlParams = urlParams || {} - // Combine URL parameters, query parameters, and form data - var connectionParams = extend({}, urlParams); - extend(connectionParams, req.query); - extend(connectionParams, req.body || {}); - - // Inject configuration - var config = { - socket: { - url: req.protocol + '://' + req.get('host'), - path: '/ssh/socket.io' - }, - ssh: { - host: urlParams.host || '', - port: urlParams.port || 22 - }, - autoConnect: !!req.session.sshCredentials - }; - - // Read the client.htm file - fs.readFile(path.join(clientPath, 'client.htm'), 'utf8', function(err, data) { + // The path to the client directory of the webssh2 module. + var clientPath = path.resolve( + __dirname, + "..", + "node_modules", + "webssh2_client", + "client", + "public" + ) + + // Combine URL parameters, query parameters, and form data + var connectionParams = extend({}, urlParams) + extend(connectionParams, req.query) + extend(connectionParams, req.body || {}) + + // Inject configuration + var config = { + socket: { + url: req.protocol + "://" + req.get("host"), + path: "/ssh/socket.io" + }, + ssh: { + host: urlParams.host || "", + port: urlParams.port || 22 + }, + autoConnect: !!req.session.sshCredentials + } + + // Read the client.htm file + fs.readFile( + path.join(clientPath, "client.htm"), + "utf8", + function (err, data) { if (err) { - return res.status(500).send('Error loading client file'); + return res.status(500).send("Error loading client file") } - + // Replace relative paths with the correct path - var modifiedHtml = data.replace(/(src|href)="(?!http|\/\/)/g, '$1="/ssh/assets/'); - + var modifiedHtml = data.replace( + /(src|href)="(?!http|\/\/)/g, + '$1="/ssh/assets/' + ) + // Inject the configuration into the HTML - modifiedHtml = modifiedHtml.replace('window.webssh2Config = null;', 'window.webssh2Config = ' + JSON.stringify(config) + ';'); - + modifiedHtml = modifiedHtml.replace( + "window.webssh2Config = null;", + "window.webssh2Config = " + JSON.stringify(config) + ";" + ) + // Send the modified HTML - res.send(modifiedHtml); - }); + res.send(modifiedHtml) + } + ) } -module.exports = handleConnection; \ No newline at end of file +module.exports = handleConnection diff --git a/app/routes.js b/app/routes.js index 4df3c3a..7f615fc 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,34 +1,34 @@ // server // /app/routes.js -const createDebug = require('debug') -const debug = createDebug('webssh2:routes') -const express = require('express'); -const router = express.Router(); -const handleConnection = require('./connectionHandler'); -const basicAuth = require('basic-auth'); +const createDebug = require("debug") +const debug = createDebug("webssh2:routes") +const express = require("express") +const router = express.Router() +const handleConnection = require("./connectionHandler") +const basicAuth = require("basic-auth") function auth(req, res, next) { - debug('Authenticating user with HTTP Basic Auth'); - var credentials = basicAuth(req); + debug("Authenticating user with HTTP Basic Auth") + var credentials = basicAuth(req) if (!credentials) { - res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH2"'); - return res.status(401).send('Authentication required.'); + res.setHeader("WWW-Authenticate", 'Basic realm="WebSSH2"') + return res.status(401).send("Authentication required.") } // Store credentials in session - req.session.sshCredentials = credentials; - next(); + req.session.sshCredentials = credentials + next() } // Scenario 1: No auth required, uses websocket authentication instead -router.get('/', function(req, res) { - debug('Accessed /ssh route'); - handleConnection(req, res); -}); +router.get("/", function (req, res) { + debug("Accessed /ssh route") + handleConnection(req, res) +}) // Scenario 2: Auth required, uses HTTP Basic Auth -router.get('/host/:host', auth, function(req, res) { - debug(`Accessed /ssh/host/${req.params.host} route`); - handleConnection(req, res, { host: req.params.host }); -}); +router.get("/host/:host", auth, function (req, res) { + debug(`Accessed /ssh/host/${req.params.host} route`) + handleConnection(req, res, { host: req.params.host }) +}) -module.exports = router; \ No newline at end of file +module.exports = router diff --git a/app/socket.js b/app/socket.js index 3622051..032af98 100644 --- a/app/socket.js +++ b/app/socket.js @@ -1,11 +1,11 @@ // server // app/socket.js -'use strict' +"use strict" -const createDebug = require('debug') -const { header } = require('./config') -const debug = createDebug('webssh2:socket') -const SSH = require('ssh2').Client +const createDebug = require("debug") +const { header } = require("./config") +const debug = createDebug("webssh2:socket") +const SSH = require("ssh2").Client /** * Handles WebSocket connections for SSH @@ -13,7 +13,7 @@ const SSH = require('ssh2').Client * @param {Object} config - The configuration object */ module.exports = function (io, config) { - io.on('connection', (socket) => handleConnection(socket, config)) + io.on("connection", (socket) => handleConnection(socket, config)) } /** @@ -22,20 +22,22 @@ module.exports = function (io, config) { * @param {Object} config - The configuration object */ function handleConnection(socket, config) { - let conn = null; - let stream = null; - let authenticated = false; - let isConnectionClosed = false; + let conn = null + let stream = null + let authenticated = false + let isConnectionClosed = false - debug(`CONNECT: ${socket.id}, URL: ${socket.handshake.url}`); + debug(`CONNECT: ${socket.id}, URL: ${socket.handshake.url}`) removeExistingListeners(socket) setupInitialSocketListeners(socket, config) // Emit an event to the client to request authentication if (!authenticated) { - debug(`Requesting authentication for ${socket.id} and authenticated is ${authenticated}`); - socket.emit('authentication', { action: 'request_auth' }); + debug( + `Requesting authentication for ${socket.id} and authenticated is ${authenticated}` + ) + socket.emit("authentication", { action: "request_auth" }) } /** @@ -43,9 +45,11 @@ function handleConnection(socket, config) { * @param {import('socket.io').Socket} socket - The Socket.IO socket */ function removeExistingListeners(socket) { - ['authenticate', 'data', 'resize', 'disconnect', 'control'].forEach(event => { - socket.removeAllListeners(event) - }) + ;["authenticate", "data", "resize", "disconnect", "control"].forEach( + (event) => { + socket.removeAllListeners(event) + } + ) } /** @@ -54,41 +58,47 @@ function handleConnection(socket, config) { * @param {Object} config - The configuration object */ function setupInitialSocketListeners(socket, config) { - socket.on('error', (error) => console.error(`Socket error for ${socket.id}:`, error)); - socket.on('authenticate', creds => handleAuthentication(socket, creds, config)) - socket.on('disconnect', (reason) => { - debug(`Client ${socket.id} disconnected. Reason: ${reason}`); - debug('Socket state at disconnect:', socket.conn.transport.readyState); + socket.on("error", (error) => + console.error(`Socket error for ${socket.id}:`, error) + ) + socket.on("authenticate", (creds) => + handleAuthentication(socket, creds, config) + ) + socket.on("disconnect", (reason) => { + debug(`Client ${socket.id} disconnected. Reason: ${reason}`) + debug("Socket state at disconnect:", socket.conn.transport.readyState) if (conn) { - conn.end(); - conn = null; + conn.end() + conn = null } if (stream) { - stream.end(); - stream = null; + stream.end() + stream = null } - }); + }) } -/** - * Handles authentication attempts - * @param {import('socket.io').Socket} socket - The Socket.IO socket - * - * @param {Credentials} creds - The credentials for authentication - * @param {Object} config - The configuration object - */ -function handleAuthentication(socket, creds, config) { - debug(`AUTHENTICATE: ${socket.id}, Host: ${creds.host}`); - - if (isValidCredentials(creds)) { - debug(`CREDENTIALS VALID: ${socket.id}, Host: ${creds.host}`); - initializeConnection(socket, creds, config); - } else { - debug(`CREDENTIALS INVALID: ${socket.id}, Host: ${creds.host}`); - socket.emit('authentication', { success: false, message: 'Invalid credentials format' }); + /** + * Handles authentication attempts + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * + * @param {Credentials} creds - The credentials for authentication + * @param {Object} config - The configuration object + */ + function handleAuthentication(socket, creds, config) { + debug(`AUTHENTICATE: ${socket.id}, Host: ${creds.host}`) + if (isValidCredentials(creds)) { + debug(`CREDENTIALS VALID: ${socket.id}, Host: ${creds.host}`) + initializeConnection(socket, creds, config) + } else { + debug(`CREDENTIALS INVALID: ${socket.id}, Host: ${creds.host}`) + socket.emit("authentication", { + success: false, + message: "Invalid credentials format" + }) + } } -} /** * Initializes an SSH connection @@ -97,42 +107,51 @@ function handleAuthentication(socket, creds, config) { * @param {Object} config - The configuration object */ function initializeConnection(socket, creds, config) { - debug(`INITIALIZING SSH CONNECTION: ${socket.id}, Host: ${creds.host}`); + debug(`INITIALIZING SSH CONNECTION: ${socket.id}, Host: ${creds.host}`) if (conn) { conn.end() } - + conn = new SSH() - - conn.on('ready', () => { - authenticated = true; - debug(`SSH CONNECTION READY: ${socket.id}, Host: ${creds.host}`); - socket.emit('authentication', { action: 'auth_result', success: true }); + + conn.on("ready", () => { + authenticated = true + debug(`SSH CONNECTION READY: ${socket.id}, Host: ${creds.host}`) + socket.emit("authentication", { action: "auth_result", success: true }) // Emit consolidated permissions - socket.emit('permissions', { + socket.emit("permissions", { allowReplay: config.options.allowReplay || false, allowReauth: config.options.allowReauth || false - }); + }) if (config.header && config.header.text !== null) { - debug('header:', config.header) - socket.emit('updateUI', { header: config.header } || { header: { text: '', background: '' } }) + debug("header:", config.header) + socket.emit( + "updateUI", + { header: config.header } || { header: { text: "", background: "" } } + ) } - + setupSSHListeners(socket, creds) initializeShell(socket, creds) }) - - conn.on('error', err => { - console.error(`SSH CONNECTION ERROR: ${socket.id}, Host: ${creds.host}, Error: ${err.message}`); - if (err.level === 'client-authentication') { - socket.emit('authentication', { action: 'auth_result', success: false, message: 'Authentication failed' }) + + conn.on("error", (err) => { + console.error( + `SSH CONNECTION ERROR: ${socket.id}, Host: ${creds.host}, Error: ${err.message}` + ) + if (err.level === "client-authentication") { + socket.emit("authentication", { + action: "auth_result", + success: false, + message: "Authentication failed" + }) } else { - handleError(socket, 'SSH CONNECTION ERROR', err) + handleError(socket, "SSH CONNECTION ERROR", err) } }) - + conn.connect(getSSHConfig(creds, config)) } @@ -142,13 +161,15 @@ function handleAuthentication(socket, creds, config) { * @param {Credentials} creds - The user credentials */ function setupSSHListeners(socket, creds) { - conn.on('banner', data => handleBanner(socket, data)) - conn.on('end', () => handleSSHEnd(socket)) - conn.on('close', () => handleSSHClose(socket)) + conn.on("banner", (data) => handleBanner(socket, data)) + conn.on("end", () => handleSSHEnd(socket)) + conn.on("close", () => handleSSHClose(socket)) - socket.on('data', data => handleData(socket, stream, data)) - socket.on('resize', data => handleResize(stream, data)) - socket.on('control', controlData => handleControl(socket, stream, creds, controlData, config)) + socket.on("data", (data) => handleData(socket, stream, data)) + socket.on("resize", (data) => handleResize(stream, data)) + socket.on("control", (controlData) => + handleControl(socket, stream, creds, controlData, config) + ) } /** @@ -165,17 +186,18 @@ function handleAuthentication(socket, creds, config) { }, (err, str) => { if (err) { - return handleError(socket, 'EXEC ERROR', err) + return handleError(socket, "EXEC ERROR", err) } stream = str - stream.on('data', data => socket.emit('data', data.toString('utf-8'))) - stream.on('close', (code, signal) => { - handleError(socket, 'STREAM CLOSE', { - message: code || signal ? `CODE: ${code} SIGNAL: ${signal}` : undefined + stream.on("data", (data) => socket.emit("data", data.toString("utf-8"))) + stream.on("close", (code, signal) => { + handleError(socket, "STREAM CLOSE", { + message: + code || signal ? `CODE: ${code} SIGNAL: ${signal}` : undefined }) }) - stream.stderr.on('data', data => debug('STDERR: ' + data)) + stream.stderr.on("data", (data) => debug("STDERR: " + data)) } ) } @@ -186,7 +208,7 @@ function handleAuthentication(socket, creds, config) { * @param {string} data - The banner data */ function handleBanner(socket, data) { - socket.emit('data', data.replace(/\r?\n/g, '\r\n')) + socket.emit("data", data.replace(/\r?\n/g, "\r\n")) } /** @@ -221,7 +243,7 @@ function handleAuthentication(socket, creds, config) { conn.end() conn = null } - socket.emit('connection_closed') + socket.emit("connection_closed") } /** @@ -245,12 +267,12 @@ function handleAuthentication(socket, creds, config) { try { stream.write(data) } catch (error) { - debug('Error writing to stream:', error.message) + debug("Error writing to stream:", error.message) handleConnectionClose(socket) } } else if (isConnectionClosed) { - debug('Attempted to write to closed connection') - socket.emit('connection_closed') + debug("Attempted to write to closed connection") + socket.emit("connection_closed") } } @@ -276,15 +298,15 @@ function handleAuthentication(socket, creds, config) { * @param {Object} config - The configuration object */ function handleControl(socket, stream, credentials, controlData, config) { - debug(`Received control data: ${controlData}`); - - if (controlData === 'replayCredentials' && stream && credentials) { - replayCredentials(socket, stream, credentials, config); - } else if (controlData === 'reauth' && config.options.allowReauth) { - handleReauth(socket); + debug(`Received control data: ${controlData}`) + + if (controlData === "replayCredentials" && stream && credentials) { + replayCredentials(socket, stream, credentials, config) + } else if (controlData === "reauth" && config.options.allowReauth) { + handleReauth(socket) } } - + /** * Replays the user credentials to the SSH stream * @param {import('socket.io').Socket} socket - The Socket.IO socket @@ -293,13 +315,13 @@ function handleAuthentication(socket, creds, config) { * @param {Object} config - The configuration object */ function replayCredentials(socket, stream, credentials, config) { - let allowReplay = config.options.allowReplay || false; - + let allowReplay = config.options.allowReplay || false + if (allowReplay) { - debug(`Replaying credentials for ${socket.id}`); - stream.write(credentials.password + '\n'); + debug(`Replaying credentials for ${socket.id}`) + stream.write(credentials.password + "\n") } else { - debug(`Credential replay not allowed for ${socket.id}`); + debug(`Credential replay not allowed for ${socket.id}`) } } @@ -308,9 +330,9 @@ function handleAuthentication(socket, creds, config) { * @param {import('socket.io').Socket} socket - The Socket.IO socket */ function handleReauth(socket) { - debug(`Reauthentication requested for ${socket.id}`); - socket.emit('authentication', { action: 'reauth' }); - handleConnectionClose(socket); + debug(`Reauthentication requested for ${socket.id}`) + socket.emit("authentication", { action: "reauth" }) + handleConnectionClose(socket) } /** @@ -320,9 +342,9 @@ function handleAuthentication(socket, creds, config) { * @param {Error} [err] - The error object */ function handleError(socket, context, err) { - const errorMessage = err ? `: ${err.message}` : '' + const errorMessage = err ? `: ${err.message}` : "" debug(`WebSSH2 error: ${context}${errorMessage}`) - socket.emit('ssherror', `SSH ${context}${errorMessage}`) + socket.emit("ssherror", `SSH ${context}${errorMessage}`) handleConnectionClose(socket) } @@ -333,11 +355,13 @@ function handleAuthentication(socket, creds, config) { */ function isValidCredentials(credentials) { // Basic format validation - return credentials && - typeof credentials.username === 'string' && - typeof credentials.password === 'string' && - typeof credentials.host === 'string' && - typeof credentials.port === 'number' + return ( + credentials && + typeof credentials.username === "string" && + typeof credentials.password === "string" && + typeof credentials.host === "string" && + typeof credentials.port === "number" + ) } /** @@ -357,7 +381,7 @@ function handleAuthentication(socket, creds, config) { readyTimeout: credentials.readyTimeout, keepaliveInterval: credentials.keepaliveInterval, keepaliveCountMax: credentials.keepaliveCountMax, - debug: createDebug('webssh2:ssh') + debug: createDebug("webssh2:ssh") } } -} \ No newline at end of file +} diff --git a/index.js b/index.js index cc0ac55..83af716 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -'use strict' +"use strict" // server // index.js /** @@ -8,7 +8,7 @@ * Bill Church - https://github.com/billchurch/WebSSH2 - May 2017 */ -const { startServer, config } = require('./app/app') +const { startServer, config } = require("./app/app") /** * Main function to start the application @@ -23,4 +23,4 @@ main() // For testing purposes, export the functions module.exports = { startServer -} \ No newline at end of file +}