chore: linting

This commit is contained in:
Bill Church 2024-08-21 11:04:28 +00:00
parent dbcfc30cd0
commit da21c89f20
No known key found for this signature in database
7 changed files with 207 additions and 240 deletions

View file

@ -1,9 +1,7 @@
// server // server
// app/app.js // app/app.js
"use strict"
const createDebug = require("debug") // const createDebug = require("debug")
const debug = createDebug("webssh2")
const http = require("http") const http = require("http")
const express = require("express") const express = require("express")
const socketIo = require("socket.io") const socketIo = require("socket.io")
@ -14,6 +12,9 @@ const sharedsession = require("express-socket.io-session")
const config = require("./config") const config = require("./config")
const socketHandler = require("./socket") const socketHandler = require("./socket")
const sshRoutes = require("./routes") const sshRoutes = require("./routes")
const { getCorsConfig } = require("./config")
// const debug = createDebug("webssh2")
/** /**
* Creates and configures the Express application * Creates and configures the Express application
@ -54,8 +55,8 @@ function createApp() {
} }
res.cookie("basicauth", JSON.stringify(cookieData), { res.cookie("basicauth", JSON.stringify(cookieData), {
httpOnly: false, httpOnly: false,
path: '/ssh/host/', path: "/ssh/host/",
sameSite: 'Strict' sameSite: "Strict"
}) // ensure httOnly is false for the client to read the cookie }) // ensure httOnly is false for the client to read the cookie
} }
next() next()
@ -104,18 +105,6 @@ function createServer(app) {
return http.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 * Handles server errors
* @param {Error} err - The error object * @param {Error} err - The error object
@ -124,6 +113,14 @@ function handleServerError(err) {
console.error("WebSSH2 server.listen ERROR:", err.code) 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 * Initializes and starts the server
* @returns {Object} An object containing the server, io, and app instances * @returns {Object} An object containing the server, io, and app instances
@ -148,13 +145,5 @@ function startServer() {
return { server, io, app } 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 // Don't start the server immediately, export the function instead
module.exports = { startServer, config } module.exports = { startServer, config }

View file

@ -1,12 +1,11 @@
// server // server
// app/config.js // app/config.js
"use strict"
const path = require("path") const path = require("path")
const fs = require("fs") const fs = require("fs")
const readConfig = require("read-config-ng") const readConfig = require("read-config-ng")
const Ajv = require("ajv") const Ajv = require("ajv")
const crypto = require("crypto") const { deepMerge, generateSecureSecret } = require("./utils")
/** /**
* @typedef {Object} Config * @typedef {Object} Config
@ -204,15 +203,7 @@ const configSchema = {
required: ["secret", "name"] required: ["secret", "name"]
} }
}, },
required: [ required: ["listen", "http", "user", "ssh", "header", "options", "algorithms"]
"listen",
"http",
"user",
"ssh",
"header",
"options",
"algorithms"
]
} }
/** /**
@ -230,7 +221,7 @@ function getConfigPath() {
* @returns {Object} The configuration object * @returns {Object} The configuration object
*/ */
function readConfigFile(configPath) { function readConfigFile(configPath) {
console.log("WebSSH2 service reading config from: " + configPath) console.log(`WebSSH2 service reading config from: ${configPath}`)
return readConfig.sync(configPath) return readConfig.sync(configPath)
} }
@ -247,7 +238,7 @@ function validateConfig(config) {
console.log("WebSSH2 service validating config") console.log("WebSSH2 service validating config")
if (!valid) { if (!valid) {
throw new Error( throw new Error(
"Config validation error: " + ajv.errorsText(validate.errors) `Config validation error: ${ajv.errorsText(validate.errors)}`
) )
} }
return config return config
@ -261,7 +252,7 @@ function validateConfig(config) {
function logError(message, error) { function logError(message, error) {
console.error(message) console.error(message)
if (error) { if (error) {
console.error("ERROR:\n\n " + error) console.error(`ERROR:\n\n ${error}`)
} }
} }
@ -289,54 +280,26 @@ function loadConfig() {
} }
const validatedConfig = validateConfig(mergedConfig) const validatedConfig = validateConfig(mergedConfig)
console.log('Merged and validated configuration') console.log("Merged and validated configuration")
return validatedConfig return validatedConfig
} else { }
logError( logError(
'\n\nERROR: Missing config.json for webssh. Using default config: ' + `\n\nERROR: Missing config.json for webssh. Using default config: ${JSON.stringify(
JSON.stringify(defaultConfig) + defaultConfig
'\n\n See config.json.sample for details\n\n' )}\n\n See config.json.sample for details\n\n`
) )
return defaultConfig return defaultConfig
}
} catch (err) { } catch (err) {
logError( 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(
JSON.stringify(defaultConfig) + defaultConfig
'\n\n See config.json.sample for details\n\n', )}\n\n See config.json.sample for details\n\n`,
err err
) )
return defaultConfig 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 * The loaded configuration
* @type {Object} * @type {Object}
@ -344,3 +307,14 @@ function deepMerge(target, source) {
const config = loadConfig() const config = loadConfig()
module.exports = config 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
}
}

View file

@ -1,59 +1,55 @@
// server // server
// app/connectionHandler.js // app/connectionHandler.js
const createDebug = require('debug') const createDebug = require("debug")
const path = require('path') const path = require("path")
const fs = require('fs') const fs = require("fs")
const extend = require('util')._extend
const debug = createDebug('webssh2:connectionHandler')
function handleConnection(req, res, urlParams) { const debug = createDebug("webssh2:connectionHandler")
debug('Handling connection')
urlParams = urlParams || {} function handleConnection(req, res) {
debug("Handling connection")
const clientPath = path.resolve( const clientPath = path.resolve(
__dirname, __dirname,
'..', "..",
'node_modules', "node_modules",
'webssh2_client', "webssh2_client",
'client', "client",
'public' "public"
) )
const tempConfig = { const tempConfig = {
socket: { socket: {
url: req.protocol + '://' + req.get('host'), url: `${req.protocol}://${req.get("host")}`,
path: '/ssh/socket.io' path: "/ssh/socket.io"
}, },
autoConnect: false // Default to false autoConnect: false // Default to false
} }
// Check if the current route is /host/:host // Check if the current route is /host/:host
debug('handleConnection req.path:', req.path) debug("handleConnection req.path:", req.path)
if (req.path.startsWith('/host/')) { if (req.path.startsWith("/host/")) {
tempConfig.autoConnect = true tempConfig.autoConnect = true
} }
fs.readFile( fs.readFile(path.join(clientPath, "client.htm"), "utf8", function(err, data) {
path.join(clientPath, 'client.htm'),
'utf8',
function (err, data) {
if (err) { if (err) {
return res.status(500).send('Error loading client file') return res.status(500).send("Error loading client file")
} }
var modifiedHtml = data.replace( let modifiedHtml = data.replace(
/(src|href)="(?!http|\/\/)/g, /(src|href)="(?!http|\/\/)/g,
'$1="/ssh/assets/' '$1="/ssh/assets/'
) )
modifiedHtml = modifiedHtml.replace( modifiedHtml = modifiedHtml.replace(
'window.webssh2Config = null;', "window.webssh2Config = null;",
'window.webssh2Config = ' + JSON.stringify(tempConfig) + ';' `window.webssh2Config = ${JSON.stringify(tempConfig)};`
) )
res.send(modifiedHtml) res.send(modifiedHtml)
} // Explicitly return to satisfy the linter
) })
} }
module.exports = handleConnection module.exports = handleConnection

