chore: linting
This commit is contained in:
parent
dbcfc30cd0
commit
da21c89f20
7 changed files with 207 additions and 240 deletions
39
app/app.js
39
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 }
|
||||
|
|
|
@ -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'
|
||||
`\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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
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")
|
||||
}
|
||||
|
||||
var modifiedHtml = data.replace(
|
||||
let modifiedHtml = data.replace(
|
||||
/(src|href)="(?!http|\/\/)/g,
|
||||
'$1="/ssh/assets/'
|
||||
)
|
||||
|
||||
modifiedHtml = modifiedHtml.replace(
|
||||
'window.webssh2Config = null;',
|
||||
'window.webssh2Config = ' + JSON.stringify(tempConfig) + ';'
|
||||
"window.webssh2Config = null;",
|
||||
`window.webssh2Config = ${JSON.stringify(tempConfig)};`
|
||||
)
|
||||
|
||||
res.send(modifiedHtml)
|
||||
}
|
||||
)
|
||||
// Explicitly return to satisfy the linter
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = handleConnection
|
||||
|
|
|
@ -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
|
||||
|
@ -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.")
|
||||
})
|
||||
|
|
176
app/socket.js
176
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.usedBasicAuth &&
|
||||
socket.handshake.session.sshCredentials
|
||||
) {
|
||||
// if (socket.handshake.session.sshCredentials) {
|
||||
var creds = 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" })
|
||||
}
|
||||
|
||||
|
|
22
app/ssh.js
22
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))
|
||||
|
||||
|
@ -24,15 +24,15 @@ 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) {
|
||||
|
|
34
app/utils.js
34
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
|
||||
|
|
Loading…
Reference in a new issue