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,28 +1,27 @@
// server // server
// app/app.js // app/app.js
// const createDebug = require("debug")
const http = require("http")
const express = require("express") const express = require("express")
const socketIo = require("socket.io")
const path = require("path") 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 config = require("./config")
const socketHandler = require("./socket") const socketHandler = require("./socket")
const sshRoutes = require("./routes") 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 * Creates and configures the Express application
* @returns {express.Application} The Express application instance * @returns {Object} An object containing the app and sessionMiddleware
*/ */
function createApp() { function createApp() {
const app = express() const app = express()
try {
// Resolve the correct path to the webssh2_client module // Resolve the correct path to the webssh2_client module
const clientPath = path.resolve( const clientPath = path.resolve(
__dirname, __dirname,
@ -33,34 +32,8 @@ function createApp() {
"public" "public"
) )
// Set up session middleware // Apply middleware
const sessionMiddleware = session({ const { sessionMiddleware } = applyMiddleware(app, config)
secret: config.session.secret || "webssh2_secret",
resave: false,
saveUninitialized: true,
name: config.session.name || "webssh2.sid"
})
app.use(sessionMiddleware)
// Handle POST and GET parameters
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
// 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()
})
// Serve static files from the webssh2_client module with a custom prefix // Serve static files from the webssh2_client module with a custom prefix
app.use("/ssh/assets", express.static(clientPath)) app.use("/ssh/assets", express.static(clientPath))
@ -68,82 +41,35 @@ function createApp() {
// Use the SSH routes // Use the SSH routes
app.use("/ssh", sshRoutes) app.use("/ssh", sshRoutes)
return { app, sessionMiddleware } return { app: app, sessionMiddleware: sessionMiddleware }
} catch (err) {
throw new ConfigError(`Failed to configure Express app: ${err.message}`)
} }
/**
* 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)
} }
/** /**
* 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
*/ */
function startServer() { function initializeServer() {
try {
const { app, sessionMiddleware } = createApp() const { app, sessionMiddleware } = createApp()
const server = createServer(app) const server = createServer(app)
const io = configureSocketIO(server, sessionMiddleware) const io = configureSocketIO(server, sessionMiddleware, config)
// Set up Socket.IO listeners // Set up Socket.IO listeners
setupSocketIOListeners(io) socketHandler(io, config)
// Start the server // Start the server
server.listen(config.listen.port, config.listen.ip, () => { startServer(server, config)
console.log(
`WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}`
)
})
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 = { initializeServer: initializeServer, config: config }
module.exports = { startServer, config }

View file

@ -1,49 +1,13 @@
// server
// app/config.js
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 { deepMerge, generateSecureSecret } = require("./utils") const { deepMerge, generateSecureSecret } = require("./utils")
const { createNamespacedDebug } = require("./logger")
const { ConfigError, handleError } = require("./errors")
/** const debug = createNamespacedDebug("config")
* @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
*/
/**
* Default configuration
* @type {Config}
*/
const defaultConfig = { const defaultConfig = {
listen: { listen: {
ip: "0.0.0.0", ip: "0.0.0.0",
@ -71,9 +35,9 @@ const defaultConfig = {
options: { options: {
challengeButton: true, challengeButton: true,
autoLog: false, autoLog: false,
allowReauth: false, allowReauth: true,
allowReconnect: false, allowReconnect: true,
allowReplay: false allowReplay: true
}, },
algorithms: { algorithms: {
kex: [ kex: [
@ -206,36 +170,15 @@ const configSchema = {
required: ["listen", "http", "user", "ssh", "header", "options", "algorithms"] 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() { function getConfigPath() {
const nodeRoot = path.dirname(require.main.filename) const nodeRoot = path.dirname(require.main.filename)
return path.join(nodeRoot, "config.json") 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) { function validateConfig(config) {
const ajv = new Ajv() const ajv = new Ajv()
const validate = ajv.compile(configSchema) const validate = ajv.compile(configSchema)
const valid = validate(config) const valid = validate(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)}`
@ -244,77 +187,85 @@ function validateConfig(config) {
return 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() { function loadConfig() {
const configPath = getConfigPath() const configPath = getConfigPath()
try { try {
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
const providedConfig = readConfigFile(configPath) const providedConfig = readConfig.sync(configPath)
// Deep merge the provided config with the default config
const mergedConfig = deepMerge( const mergedConfig = deepMerge(
JSON.parse(JSON.stringify(defaultConfig)), JSON.parse(JSON.stringify(defaultConfig)),
providedConfig providedConfig
) )
// Override the port with the PORT environment variable if it's set
if (process.env.PORT) { if (process.env.PORT) {
mergedConfig.listen.port = parseInt(process.env.PORT, 10) 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) const validatedConfig = validateConfig(mergedConfig)
console.log("Merged and validated configuration") debug("Merged and validated configuration")
return validatedConfig return validatedConfig
} }
logError( debug("Missing config.json for webssh. Using default config")
`\n\nERROR: Missing config.json for webssh. Using default config: ${JSON.stringify(
defaultConfig
)}\n\n See config.json.sample for details\n\n`
)
return defaultConfig return defaultConfig
} catch (err) { } catch (err) {
logError( const error = new ConfigError(
`\n\nERROR: Problem loading config.json for webssh. Using default config: ${JSON.stringify( `Problem loading config.json for webssh: ${err.message}`
defaultConfig
)}\n\n See config.json.sample for details\n\n`,
err
) )
handleError(error)
return defaultConfig return defaultConfig
} }
} }
/** /**
* The loaded configuration * Configuration for the application.
* @type {Object} *
* @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() const config = loadConfig()
module.exports = config function getCorsConfig() {
/**
* Gets the CORS configuration
* @returns {Object} The CORS configuration object
*/
module.exports.getCorsConfig = function getCorsConfig() {
return { return {
origin: config.origin || ["*.*"], origin: config.http.origins,
methods: ["GET", "POST"], methods: ["GET", "POST"],
credentials: true credentials: true
} }
} }
// Extend the config object with the getCorsConfig function
config.getCorsConfig = getCorsConfig
module.exports = config

View file

@ -1,12 +1,11 @@
// server // server
// app/connectionHandler.js // app/connectionHandler.js
const createDebug = require("debug")
const fs = require("fs") const fs = require("fs")
const path = require("path") 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. * Modify the HTML content by replacing certain placeholders with dynamic values.
* @param {string} html - The original HTML content. * @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 // server
// /app/routes.js // /app/routes.js
const createDebug = require("debug")
const debug = createDebug("webssh2:routes")
const express = require("express") const express = require("express")
const router = express.Router()
const basicAuth = require("basic-auth") const basicAuth = require("basic-auth")
const maskObject = require("jsmasker") const maskObject = require("jsmasker")
const validator = require("validator") const validator = require("validator")
@ -15,6 +11,11 @@ const {
validateSshTerm validateSshTerm
} = require("./utils") } = require("./utils")
const handleConnection = require("./connectionHandler") 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 // eslint-disable-next-line consistent-return
function auth(req, res, next) { function auth(req, res, next) {
@ -39,11 +40,11 @@ router.get("/", function(req, res) {
handleConnection(req, res) handleConnection(req, res)
}) })
// Scenario 2: Auth required, uses HTTP Basic Auth
// 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(`router.get.host: /ssh/host/${req.params.host} route`) debug(`router.get.host: /ssh/host/${req.params.host} route`)
try {
const host = getValidatedHost(req.params.host) const host = getValidatedHost(req.params.host)
const port = getValidatedPort(req.query.port) const port = getValidatedPort(req.query.port)
@ -65,6 +66,10 @@ router.get("/host/:host", auth, function(req, res) {
debug("/ssh/host/ Credentials: ", sanitizedCredentials) debug("/ssh/host/ Credentials: ", sanitizedCredentials)
handleConnection(req, res, { host: host }) handleConnection(req, res, { host: host })
} catch (err) {
const error = new ConfigError(`Invalid configuration: ${err.message}`)
handleError(error, res)
}
}) })
// Clear credentials route // 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 // server
// app/socket.js // app/socket.js
const createDebug = require("debug")
const maskObject = require("jsmasker") const maskObject = require("jsmasker")
const validator = require("validator") const validator = require("validator")
const SSHConnection = require("./ssh") 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") const { validateSshTerm, isValidCredentials } = require("./utils")
class WebSSH2Socket { class WebSSH2Socket {
/**
* Creates an instance of WebSSH2Socket.
* @param {SocketIO.Socket} socket - The socket instance.
* @param {Object} config - The configuration object.
*/
constructor(socket, config) { constructor(socket, config) {
this.socket = socket this.socket = socket
this.config = config this.config = config
@ -32,9 +28,6 @@ class WebSSH2Socket {
this.initializeSocketEvents() this.initializeSocketEvents()
} }
/**
* Initializes the socket event listeners.
*/
initializeSocketEvents() { initializeSocketEvents() {
debug(`io.on connection: ${this.socket.id}`) debug(`io.on connection: ${this.socket.id}`)
@ -53,15 +46,26 @@ class WebSSH2Socket {
this.socket.emit("authentication", { action: "request_auth" }) this.socket.emit("authentication", { action: "request_auth" })
} }
this.socket.on("authenticate", creds => this.handleAuthenticate(creds)) this.socket.on(
this.socket.on("terminal", data => this.handleTerminal(data)) "authenticate",
this.socket.on("disconnect", reason => this.handleConnectionClose(reason)) 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) { handleAuthenticate(creds) {
debug(`handleAuthenticate: ${this.socket.id}, %O`, maskObject(creds)) debug(`handleAuthenticate: ${this.socket.id}, %O`, maskObject(creds))
@ -71,7 +75,7 @@ class WebSSH2Socket {
: this.config.ssh.term : this.config.ssh.term
this.initializeConnection(creds) this.initializeConnection(creds)
} else { } else {
console.warn(`handleAuthenticate: ${this.socket.id}, CREDENTIALS INVALID`) debug(`handleAuthenticate: ${this.socket.id}, CREDENTIALS INVALID`)
this.socket.emit("authentication", { this.socket.emit("authentication", {
success: false, success: false,
message: "Invalid credentials format" 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) { initializeConnection(creds) {
debug( debug(
`initializeConnection: ${this.socket.id}, INITIALIZING SSH CONNECTION: Host: ${creds.host}, creds: %O`, `initializeConnection: ${this.socket.id}, INITIALIZING SSH CONNECTION: Host: ${creds.host}, creds: %O`,
@ -91,7 +91,8 @@ class WebSSH2Socket {
this.ssh this.ssh
.connect(creds) .connect(creds)
.then(() => { .then(
function() {
this.sessionState = Object.assign({}, this.sessionState, { this.sessionState = Object.assign({}, this.sessionState, {
authenticated: true, authenticated: true,
username: creds.username, username: creds.username,
@ -118,13 +119,19 @@ class WebSSH2Socket {
} }
this.socket.emit("getTerminal", true) this.socket.emit("getTerminal", true)
}) }.bind(this)
.catch(err => { )
console.error( .catch(
function(err) {
debug(
`initializeConnection: SSH CONNECTION ERROR: ${this.socket.id}, Host: ${creds.host}, Error: ${err.message}` `initializeConnection: SSH CONNECTION ERROR: ${this.socket.id}, Host: ${creds.host}, Error: ${err.message}`
) )
this.handleError("SSH CONNECTION ERROR", err) 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) { handleError(context, err) {
const errorMessage = err ? `: ${err.message}` : "" const errorMessage = err ? `: ${err.message}` : ""
handleError(new SSHConnectionError(`SSH ${context}${errorMessage}`))
this.socket.emit("ssherror", `SSH ${context}${errorMessage}`) this.socket.emit("ssherror", `SSH ${context}${errorMessage}`)
this.handleConnectionClose() this.handleConnectionClose()
} }

View file

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

View file

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

View file

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