chore: refactor debugging, logging and error handling.

This commit is contained in:
Bill Church 2024-08-21 15:08:31 +00:00
parent e30c0c1c9b
commit 66ce643dd9
No known key found for this signature in database
13 changed files with 450 additions and 309 deletions

View file

@ -1,149 +1,75 @@
// server
// app/app.js
// const createDebug = require("debug")
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 { getCorsConfig } = require("./config")
const { applyMiddleware } = require("./middleware")
const { createServer, startServer } = require("./server")
const { configureSocketIO } = require("./io")
const { handleError, ConfigError } = require("./errors")
const { createNamespacedDebug } = require("./logger")
// const debug = createDebug("webssh2")
const debug = createNamespacedDebug("app")
/**
* Creates and configures the Express application
* @returns {express.Application} The Express application instance
* @returns {Object} An object containing the app and sessionMiddleware
*/
function createApp() {
const app = express()
// Resolve the correct path to the webssh2_client module
const clientPath = path.resolve(
__dirname,
"..",
"node_modules",
"webssh2_client",
"client",
"public"
)
try {
// Resolve the correct path to the webssh2_client module
const clientPath = path.resolve(
__dirname,
"..",
"node_modules",
"webssh2_client",
"client",
"public"
)
// Set up session middleware
const sessionMiddleware = session({
secret: config.session.secret || "webssh2_secret",
resave: false,
saveUninitialized: true,
name: config.session.name || "webssh2.sid"
})
app.use(sessionMiddleware)
// Apply middleware
const { sessionMiddleware } = applyMiddleware(app, config)
// Handle POST and GET parameters
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))
// Add cookie-setting middleware
app.use((req, res, next) => {
if (req.session.sshCredentials) {
const cookieData = {
host: req.session.sshCredentials.host,
port: req.session.sshCredentials.port
}
res.cookie("basicauth", JSON.stringify(cookieData), {
httpOnly: false,
path: "/ssh/host/",
sameSite: "Strict"
}) // ensure httOnly is false for the client to read the cookie
}
next()
})
// Use the SSH routes
app.use("/ssh", sshRoutes)
// Serve static files from the webssh2_client module with a custom prefix
app.use("/ssh/assets", express.static(clientPath))
// Use the SSH routes
app.use("/ssh", sshRoutes)
return { app, sessionMiddleware }
}
/**
* Configures Socket.IO with the given server
* @param {http.Server} server - The HTTP server instance
* @param {Function} sessionMiddleware - The session middleware
* @returns {import('socket.io').Server} The Socket.IO server instance
*/
function configureSocketIO(server, sessionMiddleware) {
const io = socketIo(server, {
serveClient: false,
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
})
)
return io
}
/**
* Creates and configures the HTTP server
* @param {express.Application} app - The Express application instance
* @returns {http.Server} The HTTP server instance
*/
function createServer(app) {
return http.createServer(app)
}
/**
* Handles server errors
* @param {Error} err - The error object
*/
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)
return { app: app, sessionMiddleware: sessionMiddleware }
} catch (err) {
throw new ConfigError(`Failed to configure Express app: ${err.message}`)
}
}
/**
* Initializes and starts the server
* @returns {Object} An object containing the server, io, and app instances
*/
function startServer() {
const { app, sessionMiddleware } = createApp()
const server = createServer(app)
const io = configureSocketIO(server, sessionMiddleware)
function initializeServer() {
try {
const { app, sessionMiddleware } = createApp()
const server = createServer(app)
const io = configureSocketIO(server, sessionMiddleware, config)
// Set up Socket.IO listeners
setupSocketIOListeners(io)
// Set up Socket.IO listeners
socketHandler(io, config)
// Start the server
server.listen(config.listen.port, config.listen.ip, () => {
console.log(
`WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}`
)
})
// Start the server
startServer(server, config)
server.on("error", handleServerError)
debug("Server initialized")
return { server, io, app }
return { server: server, io: io, app: app }
} catch (err) {
handleError(err)
process.exit(1)
}
}
// Don't start the server immediately, export the function instead
module.exports = { startServer, config }
module.exports = { initializeServer: initializeServer, config: config }

View file

@ -1,49 +1,13 @@
// server
// app/config.js
const path = require("path")
const fs = require("fs")
const readConfig = require("read-config-ng")
const Ajv = require("ajv")
const { deepMerge, generateSecureSecret } = require("./utils")
const { createNamespacedDebug } = require("./logger")
const { ConfigError, handleError } = require("./errors")
/**
* @typedef {Object} Config
* @property {Object} listen - Listening configuration
* @property {string} listen.ip - IP address to listen on
* @property {number} listen.port - Port to listen on
* @property {Object} http - HTTP configuration
* @property {string[]} http.origins - Allowed origins
* @property {Object} user - User configuration
* @property {string|null} user.name - Username
* @property {string|null} user.password - Password
* @property {Object} ssh - SSH configuration
* @property {string|null} ssh.host - SSH host
* @property {number} ssh.port - SSH port
* @property {string} ssh.term - Terminal type
* @property {number} ssh.readyTimeout - Ready timeout
* @property {number} ssh.keepaliveInterval - Keepalive interval
* @property {number} ssh.keepaliveCountMax - Max keepalive count
* @property {Object} header - Header configuration
* @property {string|null} header.text - Header text
* @property {string} header.background - Header background color
* @property {Object} options - Options configuration
* @property {boolean} options.challengeButton - Challenge button enabled
* @property {boolean} options.autoLog - Auto log enabled
* @property {boolean} options.allowReauth - Allow reauthentication
* @property {boolean} options.allowReconnect - Allow reconnection
* @property {boolean} options.allowReplay - Allow replay
* @property {Object} algorithms - Encryption algorithms
* @property {string[]} algorithms.kex - Key exchange algorithms
* @property {string[]} algorithms.cipher - Cipher algorithms
* @property {string[]} algorithms.hmac - HMAC algorithms
* @property {string[]} algorithms.compress - Compression algorithms
*/
const debug = createNamespacedDebug("config")
/**
* Default configuration
* @type {Config}
*/
const defaultConfig = {
listen: {
ip: "0.0.0.0",
@ -71,9 +35,9 @@ const defaultConfig = {
options: {
challengeButton: true,
autoLog: false,
allowReauth: false,
allowReconnect: false,
allowReplay: false
allowReauth: true,
allowReconnect: true,
allowReplay: true
},
algorithms: {
kex: [
@ -206,36 +170,15 @@ const configSchema = {
required: ["listen", "http", "user", "ssh", "header", "options", "algorithms"]
}
/**
* Gets the path to the config file
* @returns {string} The path to the config file
*/
function getConfigPath() {
const nodeRoot = path.dirname(require.main.filename)
return path.join(nodeRoot, "config.json")
}
/**
* Reads the config file synchronously
* @param {string} configPath - The path to the config file
* @returns {Object} The configuration object
*/
function readConfigFile(configPath) {
console.log(`WebSSH2 service reading config from: ${configPath}`)
return readConfig.sync(configPath)
}
/**
* Validates the configuration against the schema
* @param {Object} config - The configuration object to validate
* @returns {Object} The validated configuration object
* @throws {Error} If the configuration is invalid
*/
function validateConfig(config) {
const ajv = new Ajv()
const validate = ajv.compile(configSchema)
const valid = validate(config)
console.log("WebSSH2 service validating config")
if (!valid) {
throw new Error(
`Config validation error: ${ajv.errorsText(validate.errors)}`
@ -244,77 +187,85 @@ function validateConfig(config) {
return config
}
/**
* Logs an error message
* @param {string} message - The error message
* @param {Error} [error] - The error object
*/
function logError(message, error) {
console.error(message)
if (error) {
console.error(`ERROR:\n\n ${error}`)
}
}
/**
* Loads and merges the configuration synchronously
* @returns {Object} The merged configuration
*/
function loadConfig() {
const configPath = getConfigPath()
try {
if (fs.existsSync(configPath)) {
const providedConfig = readConfigFile(configPath)
// Deep merge the provided config with the default config
const providedConfig = readConfig.sync(configPath)
const mergedConfig = deepMerge(
JSON.parse(JSON.stringify(defaultConfig)),
providedConfig
)
// Override the port with the PORT environment variable if it's set
if (process.env.PORT) {
mergedConfig.listen.port = parseInt(process.env.PORT, 10)
console.log(`Using PORT from environment: ${mergedConfig.listen.port}`)
debug("Using PORT from environment: %s", mergedConfig.listen.port)
}
const validatedConfig = validateConfig(mergedConfig)
console.log("Merged and validated configuration")
debug("Merged and validated configuration")
return validatedConfig
}
logError(
`\n\nERROR: Missing config.json for webssh. Using default config: ${JSON.stringify(
defaultConfig
)}\n\n See config.json.sample for details\n\n`
)
debug("Missing config.json for webssh. Using default config")
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`,
err
const error = new ConfigError(
`Problem loading config.json for webssh: ${err.message}`
)
handleError(error)
return defaultConfig
}
}
/**
* The loaded configuration
* @type {Object}
* Configuration for the application.
*
* @returns {Object} config
* @property {Object} listen - Configuration for listening IP and port.
* @property {string} listen.ip - The IP address to listen on.
* @property {number} listen.port - The port number to listen on.
* @property {Object} http - Configuration for HTTP settings.
* @property {string[]} http.origins - The allowed origins for HTTP requests.
* @property {Object} user - Configuration for user settings.
* @property {string|null} user.name - The name of the user.
* @property {string|null} user.password - The password of the user.
* @property {Object} ssh - Configuration for SSH settings.
* @property {string|null} ssh.host - The SSH host.
* @property {number} ssh.port - The SSH port.
* @property {string} ssh.term - The SSH terminal type.
* @property {number} ssh.readyTimeout - The SSH ready timeout.
* @property {number} ssh.keepaliveInterval - The SSH keepalive interval.
* @property {number} ssh.keepaliveCountMax - The SSH keepalive count maximum.
* @property {Object} header - Configuration for header settings.
* @property {string|null} header.text - The header text.
* @property {string} header.background - The header background color.
* @property {Object} options - Configuration for options settings.
* @property {boolean} options.challengeButton - Whether to show the challenge button.
* @property {boolean} options.autoLog - Whether to automatically log.
* @property {boolean} options.allowReauth - Whether to allow reauthentication.
* @property {boolean} options.allowReconnect - Whether to allow reconnection.
* @property {boolean} options.allowReplay - Whether to allow replay.
* @property {Object} algorithms - Configuration for algorithms settings.
* @property {string[]} algorithms.kex - The key exchange algorithms.
* @property {string[]} algorithms.cipher - The cipher algorithms.
* @property {string[]} algorithms.hmac - The HMAC algorithms.
* @property {string[]} algorithms.compress - The compression algorithms.
* @property {Object} session - Configuration for session settings.
* @property {string} session.secret - The session secret.
* @property {string} session.name - The session name.
*/
const config = loadConfig()
module.exports = config
/**
* Gets the CORS configuration
* @returns {Object} The CORS configuration object
*/
module.exports.getCorsConfig = function getCorsConfig() {
function getCorsConfig() {
return {
origin: config.origin || ["*.*"],
origin: config.http.origins,
methods: ["GET", "POST"],
credentials: true
}
}
// Extend the config object with the getCorsConfig function
config.getCorsConfig = getCorsConfig
module.exports = config

View file

@ -1,12 +1,11 @@
// server
// app/connectionHandler.js
const createDebug = require("debug")
const fs = require("fs")
const path = require("path")
const { createNamespacedDebug } = require("./logger")
const debug = createDebug("webssh2:connectionHandler")
const debug = createNamespacedDebug("connectionHandler")
/**
* Modify the HTML content by replacing certain placeholders with dynamic values.
* @param {string} html - The original HTML content.

69
app/errors.js Normal file
View file

@ -0,0 +1,69 @@
// server
// app/errors.js
const util = require("util")
const { logError, createNamespacedDebug } = require("./logger")
const debug = createNamespacedDebug("errors")
/**
* Custom error for WebSSH2
* @param {string} message - The error message
* @param {string} code - The error code
*/
function WebSSH2Error(message, code) {
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
this.code = code
}
util.inherits(WebSSH2Error, Error)
/**
* Custom error for configuration issues
* @param {string} message - The error message
*/
function ConfigError(message) {
WebSSH2Error.call(this, message, "CONFIG_ERROR")
}
util.inherits(ConfigError, WebSSH2Error)
/**
* Custom error for SSH connection issues
* @param {string} message - The error message
*/
function SSHConnectionError(message) {
WebSSH2Error.call(this, message, "SSH_CONNECTION_ERROR")
}
util.inherits(SSHConnectionError, WebSSH2Error)
/**
* Handles an error by logging it and optionally sending a response
* @param {Error} err - The error to handle
* @param {Object} [res] - The response object (if in an Express route)
*/
function handleError(err, res) {
if (err instanceof WebSSH2Error) {
logError(err.message, err)
debug(err.message)
if (res) {
res.status(500).json({ error: err.message, code: err.code })
}
} else {
logError("An unexpected error occurred", err)
debug("Unexpected error: %O", err)
if (res) {
res.status(500).json({ error: "An unexpected error occurred" })
}
}
}
module.exports = {
WebSSH2Error: WebSSH2Error,
ConfigError: ConfigError,
SSHConnectionError: SSHConnectionError,
handleError: handleError
}

35
app/io.js Normal file
View file

@ -0,0 +1,35 @@
const socketIo = require("socket.io")
const sharedsession = require("express-socket.io-session")
const { createNamespacedDebug } = require("./logger")
const debug = createNamespacedDebug("app")
/**
* Configures Socket.IO with the given server
* @param {http.Server} server - The HTTP server instance
* @param {Function} sessionMiddleware - The session middleware
* @param {Object} config - The configuration object
* @returns {import('socket.io').Server} The Socket.IO server instance
*/
function configureSocketIO(server, sessionMiddleware, config) {
const io = socketIo(server, {
serveClient: false,
path: "/ssh/socket.io",
pingTimeout: 60000, // 1 minute
pingInterval: 25000, // 25 seconds
cors: config.getCorsConfig()
})
// Share session with io sockets
io.use(
sharedsession(sessionMiddleware, {
autoSave: true
})
)
debug("Socket.IO configured")
return io
}
module.exports = { configureSocketIO }

30
app/logger.js Normal file
View file

@ -0,0 +1,30 @@
// server
// app/logger.js
const createDebug = require("debug")
/**
* Creates a debug function for a specific namespace
* @param {string} namespace - The debug namespace
* @returns {Function} The debug function
*/
function createNamespacedDebug(namespace) {
return createDebug(`webssh2:${namespace}`)
}
/**
* Logs an error message
* @param {string} message - The error message
* @param {Error} [error] - The error object
*/
function logError(message, error) {
console.error(message)
if (error) {
console.error(`ERROR:\n\n ${error}`)
}
}
module.exports = {
createNamespacedDebug: createNamespacedDebug,
logError: logError
}

76
app/middleware.js Normal file
View file

@ -0,0 +1,76 @@
// server
// app/middleware.js
const createDebug = require("debug")
const session = require("express-session")
const bodyParser = require("body-parser")
const debug = createDebug("webssh2:middleware")
/**
* Creates and configures session middleware
* @param {Object} config - The configuration object
* @returns {Function} The session middleware
*/
function createSessionMiddleware(config) {
return session({
secret: config.session.secret || "webssh2_secret",
resave: false,
saveUninitialized: true,
name: config.session.name || "webssh2.sid"
})
}
/**
* Creates body parser middleware
* @returns {Function[]} Array of body parser middleware
*/
function createBodyParserMiddleware() {
return [bodyParser.urlencoded({ extended: true }), bodyParser.json()]
}
/**
* Creates cookie-setting middleware
* @returns {Function} The cookie-setting middleware
*/
function createCookieMiddleware() {
return (req, res, next) => {
if (req.session.sshCredentials) {
const cookieData = {
host: req.session.sshCredentials.host,
port: req.session.sshCredentials.port
}
res.cookie("basicauth", JSON.stringify(cookieData), {
httpOnly: false,
path: "/ssh/host/",
sameSite: "Strict"
})
}
next()
}
}
/**
* Applies all middleware to the Express app
* @param {express.Application} app - The Express application
* @param {Object} config - The configuration object
* @returns {Object} An object containing the session middleware
*/
function applyMiddleware(app, config) {
const sessionMiddleware = createSessionMiddleware(config)
app.use(sessionMiddleware)
app.use(createBodyParserMiddleware())
app.use(createCookieMiddleware())
debug("Middleware applied")
return { sessionMiddleware }
}
module.exports = {
applyMiddleware,
createSessionMiddleware,
createBodyParserMiddleware,
createCookieMiddleware
}

View file

@ -1,11 +1,7 @@
// server
// /app/routes.js
const createDebug = require("debug")
const debug = createDebug("webssh2:routes")
const express = require("express")
const router = express.Router()
const basicAuth = require("basic-auth")
const maskObject = require("jsmasker")
const validator = require("validator")
@ -15,6 +11,11 @@ const {
validateSshTerm
} = require("./utils")
const handleConnection = require("./connectionHandler")
const { createNamespacedDebug } = require("./logger")
const { ConfigError, handleError } = require("./errors")
const debug = createNamespacedDebug("routes")
const router = express.Router()
// eslint-disable-next-line consistent-return
function auth(req, res, next) {
@ -39,32 +40,36 @@ router.get("/", function(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) {
debug(`router.get.host: /ssh/host/${req.params.host} route`)
const host = getValidatedHost(req.params.host)
const port = getValidatedPort(req.query.port)
try {
const host = getValidatedHost(req.params.host)
const port = getValidatedPort(req.query.port)
// Validate and sanitize sshterm parameter if it exists
const sshterm = validateSshTerm(req.query.sshterm)
// Validate and sanitize sshterm parameter if it exists
const sshterm = validateSshTerm(req.query.sshterm)
req.session.sshCredentials = req.session.sshCredentials || {}
req.session.sshCredentials.host = host
req.session.sshCredentials.port = port
if (req.query.sshterm) {
req.session.sshCredentials.term = sshterm
req.session.sshCredentials = req.session.sshCredentials || {}
req.session.sshCredentials.host = host
req.session.sshCredentials.port = port
if (req.query.sshterm) {
req.session.sshCredentials.term = sshterm
}
req.session.usedBasicAuth = true
// Sanitize and log the sshCredentials object
const sanitizedCredentials = maskObject(
JSON.parse(JSON.stringify(req.session.sshCredentials))
)
debug("/ssh/host/ Credentials: ", sanitizedCredentials)
handleConnection(req, res, { host: host })
} catch (err) {
const error = new ConfigError(`Invalid configuration: ${err.message}`)
handleError(error, res)
}
req.session.usedBasicAuth = true
// Sanitize and log the sshCredentials object
const sanitizedCredentials = maskObject(
JSON.parse(JSON.stringify(req.session.sshCredentials))
)
debug("/ssh/host/ Credentials: ", sanitizedCredentials)
handleConnection(req, res, { host: host })
})
// Clear credentials route

37
app/server.js Normal file
View file

@ -0,0 +1,37 @@
const http = require("http")
// const { createNamespacedDebug } = require("./logger")
// const debug = createNamespacedDebug("server")
/**
* Creates and configures the HTTP server
* @param {express.Application} app - The Express application instance
* @returns {http.Server} The HTTP server instance
*/
function createServer(app) {
return http.createServer(app)
}
/**
* Handles server errors
* @param {Error} err - The error object
*/
function handleServerError(err) {
console.error("WebSSH2 server.listen ERROR:", err.code)
}
/**
* Starts the server
* @param {http.Server} server - The server instance
* @param {Object} config - The configuration object
*/
function startServer(server, config) {
server.listen(config.listen.port, config.listen.ip, () => {
console.log(
`startServer: listening on ${config.listen.ip}:${config.listen.port}`
)
})
server.on("error", handleServerError)
}
module.exports = { createServer, startServer }

View file

@ -1,20 +1,16 @@
// server
// app/socket.js
const createDebug = require("debug")
const maskObject = require("jsmasker")
const validator = require("validator")
const SSHConnection = require("./ssh")
const { createNamespacedDebug } = require("./logger")
const { SSHConnectionError, handleError } = require("./errors")
const debug = createDebug("webssh2:socket")
const debug = createNamespacedDebug("socket")
const { validateSshTerm, isValidCredentials } = require("./utils")
class WebSSH2Socket {
/**
* Creates an instance of WebSSH2Socket.
* @param {SocketIO.Socket} socket - The socket instance.
* @param {Object} config - The configuration object.
*/
constructor(socket, config) {
this.socket = socket
this.config = config
@ -32,9 +28,6 @@ class WebSSH2Socket {
this.initializeSocketEvents()
}
/**
* Initializes the socket event listeners.
*/
initializeSocketEvents() {
debug(`io.on connection: ${this.socket.id}`)
@ -53,15 +46,26 @@ class WebSSH2Socket {
this.socket.emit("authentication", { action: "request_auth" })
}
this.socket.on("authenticate", creds => this.handleAuthenticate(creds))
this.socket.on("terminal", data => this.handleTerminal(data))
this.socket.on("disconnect", reason => this.handleConnectionClose(reason))
this.socket.on(
"authenticate",
function(creds) {
this.handleAuthenticate(creds)
}.bind(this)
)
this.socket.on(
"terminal",
function(data) {
this.handleTerminal(data)
}.bind(this)
)
this.socket.on(
"disconnect",
function(reason) {
this.handleConnectionClose(reason)
}.bind(this)
)
}
/**
* Handles the authentication process.
* @param {Object} creds - The credentials for authentication.
*/
handleAuthenticate(creds) {
debug(`handleAuthenticate: ${this.socket.id}, %O`, maskObject(creds))
@ -71,7 +75,7 @@ class WebSSH2Socket {
: this.config.ssh.term
this.initializeConnection(creds)
} else {
console.warn(`handleAuthenticate: ${this.socket.id}, CREDENTIALS INVALID`)
debug(`handleAuthenticate: ${this.socket.id}, CREDENTIALS INVALID`)
this.socket.emit("authentication", {
success: false,
message: "Invalid credentials format"
@ -79,10 +83,6 @@ class WebSSH2Socket {
}
}
/**
* Initializes the SSH connection.
* @param {Object} creds - The credentials for the SSH connection.
*/
initializeConnection(creds) {
debug(
`initializeConnection: ${this.socket.id}, INITIALIZING SSH CONNECTION: Host: ${creds.host}, creds: %O`,
@ -91,40 +91,47 @@ class WebSSH2Socket {
this.ssh
.connect(creds)
.then(() => {
this.sessionState = Object.assign({}, this.sessionState, {
authenticated: true,
username: creds.username,
password: creds.password,
host: creds.host,
port: creds.port
})
.then(
function() {
this.sessionState = Object.assign({}, this.sessionState, {
authenticated: true,
username: creds.username,
password: creds.password,
host: creds.host,
port: creds.port
})
const authResult = { action: "auth_result", success: true }
this.socket.emit("authentication", authResult)
const authResult = { action: "auth_result", success: true }
this.socket.emit("authentication", authResult)
const permissions = {
autoLog: this.config.options.autoLog || false,
allowReplay: this.config.options.allowReplay || false,
allowReconnect: this.config.options.allowReconnect || false,
allowReauth: this.config.options.allowReauth || false
}
this.socket.emit("permissions", permissions)
const permissions = {
autoLog: this.config.options.autoLog || false,
allowReplay: this.config.options.allowReplay || false,
allowReconnect: this.config.options.allowReconnect || false,
allowReauth: this.config.options.allowReauth || false
}
this.socket.emit("permissions", permissions)
this.updateElement("footer", `ssh://${creds.host}:${creds.port}`)
this.updateElement("footer", `ssh://${creds.host}:${creds.port}`)
if (this.config.header && this.config.header.text !== null) {
this.updateElement("header", this.config.header.text)
}
if (this.config.header && this.config.header.text !== null) {
this.updateElement("header", this.config.header.text)
}
this.socket.emit("getTerminal", true)
})
.catch(err => {
console.error(
`initializeConnection: SSH CONNECTION ERROR: ${this.socket.id}, Host: ${creds.host}, Error: ${err.message}`
)
this.handleError("SSH CONNECTION ERROR", err)
})
this.socket.emit("getTerminal", true)
}.bind(this)
)
.catch(
function(err) {
debug(
`initializeConnection: SSH CONNECTION ERROR: ${this.socket.id}, Host: ${creds.host}, Error: ${err.message}`
)
handleError(
new SSHConnectionError(`SSH CONNECTION ERROR: ${err.message}`)
)
this.socket.emit("ssherror", `SSH CONNECTION ERROR: ${err.message}`)
}.bind(this)
)
}
/**
@ -232,6 +239,7 @@ class WebSSH2Socket {
*/
handleError(context, err) {
const errorMessage = err ? `: ${err.message}` : ""
handleError(new SSHConnectionError(`SSH ${context}${errorMessage}`))
this.socket.emit("ssherror", `SSH ${context}${errorMessage}`)
this.handleConnectionClose()
}

View file

@ -1,11 +1,12 @@
// server
// app/ssh.js
const createDebug = require("debug")
const SSH = require("ssh2").Client
const maskObject = require("jsmasker")
const { createNamespacedDebug } = require("./logger")
const { SSHConnectionError, handleError } = require("./errors")
const debug = createDebug("webssh2:ssh")
const debug = createNamespacedDebug("ssh")
function SSHConnection(config) {
this.config = config
@ -32,8 +33,11 @@ SSHConnection.prototype.connect = function(creds) {
})
self.conn.on("error", function(err) {
console.error(`connect: error:${err.message}`)
reject(err)
const error = new SSHConnectionError(
`SSH Connection error: ${err.message}`
)
handleError(error)
reject(error)
})
self.conn.connect(sshConfig)
@ -53,7 +57,7 @@ SSHConnection.prototype.getSSHConfig = function(creds) {
creds.keepaliveInterval || this.config.ssh.keepaliveInterval,
keepaliveCountMax:
creds.keepaliveCountMax || this.config.ssh.keepaliveCountMax,
debug: createDebug("ssh")
debug: createNamespacedDebug("ssh2")
}
}

View file

@ -1,10 +1,10 @@
// server
// /app/utils.js
const validator = require("validator")
const createDebug = require("debug")
const crypto = require("crypto")
const { createNamespacedDebug } = require("./logger")
const debug = createDebug("webssh2:utils")
const debug = createNamespacedDebug("utils")
/**
* Deep merges two objects

View file

@ -1,4 +1,5 @@
"use strict"
#!/usr/bin/env node
// server
// index.js
/**
@ -8,13 +9,13 @@
* Bill Church - https://github.com/billchurch/WebSSH2 - May 2017
*/
const { startServer, config } = require("./app/app")
const { initializeServer } = require("./app/app")
/**
* Main function to start the application
*/
function main() {
startServer()
initializeServer()
}
// Run the application
@ -22,5 +23,5 @@ main()
// For testing purposes, export the functions
module.exports = {
startServer
initializeServer
}