diff --git a/app/app.js b/app/app.js index aef5d1f..209d6dc 100644 --- a/app/app.js +++ b/app/app.js @@ -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 } diff --git a/app/config.js b/app/config.js index 1137e2d..d9dc757 100644 --- a/app/config.js +++ b/app/config.js @@ -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 diff --git a/app/connectionHandler.js b/app/connectionHandler.js index 7bb8a0d..a04623d 100644 --- a/app/connectionHandler.js +++ b/app/connectionHandler.js @@ -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. diff --git a/app/errors.js b/app/errors.js new file mode 100644 index 0000000..1da58d8 --- /dev/null +++ b/app/errors.js @@ -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 +} diff --git a/app/io.js b/app/io.js new file mode 100644 index 0000000..45c9bc2 --- /dev/null +++ b/app/io.js @@ -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 } diff --git a/app/logger.js b/app/logger.js new file mode 100644 index 0000000..49fcb2e --- /dev/null +++ b/app/logger.js @@ -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 +} diff --git a/app/middleware.js b/app/middleware.js new file mode 100644 index 0000000..c16bafc --- /dev/null +++ b/app/middleware.js @@ -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 +} diff --git a/app/routes.js b/app/routes.js index 8189ba1..28b9107 100644 --- a/app/routes.js +++ b/app/routes.js @@ -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 diff --git a/app/server.js b/app/server.js new file mode 100644 index 0000000..709a000 --- /dev/null +++ b/app/server.js @@ -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 } diff --git a/app/socket.js b/app/socket.js index 6477677..060459e 100644 --- a/app/socket.js +++ b/app/socket.js @@ -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() } diff --git a/app/ssh.js b/app/ssh.js index 5d6531e..d743226 100644 --- a/app/ssh.js +++ b/app/ssh.js @@ -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") } } diff --git a/app/utils.js b/app/utils.js index 66b3a63..325b87a 100644 --- a/app/utils.js +++ b/app/utils.js @@ -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 diff --git a/index.js b/index.js index 83af716..03483e3 100644 --- a/index.js +++ b/index.js @@ -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 }