View file

@ -1,18 +1,20 @@
// server // server
// /app/routes.js // /app/routes.js
const createDebug = require("debug") const createDebug = require("debug")
const debug = createDebug("webssh2:routes") const debug = createDebug("webssh2:routes")
const express = require("express") const express = require("express")
const router = express.Router() const router = express.Router()
const handleConnection = require("./connectionHandler")
const basicAuth = require("basic-auth") const basicAuth = require("basic-auth")
const { validateSshTerm } = require("./utils") const maskObject = require("jsmasker")
const maskObject = require('jsmasker');
const validator = require("validator") const validator = require("validator")
const { validateSshTerm } = require("./utils")
const handleConnection = require("./connectionHandler")
function auth(req, res, next) { function auth(req, res, next) {
debug("auth: Basic Auth") debug("auth: Basic Auth")
var credentials = basicAuth(req) const credentials = basicAuth(req)
if (!credentials) { if (!credentials) {
res.setHeader("WWW-Authenticate", 'Basic realm="WebSSH2"') res.setHeader("WWW-Authenticate", 'Basic realm="WebSSH2"')
return res.status(401).send("Authentication required.") 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 // Scenario 1: No auth required, uses websocket authentication instead
router.get("/", function (req, res) { router.get("/", function(req, res) {
debug("Accessed / route") debug("Accessed / route")
handleConnection(req, res) handleConnection(req, res)
}) })
// Scenario 2: Auth required, uses HTTP Basic Auth // 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`) debug(`Accessed /ssh/host/${req.params.host} route`)
// Validate and sanitize host parameter // Validate and sanitize host parameter
@ -73,12 +75,12 @@ router.get("/host/:host", auth, function (req, res) {
}) })
// Clear credentials route // Clear credentials route
router.get("/clear-credentials", function (req, res) { router.get("/clear-credentials", function(req, res) {
req.session.sshCredentials = null req.session.sshCredentials = null
res.status(200).send("Credentials cleared.") res.status(200).send("Credentials cleared.")
}) })
router.get("/force-reconnect", function (req, res) { router.get("/force-reconnect", function(req, res) {
req.session.sshCredentials = null req.session.sshCredentials = null
res.status(401).send("Authentication required.") res.status(401).send("Authentication required.")
}) })

View file

@ -1,19 +1,19 @@
// server // server
// app/socket.js // app/socket.js
"use strict"
const createDebug = require("debug") const createDebug = require("debug")
const debug = createDebug("webssh2:socket") const debug = createDebug("webssh2:socket")
const maskObject = require("jsmasker")
const validator = require("validator")
const SSHConnection = require("./ssh") const SSHConnection = require("./ssh")
const { validateSshTerm } = require("./utils") const { validateSshTerm } = require("./utils")
const maskObject = require('jsmasker');
const validator = require("validator")
module.exports = function (io, config) { module.exports = function(io, config) {
io.on("connection", function (socket) { io.on("connection", function(socket) {
debug("io.on connection: " + socket.id) debug(`io.on connection: ${socket.id}`)
var ssh = new SSHConnection(config) const ssh = new SSHConnection(config)
var sessionState = { const sessionState = {
authenticated: false, authenticated: false,
username: null, username: null,
password: null, password: null,
@ -31,7 +31,7 @@ module.exports = function (io, config) {
* @param {Object} config - The configuration object. * @param {Object} config - The configuration object.
*/ */
function handleAuthenticate(creds) { function handleAuthenticate(creds) {
debug("handleAuthenticate: " + socket.id + ", %O", maskObject(creds)) debug(`handleAuthenticate: ${socket.id}, %O`, maskObject(creds))
if (isValidCredentials(creds)) { if (isValidCredentials(creds)) {
sessionState.term = validateSshTerm(creds.term) sessionState.term = validateSshTerm(creds.term)
@ -39,9 +39,7 @@ module.exports = function (io, config) {
: config.ssh.term : config.ssh.term
initializeConnection(creds) initializeConnection(creds)
} else { } else {
console.warn( console.warn(`handleAuthenticate: ${socket.id}, CREDENTIALS INVALID`)
"handleAuthenticate: " + socket.id + ", CREDENTIALS INVALID"
)
socket.emit("authentication", { socket.emit("authentication", {
success: false, success: false,
message: "Invalid credentials format" message: "Invalid credentials format"
@ -61,17 +59,13 @@ module.exports = function (io, config) {
*/ */
function initializeConnection(creds) { function initializeConnection(creds) {
debug( debug(
"initializeConnection: " + `initializeConnection: ${socket.id}, INITIALIZING SSH CONNECTION: Host: ${creds.host}, creds: %O`,
socket.id +
", INITIALIZING SSH CONNECTION: Host: " +
creds.host +
", creds: %O",
maskObject(creds) maskObject(creds)
) )
ssh ssh
.connect(creds) .connect(creds)
.then(function () { .then(function() {
sessionState.authenticated = true sessionState.authenticated = true
sessionState.username = creds.username sessionState.username = creds.username
sessionState.password = creds.password sessionState.password = creds.password
@ -79,65 +73,51 @@ module.exports = function (io, config) {
sessionState.port = creds.port sessionState.port = creds.port
debug( debug(
"initializeConnection: " + `initializeConnection: ${socket.id} conn.on ready: Host: ${creds.host}`
socket.id +
" conn.on ready: Host: " +
creds.host
) )
console.log( console.log(
"initializeConnection: " + `initializeConnection: ${socket.id} conn.on ready: ${creds.username}@${creds.host}:${creds.port} successfully connected`
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( debug(
"initializeConnection: " + `initializeConnection: ${
socket.id + socket.id
" conn.on ready: emitting authentication: " + } conn.on ready: emitting authentication: ${JSON.stringify(
JSON.stringify(auth_result) auth_result
)}`
) )
socket.emit("authentication", auth_result) socket.emit("authentication", auth_result)
// Emit consolidated permissions // Emit consolidated permissions
var permissions = { const permissions = {
autoLog: config.options.autoLog || false, autoLog: config.options.autoLog || false,
allowReplay: config.options.allowReplay || false, allowReplay: config.options.allowReplay || false,
allowReconnect: config.options.allowReconnect || false, allowReconnect: config.options.allowReconnect || false,
allowReauth: config.options.allowReauth || false allowReauth: config.options.allowReauth || false
} }
debug( debug(
"initializeConnection: " + `initializeConnection: ${
socket.id + socket.id
" conn.on ready: emitting permissions: " + } conn.on ready: emitting permissions: ${JSON.stringify(
JSON.stringify(permissions) permissions
)}`
) )
socket.emit("permissions", 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) { if (config.header && config.header.text !== null) {
debug("initializeConnection header: " + config.header) debug(`initializeConnection header: ${config.header}`)
updateElement("header", config.header.text) updateElement("header", config.header.text)
} }
// Request terminal information from client // Request terminal information from client
socket.emit("getTerminal", true) socket.emit("getTerminal", true)
}) })
.catch(function (err) { .catch(function(err) {
console.error( console.error(
"initializeConnection: SSH CONNECTION ERROR: " + `initializeConnection: SSH CONNECTION ERROR: ${socket.id}, Host: ${creds.host}, Error: ${err.message}`
socket.id +
", Host: " +
creds.host +
", Error: " +
err.message
) )
if (err.level === "client-authentication") { if (err.level === "client-authentication") {
socket.emit("authentication", { socket.emit("authentication", {
@ -161,24 +141,24 @@ module.exports = function (io, config) {
* @returns {void} * @returns {void}
*/ */
function handleTerminal(data) { function handleTerminal(data) {
debug("handleTerminal: Received terminal data: " + JSON.stringify(data)) debug(`handleTerminal: Received terminal data: ${JSON.stringify(data)}`)
var term = data.term const { term } = data
var rows = data.rows const { rows } = data
var cols = data.cols const { cols } = data
if (term && validateSshTerm(term)) { if (term && validateSshTerm(term)) {
sessionState.term = term sessionState.term = term
debug("handleTerminal: Set term to " + sessionState.term) debug(`handleTerminal: Set term to ${sessionState.term}`)
} }
if (rows && validator.isInt(rows.toString())) { if (rows && validator.isInt(rows.toString())) {
sessionState.rows = parseInt(rows, 10) 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())) { if (cols && validator.isInt(cols.toString())) {
sessionState.cols = parseInt(cols, 10) 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 // Now that we have terminal information, we can create the shell
@ -199,35 +179,35 @@ module.exports = function (io, config) {
cols: sessionState.cols, cols: sessionState.cols,
rows: sessionState.rows rows: sessionState.rows
}) })
.then(function (stream) { .then(function(stream) {
stream.on("data", function (data) { stream.on("data", function(data) {
socket.emit("data", data.toString("utf-8")) socket.emit("data", data.toString("utf-8"))
}) })
stream.stderr.on("data", function (data) { stream.stderr.on("data", function(data) {
debug("STDERR: " + data) debug(`STDERR: ${data}`)
}) })
stream.on("close", function (code, signal) { stream.on("close", function(code, signal) {
debug("handleStreamClose: " + socket.id) debug(`handleStreamClose: ${socket.id}`)
handleConnectionClose() handleConnectionClose()
}) })
socket.on("data", function (data) { socket.on("data", function(data) {
if (stream) { if (stream) {
stream.write(data) stream.write(data)
} }
}) })
socket.on("control", function (controlData) { socket.on("control", function(controlData) {
handleControl(controlData) handleControl(controlData)
}) })
socket.on("resize", function (data) { socket.on("resize", function(data) {
handleResize(data) handleResize(data)
}) })
}) })
.catch(function (err) { .catch(function(err) {
handleError("SHELL ERROR", 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. * @param {Object} data - The resize data containing the number of rows and columns.
*/ */
function handleResize(data) { function handleResize(data) {
var rows = data.rows const { rows } = data
var cols = data.cols const { cols } = data
if (ssh.stream) { if (ssh.stream) {
if (rows && validator.isInt(rows.toString())) { if (rows && validator.isInt(rows.toString())) {
@ -248,9 +228,7 @@ module.exports = function (io, config) {
if (cols && validator.isInt(cols.toString())) { if (cols && validator.isInt(cols.toString())) {
sessionState.cols = parseInt(cols, 10) sessionState.cols = parseInt(cols, 10)
} }
debug( debug(`Resizing terminal to ${sessionState.rows}x${sessionState.cols}`)
"Resizing terminal to " + sessionState.rows + "x" + sessionState.cols
)
ssh.resizeTerminal(sessionState.rows, sessionState.cols) ssh.resizeTerminal(sessionState.rows, sessionState.cols)
} }
} }
@ -262,7 +240,7 @@ module.exports = function (io, config) {
* @returns {void} * @returns {void}
*/ */
function handleControl(controlData) { function handleControl(controlData) {
debug("handleControl: Received control data: " + controlData) debug(`handleControl: Received control data: ${controlData}`)
if ( if (
validator.isIn(controlData, ["replayCredentials", "reauth"]) && validator.isIn(controlData, ["replayCredentials", "reauth"]) &&
ssh.stream ssh.stream
@ -274,7 +252,7 @@ module.exports = function (io, config) {
} }
} else { } else {
console.warn( 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} * @returns {void}
*/ */
function replayCredentials() { function replayCredentials() {
var password = sessionState.password const { password } = sessionState
var allowReplay = config.options.allowReplay || false const allowReplay = config.options.allowReplay || false
if (allowReplay && ssh.stream) { if (allowReplay && ssh.stream) {
debug(`replayCredentials: ${socket.id} Replaying credentials for `) debug(`replayCredentials: ${socket.id} Replaying credentials for `)
ssh.stream.write(password + "\n") ssh.stream.write(`${password}\n`)
} else { } else {
console.warn( 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. * Handles reauthentication for the socket.
*/ */
function handleReauth() { function handleReauth() {
debug("handleReauth: Reauthentication requested for " + socket.id) debug(`handleReauth: Reauthentication requested for ${socket.id}`)
if (config.options.allowReauth) { if (config.options.allowReauth) {
clearSessionCredentials() clearSessionCredentials()
debug(`handleReauth: Reauthenticating ${socket.id}`) debug(`handleReauth: Reauthenticating ${socket.id}`)
@ -322,9 +300,9 @@ module.exports = function (io, config) {
* @param {Error} err - The error object. * @param {Error} err - The error object.
*/ */
function handleError(context, err) { function handleError(context, err) {
var errorMessage = err ? ": " + err.message : "" const errorMessage = err ? `: ${err.message}` : ""
debug("WebSSH2 error: " + context + errorMessage) debug(`WebSSH2 error: ${context}${errorMessage}`)
socket.emit("ssherror", "SSH " + context + errorMessage) socket.emit("ssherror", `SSH ${context}${errorMessage}`)
handleConnectionClose() handleConnectionClose()
} }
@ -336,14 +314,7 @@ module.exports = function (io, config) {
* @returns {void} * @returns {void}
*/ */
function updateElement(element, value) { function updateElement(element, value) {
debug( debug(`updateElement: ${socket.id}, Element: ${element}, Value: ${value}`)
"updateElement: " +
socket.id +
", Element: " +
element +
", Value: " +
value
)
socket.emit("updateUI", { 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. * @param {string} reason - The reason for the closure.
*/ */
function handleConnectionClose(reason) { function handleConnectionClose(reason) {
debug("handleDisconnect: " + socket.id + ", Reason: " + reason) debug(`handleDisconnect: ${socket.id}, Reason: ${reason}`)
debug("handleConnectionClose: " + socket.id) debug(`handleConnectionClose: ${socket.id}`)
if (ssh) { if (ssh) {
ssh.end() ssh.end()
} }
@ -366,7 +337,7 @@ module.exports = function (io, config) {
*/ */
function clearSessionCredentials() { function clearSessionCredentials() {
debug( debug(
"clearSessionCredentials: Clearing session credentials for " + socket.id `clearSessionCredentials: Clearing session credentials for ${socket.id}`
) )
if (socket.handshake.session.sshCredentials) { if (socket.handshake.session.sshCredentials) {
socket.handshake.session.sshCredentials.username = null socket.handshake.session.sshCredentials.username = null
@ -376,28 +347,27 @@ module.exports = function (io, config) {
sessionState.authenticated = false sessionState.authenticated = false
sessionState.username = null sessionState.username = null
sessionState.password = null sessionState.password = null
socket.handshake.session.save(function (err) { socket.handshake.session.save(function(err) {
if (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 // Check for HTTP Basic Auth credentials
if (socket.handshake.session.usedBasicAuth && socket.handshake.session.sshCredentials) { if (
socket.handshake.session.usedBasicAuth &&
socket.handshake.session.sshCredentials
) {
// if (socket.handshake.session.sshCredentials) { // if (socket.handshake.session.sshCredentials) {
var creds = socket.handshake.session.sshCredentials const creds = socket.handshake.session.sshCredentials
debug( debug(
"handleConnection: " + `handleConnection: ${socket.id}, Host: ${creds.host}: HTTP Basic Credentials Exist, creds: %O`,
socket.id +
", Host: " +
creds.host +
": HTTP Basic Credentials Exist, creds: %O",
maskObject(creds) maskObject(creds)
) )
handleAuthenticate(creds) handleAuthenticate(creds)
} else if (!sessionState.authenticated) { } else if (!sessionState.authenticated) {
debug("handleConnection: " + socket.id + ", emitting request_auth") debug(`handleConnection: ${socket.id}, emitting request_auth`)
socket.emit("authentication", { action: "request_auth" }) socket.emit("authentication", { action: "request_auth" })
} }

View file

@ -1,11 +1,11 @@
// server // server
// app/ssh.js // app/ssh.js
"use strict"
const createDebug = require("debug") const createDebug = require("debug")
const debug = createDebug("webssh2:ssh")
const SSH = require("ssh2").Client const SSH = require("ssh2").Client
const maskObject = require('jsmasker'); const maskObject = require("jsmasker")
const debug = createDebug("webssh2:ssh")
function SSHConnection(config) { function SSHConnection(config) {
this.config = config this.config = config
@ -14,7 +14,7 @@ function SSHConnection(config) {
} }
SSHConnection.prototype.connect = function(creds) { SSHConnection.prototype.connect = function(creds) {
var self = this const self = this
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
debug("connect: %O", maskObject(creds)) debug("connect: %O", maskObject(creds))
@ -24,15 +24,15 @@ SSHConnection.prototype.connect = function(creds) {
self.conn = new SSH() self.conn = new SSH()
var sshConfig = self.getSSHConfig(creds) const sshConfig = self.getSSHConfig(creds)
self.conn.on("ready", function() { self.conn.on("ready", function() {
debug("connect: ready: " + creds.host) debug(`connect: ready: ${creds.host}`)
resolve() resolve()
}) })
self.conn.on("error", function(err) { self.conn.on("error", function(err) {
console.error("connect: error:" + err.message) console.error(`connect: error:${err.message}`)
reject(err) reject(err)
}) })
@ -49,14 +49,16 @@ SSHConnection.prototype.getSSHConfig = function(creds) {
tryKeyboard: true, tryKeyboard: true,
algorithms: creds.algorithms || this.config.ssh.algorithms, algorithms: creds.algorithms || this.config.ssh.algorithms,
readyTimeout: creds.readyTimeout || this.config.ssh.readyTimeout, readyTimeout: creds.readyTimeout || this.config.ssh.readyTimeout,
keepaliveInterval: creds.keepaliveInterval || this.config.ssh.keepaliveInterval, keepaliveInterval:
keepaliveCountMax: creds.keepaliveCountMax || this.config.ssh.keepaliveCountMax, creds.keepaliveInterval || this.config.ssh.keepaliveInterval,
keepaliveCountMax:
creds.keepaliveCountMax || this.config.ssh.keepaliveCountMax,
debug: createDebug("ssh") debug: createDebug("ssh")
} }
} }
SSHConnection.prototype.shell = function(options) { SSHConnection.prototype.shell = function(options) {
var self = this const self = this
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
self.conn.shell(options, function(err, stream) { self.conn.shell(options, function(err, stream) {
if (err) { if (err) {

View file

@ -2,6 +2,8 @@
// /app/utils.js // /app/utils.js
const validator = require("validator") const validator = require("validator")
const createDebug = require("debug") const createDebug = require("debug")
const crypto = require("crypto")
const debug = createDebug("webssh2:utils") const debug = createDebug("webssh2:utils")
/** /**
@ -23,3 +25,35 @@ function validateSshTerm(term) {
} }
exports.validateSshTerm = validateSshTerm 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