From da21c89f20ebecd84cde3f1cf6db70f9338ab42a Mon Sep 17 00:00:00 2001 From: Bill Church Date: Wed, 21 Aug 2024 11:04:28 +0000 Subject: [PATCH] chore: linting --- app/app.js | 39 +++------ app/config.js | 78 ++++++----------- app/connectionHandler.js | 70 ++++++++------- app/routes.js | 20 +++-- app/socket.js | 178 ++++++++++++++++----------------------- app/ssh.js | 28 +++--- app/utils.js | 34 ++++++++ 7 files changed, 207 insertions(+), 240 deletions(-) diff --git a/app/app.js b/app/app.js index 414f922..aef5d1f 100644 --- a/app/app.js +++ b/app/app.js @@ -1,9 +1,7 @@ // server // app/app.js -"use strict" -const createDebug = require("debug") -const debug = createDebug("webssh2") +// const createDebug = require("debug") const http = require("http") const express = require("express") const socketIo = require("socket.io") @@ -14,6 +12,9 @@ const sharedsession = require("express-socket.io-session") const config = require("./config") const socketHandler = require("./socket") const sshRoutes = require("./routes") +const { getCorsConfig } = require("./config") + +// const debug = createDebug("webssh2") /** * Creates and configures the Express application @@ -54,8 +55,8 @@ function createApp() { } res.cookie("basicauth", JSON.stringify(cookieData), { httpOnly: false, - path: '/ssh/host/', - sameSite: 'Strict' + path: "/ssh/host/", + sameSite: "Strict" }) // ensure httOnly is false for the client to read the cookie } next() @@ -104,18 +105,6 @@ function createServer(app) { return http.createServer(app) } -/** - * Gets the CORS configuration - * @returns {Object} The CORS configuration object - */ -function getCorsConfig() { - return { - origin: config.origin || ["*.*"], - methods: ["GET", "POST"], - credentials: true - } -} - /** * Handles server errors * @param {Error} err - The error object @@ -124,6 +113,14 @@ function handleServerError(err) { console.error("WebSSH2 server.listen ERROR:", err.code) } +/** + * Sets up Socket.IO event listeners + * @param {import('socket.io').Server} io - The Socket.IO server instance + */ +function setupSocketIOListeners(io) { + socketHandler(io, config) +} + /** * Initializes and starts the server * @returns {Object} An object containing the server, io, and app instances @@ -148,13 +145,5 @@ function startServer() { return { server, io, app } } -/** - * Sets up Socket.IO event listeners - * @param {import('socket.io').Server} io - The Socket.IO server instance - */ -function setupSocketIOListeners(io) { - socketHandler(io, config) -} - // Don't start the server immediately, export the function instead module.exports = { startServer, config } diff --git a/app/config.js b/app/config.js index 9cfa572..1137e2d 100644 --- a/app/config.js +++ b/app/config.js @@ -1,12 +1,11 @@ // server // app/config.js -"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 { deepMerge, generateSecureSecret } = require("./utils") /** * @typedef {Object} Config @@ -204,15 +203,7 @@ const configSchema = { required: ["secret", "name"] } }, - required: [ - "listen", - "http", - "user", - "ssh", - "header", - "options", - "algorithms" - ] + required: ["listen", "http", "user", "ssh", "header", "options", "algorithms"] } /** @@ -230,7 +221,7 @@ function getConfigPath() { * @returns {Object} The configuration object */ function readConfigFile(configPath) { - console.log("WebSSH2 service reading config from: " + configPath) + console.log(`WebSSH2 service reading config from: ${configPath}`) return readConfig.sync(configPath) } @@ -247,7 +238,7 @@ function validateConfig(config) { console.log("WebSSH2 service validating config") if (!valid) { throw new Error( - "Config validation error: " + ajv.errorsText(validate.errors) + `Config validation error: ${ajv.errorsText(validate.errors)}` ) } return config @@ -261,7 +252,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}`) } } @@ -289,54 +280,26 @@ function loadConfig() { } 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: ' + - JSON.stringify(defaultConfig) + - '\n\n See config.json.sample for details\n\n' - ) - return defaultConfig } + logError( + `\n\nERROR: Missing config.json for webssh. Using default config: ${JSON.stringify( + defaultConfig + )}\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: ' + - JSON.stringify(defaultConfig) + - '\n\n See config.json.sample for details\n\n', + `\n\nERROR: Problem loading config.json for webssh. Using default config: ${JSON.stringify( + defaultConfig + )}\n\n See config.json.sample for details\n\n`, err ) return defaultConfig } } -/** - * Generates a secure random session secret - * @returns {string} A random 32-byte hex string - */ -function generateSecureSecret() { - return crypto.randomBytes(32).toString("hex") -} - -/** - * Deep merges two objects - * @param {Object} target - The target object to merge into - * @param {Object} source - The source object to merge from - * @returns {Object} The merged object - */ -function deepMerge(target, source) { - for (const key in source) { - if (source.hasOwnProperty(key)) { - if (source[key] instanceof Object && !Array.isArray(source[key])) { - target[key] = deepMerge(target[key] || {}, source[key]) - } else { - target[key] = source[key] - } - } - } - return target -} - /** * The loaded configuration * @type {Object} @@ -344,3 +307,14 @@ function deepMerge(target, source) { const config = loadConfig() module.exports = config +/** + * Gets the CORS configuration + * @returns {Object} The CORS configuration object + */ +module.exports.getCorsConfig = function getCorsConfig() { + return { + origin: config.origin || ["*.*"], + methods: ["GET", "POST"], + credentials: true + } +} diff --git a/app/connectionHandler.js b/app/connectionHandler.js index 4ed791c..98ad252 100644 --- a/app/connectionHandler.js +++ b/app/connectionHandler.js @@ -1,59 +1,55 @@ // server // app/connectionHandler.js -const createDebug = require('debug') -const path = require('path') -const fs = require('fs') -const extend = require('util')._extend -const debug = createDebug('webssh2:connectionHandler') +const createDebug = require("debug") +const path = require("path") +const fs = require("fs") -function handleConnection(req, res, urlParams) { - debug('Handling connection') - urlParams = urlParams || {} +const debug = createDebug("webssh2:connectionHandler") + +function handleConnection(req, res) { + debug("Handling connection") const clientPath = path.resolve( __dirname, - '..', - 'node_modules', - 'webssh2_client', - 'client', - 'public' + "..", + "node_modules", + "webssh2_client", + "client", + "public" ) const tempConfig = { socket: { - url: req.protocol + '://' + req.get('host'), - path: '/ssh/socket.io' + url: `${req.protocol}://${req.get("host")}`, + path: "/ssh/socket.io" }, autoConnect: false // Default to false } // Check if the current route is /host/:host - debug('handleConnection req.path:', req.path) - if (req.path.startsWith('/host/')) { + debug("handleConnection req.path:", req.path) + if (req.path.startsWith("/host/")) { tempConfig.autoConnect = true } - fs.readFile( - path.join(clientPath, 'client.htm'), - 'utf8', - function (err, data) { - if (err) { - return res.status(500).send('Error loading client file') - } - - var modifiedHtml = data.replace( - /(src|href)="(?!http|\/\/)/g, - '$1="/ssh/assets/' - ) - - modifiedHtml = modifiedHtml.replace( - 'window.webssh2Config = null;', - 'window.webssh2Config = ' + JSON.stringify(tempConfig) + ';' - ) - - res.send(modifiedHtml) + fs.readFile(path.join(clientPath, "client.htm"), "utf8", function(err, data) { + if (err) { + return res.status(500).send("Error loading client file") } - ) + + let modifiedHtml = data.replace( + /(src|href)="(?!http|\/\/)/g, + '$1="/ssh/assets/' + ) + + modifiedHtml = modifiedHtml.replace( + "window.webssh2Config = null;", + `window.webssh2Config = ${JSON.stringify(tempConfig)};` + ) + + res.send(modifiedHtml) + // Explicitly return to satisfy the linter + }) } module.exports = handleConnection diff --git a/app/routes.js b/app/routes.js index 26dd853..8d74115 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,18 +1,20 @@ // 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 { validateSshTerm } = require("./utils") -const maskObject = require('jsmasker'); +const maskObject = require("jsmasker") const validator = require("validator") +const { validateSshTerm } = require("./utils") +const handleConnection = require("./connectionHandler") function auth(req, res, next) { debug("auth: Basic Auth") - var credentials = basicAuth(req) + const credentials = basicAuth(req) if (!credentials) { res.setHeader("WWW-Authenticate", 'Basic realm="WebSSH2"') return res.status(401).send("Authentication required.") @@ -27,13 +29,13 @@ function auth(req, res, next) { } // Scenario 1: No auth required, uses websocket authentication instead -router.get("/", function (req, res) { +router.get("/", function(req, res) { debug("Accessed / route") handleConnection(req, res) }) // Scenario 2: Auth required, uses HTTP Basic Auth -router.get("/host/:host", auth, function (req, res) { +router.get("/host/:host", auth, function(req, res) { debug(`Accessed /ssh/host/${req.params.host} route`) // Validate and sanitize host parameter @@ -61,7 +63,7 @@ router.get("/host/:host", auth, function (req, res) { if (req.query.sshTerm) { req.session.sshCredentials.term = sshTerm } - req.session.usedBasicAuth = true + req.session.usedBasicAuth = true // Sanitize and log the sshCredentials object const sanitizedCredentials = maskObject( @@ -73,12 +75,12 @@ router.get("/host/:host", auth, function (req, res) { }) // Clear credentials route -router.get("/clear-credentials", function (req, res) { +router.get("/clear-credentials", function(req, res) { req.session.sshCredentials = null res.status(200).send("Credentials cleared.") }) -router.get("/force-reconnect", function (req, res) { +router.get("/force-reconnect", function(req, res) { req.session.sshCredentials = null res.status(401).send("Authentication required.") }) diff --git a/app/socket.js b/app/socket.js index b3ba4b2..e5a5808 100644 --- a/app/socket.js +++ b/app/socket.js @@ -1,19 +1,19 @@ // server // app/socket.js -"use strict" const createDebug = require("debug") + const debug = createDebug("webssh2:socket") +const maskObject = require("jsmasker") +const validator = require("validator") const SSHConnection = require("./ssh") const { validateSshTerm } = require("./utils") -const maskObject = require('jsmasker'); -const validator = require("validator") -module.exports = function (io, config) { - io.on("connection", function (socket) { - debug("io.on connection: " + socket.id) - var ssh = new SSHConnection(config) - var sessionState = { +module.exports = function(io, config) { + io.on("connection", function(socket) { + debug(`io.on connection: ${socket.id}`) + const ssh = new SSHConnection(config) + const sessionState = { authenticated: false, username: null, password: null, @@ -31,7 +31,7 @@ module.exports = function (io, config) { * @param {Object} config - The configuration object. */ function handleAuthenticate(creds) { - debug("handleAuthenticate: " + socket.id + ", %O", maskObject(creds)) + debug(`handleAuthenticate: ${socket.id}, %O`, maskObject(creds)) if (isValidCredentials(creds)) { sessionState.term = validateSshTerm(creds.term) @@ -39,9 +39,7 @@ module.exports = function (io, config) { : config.ssh.term initializeConnection(creds) } else { - console.warn( - "handleAuthenticate: " + socket.id + ", CREDENTIALS INVALID" - ) + console.warn(`handleAuthenticate: ${socket.id}, CREDENTIALS INVALID`) socket.emit("authentication", { success: false, message: "Invalid credentials format" @@ -61,17 +59,13 @@ module.exports = function (io, config) { */ function initializeConnection(creds) { debug( - "initializeConnection: " + - socket.id + - ", INITIALIZING SSH CONNECTION: Host: " + - creds.host + - ", creds: %O", + `initializeConnection: ${socket.id}, INITIALIZING SSH CONNECTION: Host: ${creds.host}, creds: %O`, maskObject(creds) ) ssh .connect(creds) - .then(function () { + .then(function() { sessionState.authenticated = true sessionState.username = creds.username sessionState.password = creds.password @@ -79,65 +73,51 @@ module.exports = function (io, config) { sessionState.port = creds.port debug( - "initializeConnection: " + - socket.id + - " conn.on ready: Host: " + - creds.host + `initializeConnection: ${socket.id} conn.on ready: Host: ${creds.host}` ) console.log( - "initializeConnection: " + - socket.id + - " conn.on ready: " + - creds.username + - "@" + - creds.host + - ":" + - creds.port + - " successfully connected" + `initializeConnection: ${socket.id} conn.on ready: ${creds.username}@${creds.host}:${creds.port} successfully connected` ) - var auth_result = { action: "auth_result", success: true } + const auth_result = { action: "auth_result", success: true } debug( - "initializeConnection: " + - socket.id + - " conn.on ready: emitting authentication: " + - JSON.stringify(auth_result) + `initializeConnection: ${ + socket.id + } conn.on ready: emitting authentication: ${JSON.stringify( + auth_result + )}` ) socket.emit("authentication", auth_result) // Emit consolidated permissions - var permissions = { + const permissions = { autoLog: config.options.autoLog || false, allowReplay: config.options.allowReplay || false, allowReconnect: config.options.allowReconnect || false, allowReauth: config.options.allowReauth || false } debug( - "initializeConnection: " + - socket.id + - " conn.on ready: emitting permissions: " + - JSON.stringify(permissions) + `initializeConnection: ${ + socket.id + } conn.on ready: emitting permissions: ${JSON.stringify( + permissions + )}` ) socket.emit("permissions", permissions) - updateElement("footer", "ssh://" + creds.host + ":" + creds.port) + updateElement("footer", `ssh://${creds.host}:${creds.port}`) if (config.header && config.header.text !== null) { - debug("initializeConnection header: " + config.header) + debug(`initializeConnection header: ${config.header}`) updateElement("header", config.header.text) } // Request terminal information from client socket.emit("getTerminal", true) }) - .catch(function (err) { + .catch(function(err) { console.error( - "initializeConnection: SSH CONNECTION ERROR: " + - socket.id + - ", Host: " + - creds.host + - ", Error: " + - err.message + `initializeConnection: SSH CONNECTION ERROR: ${socket.id}, Host: ${creds.host}, Error: ${err.message}` ) if (err.level === "client-authentication") { socket.emit("authentication", { @@ -161,24 +141,24 @@ module.exports = function (io, config) { * @returns {void} */ function handleTerminal(data) { - debug("handleTerminal: Received terminal data: " + JSON.stringify(data)) - var term = data.term - var rows = data.rows - var cols = data.cols + debug(`handleTerminal: Received terminal data: ${JSON.stringify(data)}`) + const { term } = data + const { rows } = data + const { cols } = data if (term && validateSshTerm(term)) { sessionState.term = term - debug("handleTerminal: Set term to " + sessionState.term) + debug(`handleTerminal: Set term to ${sessionState.term}`) } if (rows && validator.isInt(rows.toString())) { sessionState.rows = parseInt(rows, 10) - debug("handleTerminal: Set rows to " + sessionState.rows) + debug(`handleTerminal: Set rows to ${sessionState.rows}`) } if (cols && validator.isInt(cols.toString())) { sessionState.cols = parseInt(cols, 10) - debug("handleTerminal: Set cols to " + sessionState.cols) + debug(`handleTerminal: Set cols to ${sessionState.cols}`) } // Now that we have terminal information, we can create the shell @@ -199,35 +179,35 @@ module.exports = function (io, config) { cols: sessionState.cols, rows: sessionState.rows }) - .then(function (stream) { - stream.on("data", function (data) { + .then(function(stream) { + stream.on("data", function(data) { socket.emit("data", data.toString("utf-8")) }) - stream.stderr.on("data", function (data) { - debug("STDERR: " + data) + stream.stderr.on("data", function(data) { + debug(`STDERR: ${data}`) }) - stream.on("close", function (code, signal) { - debug("handleStreamClose: " + socket.id) + stream.on("close", function(code, signal) { + debug(`handleStreamClose: ${socket.id}`) handleConnectionClose() }) - socket.on("data", function (data) { + socket.on("data", function(data) { if (stream) { stream.write(data) } }) - socket.on("control", function (controlData) { + socket.on("control", function(controlData) { handleControl(controlData) }) - socket.on("resize", function (data) { + socket.on("resize", function(data) { handleResize(data) }) }) - .catch(function (err) { + .catch(function(err) { handleError("SHELL ERROR", err) }) } @@ -238,8 +218,8 @@ module.exports = function (io, config) { * @param {Object} data - The resize data containing the number of rows and columns. */ function handleResize(data) { - var rows = data.rows - var cols = data.cols + const { rows } = data + const { cols } = data if (ssh.stream) { if (rows && validator.isInt(rows.toString())) { @@ -248,9 +228,7 @@ module.exports = function (io, config) { if (cols && validator.isInt(cols.toString())) { sessionState.cols = parseInt(cols, 10) } - debug( - "Resizing terminal to " + sessionState.rows + "x" + sessionState.cols - ) + debug(`Resizing terminal to ${sessionState.rows}x${sessionState.cols}`) ssh.resizeTerminal(sessionState.rows, sessionState.cols) } } @@ -262,7 +240,7 @@ module.exports = function (io, config) { * @returns {void} */ function handleControl(controlData) { - debug("handleControl: Received control data: " + controlData) + debug(`handleControl: Received control data: ${controlData}`) if ( validator.isIn(controlData, ["replayCredentials", "reauth"]) && ssh.stream @@ -274,7 +252,7 @@ module.exports = function (io, config) { } } else { console.warn( - "handleControl: Invalid control command received: " + controlData + `handleControl: Invalid control command received: ${controlData}` ) } } @@ -285,15 +263,15 @@ module.exports = function (io, config) { * @returns {void} */ function replayCredentials() { - var password = sessionState.password - var allowReplay = config.options.allowReplay || false + const { password } = sessionState + const allowReplay = config.options.allowReplay || false if (allowReplay && ssh.stream) { debug(`replayCredentials: ${socket.id} Replaying credentials for `) - ssh.stream.write(password + "\n") + ssh.stream.write(`${password}\n`) } else { console.warn( - "replayCredentials: Credential replay not allowed for " + socket.id + `replayCredentials: Credential replay not allowed for ${socket.id}` ) } } @@ -302,7 +280,7 @@ module.exports = function (io, config) { * Handles reauthentication for the socket. */ function handleReauth() { - debug("handleReauth: Reauthentication requested for " + socket.id) + debug(`handleReauth: Reauthentication requested for ${socket.id}`) if (config.options.allowReauth) { clearSessionCredentials() debug(`handleReauth: Reauthenticating ${socket.id}`) @@ -322,9 +300,9 @@ module.exports = function (io, config) { * @param {Error} err - The error object. */ function handleError(context, err) { - var errorMessage = err ? ": " + err.message : "" - debug("WebSSH2 error: " + context + errorMessage) - socket.emit("ssherror", "SSH " + context + errorMessage) + const errorMessage = err ? `: ${err.message}` : "" + debug(`WebSSH2 error: ${context}${errorMessage}`) + socket.emit("ssherror", `SSH ${context}${errorMessage}`) handleConnectionClose() } @@ -336,14 +314,7 @@ module.exports = function (io, config) { * @returns {void} */ function updateElement(element, value) { - debug( - "updateElement: " + - socket.id + - ", Element: " + - element + - ", Value: " + - value - ) + debug(`updateElement: ${socket.id}, Element: ${element}, Value: ${value}`) socket.emit("updateUI", { element: element, value: value }) } @@ -353,8 +324,8 @@ module.exports = function (io, config) { * @param {string} reason - The reason for the closure. */ function handleConnectionClose(reason) { - debug("handleDisconnect: " + socket.id + ", Reason: " + reason) - debug("handleConnectionClose: " + socket.id) + debug(`handleDisconnect: ${socket.id}, Reason: ${reason}`) + debug(`handleConnectionClose: ${socket.id}`) if (ssh) { ssh.end() } @@ -366,7 +337,7 @@ module.exports = function (io, config) { */ function clearSessionCredentials() { debug( - "clearSessionCredentials: Clearing session credentials for " + socket.id + `clearSessionCredentials: Clearing session credentials for ${socket.id}` ) if (socket.handshake.session.sshCredentials) { socket.handshake.session.sshCredentials.username = null @@ -376,28 +347,27 @@ module.exports = function (io, config) { sessionState.authenticated = false sessionState.username = null sessionState.password = null - socket.handshake.session.save(function (err) { + socket.handshake.session.save(function(err) { if (err) { - console.error("Failed to save session for " + socket.id + ":", err) + console.error(`Failed to save session for ${socket.id}:`, err) } }) } // Check for HTTP Basic Auth credentials - if (socket.handshake.session.usedBasicAuth && socket.handshake.session.sshCredentials) { - // if (socket.handshake.session.sshCredentials) { - var creds = socket.handshake.session.sshCredentials + if ( + socket.handshake.session.usedBasicAuth && + socket.handshake.session.sshCredentials + ) { + // if (socket.handshake.session.sshCredentials) { + const creds = socket.handshake.session.sshCredentials debug( - "handleConnection: " + - socket.id + - ", Host: " + - creds.host + - ": HTTP Basic Credentials Exist, creds: %O", + `handleConnection: ${socket.id}, Host: ${creds.host}: HTTP Basic Credentials Exist, creds: %O`, maskObject(creds) ) handleAuthenticate(creds) } else if (!sessionState.authenticated) { - debug("handleConnection: " + socket.id + ", emitting request_auth") + debug(`handleConnection: ${socket.id}, emitting request_auth`) socket.emit("authentication", { action: "request_auth" }) } diff --git a/app/ssh.js b/app/ssh.js index eba4111..5d6531e 100644 --- a/app/ssh.js +++ b/app/ssh.js @@ -1,11 +1,11 @@ // server // app/ssh.js -"use strict" const createDebug = require("debug") -const debug = createDebug("webssh2:ssh") const SSH = require("ssh2").Client -const maskObject = require('jsmasker'); +const maskObject = require("jsmasker") + +const debug = createDebug("webssh2:ssh") function SSHConnection(config) { this.config = config @@ -14,7 +14,7 @@ function SSHConnection(config) { } SSHConnection.prototype.connect = function(creds) { - var self = this + const self = this return new Promise(function(resolve, reject) { debug("connect: %O", maskObject(creds)) @@ -23,16 +23,16 @@ SSHConnection.prototype.connect = function(creds) { } self.conn = new SSH() - - var sshConfig = self.getSSHConfig(creds) - + + const sshConfig = self.getSSHConfig(creds) + self.conn.on("ready", function() { - debug("connect: ready: " + creds.host) + debug(`connect: ready: ${creds.host}`) resolve() }) self.conn.on("error", function(err) { - console.error("connect: error:" + err.message) + console.error(`connect: error:${err.message}`) reject(err) }) @@ -49,14 +49,16 @@ SSHConnection.prototype.getSSHConfig = function(creds) { tryKeyboard: true, algorithms: creds.algorithms || this.config.ssh.algorithms, readyTimeout: creds.readyTimeout || this.config.ssh.readyTimeout, - keepaliveInterval: creds.keepaliveInterval || this.config.ssh.keepaliveInterval, - keepaliveCountMax: creds.keepaliveCountMax || this.config.ssh.keepaliveCountMax, + keepaliveInterval: + creds.keepaliveInterval || this.config.ssh.keepaliveInterval, + keepaliveCountMax: + creds.keepaliveCountMax || this.config.ssh.keepaliveCountMax, debug: createDebug("ssh") } } SSHConnection.prototype.shell = function(options) { - var self = this + const self = this return new Promise(function(resolve, reject) { self.conn.shell(options, function(err, stream) { if (err) { @@ -86,4 +88,4 @@ SSHConnection.prototype.end = function() { } } -module.exports = SSHConnection \ No newline at end of file +module.exports = SSHConnection diff --git a/app/utils.js b/app/utils.js index 3c23cbd..0423d21 100644 --- a/app/utils.js +++ b/app/utils.js @@ -2,6 +2,8 @@ // /app/utils.js const validator = require("validator") const createDebug = require("debug") +const crypto = require("crypto") + const debug = createDebug("webssh2:utils") /** @@ -23,3 +25,35 @@ function validateSshTerm(term) { } exports.validateSshTerm = validateSshTerm +/** + * Deep merges two objects + * @param {Object} target - The target object to merge into + * @param {Object} source - The source object to merge from + * @returns {Object} The merged object + */ +function deepMerge(target, source) { + const output = Object.assign({}, target) // Avoid mutating target directly + Object.keys(source).forEach(key => { + if (Object.hasOwnProperty.call(source, key)) { + if ( + source[key] instanceof Object && + !Array.isArray(source[key]) && + source[key] !== null + ) { + output[key] = deepMerge(output[key] || {}, source[key]) + } else { + output[key] = source[key] + } + } + }) + return output +} +exports.deepMerge = deepMerge +/** + * Generates a secure random session secret + * @returns {string} A random 32-byte hex string + */ +function generateSecureSecret() { + return crypto.randomBytes(32).toString("hex") +} +exports.generateSecureSecret = generateSecureSecret