diff --git a/app/app.js b/app/app.js index 209d6dc..c64ca1e 100644 --- a/app/app.js +++ b/app/app.js @@ -2,7 +2,6 @@ // app/app.js const express = require("express") -const path = require("path") const config = require("./config") const socketHandler = require("./socket") const sshRoutes = require("./routes") @@ -11,6 +10,7 @@ const { createServer, startServer } = require("./server") const { configureSocketIO } = require("./io") const { handleError, ConfigError } = require("./errors") const { createNamespacedDebug } = require("./logger") +const { DEFAULTS, MESSAGES } = require("./constants") const debug = createNamespacedDebug("app") @@ -23,14 +23,7 @@ function createApp() { try { // Resolve the correct path to the webssh2_client module - const clientPath = path.resolve( - __dirname, - "..", - "node_modules", - "webssh2_client", - "client", - "public" - ) + const clientPath = DEFAULTS.WEBSSH2_CLIENT_PATH // Apply middleware const { sessionMiddleware } = applyMiddleware(app, config) @@ -43,7 +36,9 @@ function createApp() { return { app: app, sessionMiddleware: sessionMiddleware } } catch (err) { - throw new ConfigError(`Failed to configure Express app: ${err.message}`) + throw new ConfigError( + `${MESSAGES.EXPRESS_APP_CONFIG_ERROR}: ${err.message}` + ) } } diff --git a/app/config.js b/app/config.js index d9dc757..ccc34ab 100644 --- a/app/config.js +++ b/app/config.js @@ -5,13 +5,14 @@ const Ajv = require("ajv") const { deepMerge, generateSecureSecret } = require("./utils") const { createNamespacedDebug } = require("./logger") const { ConfigError, handleError } = require("./errors") +const { DEFAULTS } = require("./constants") const debug = createNamespacedDebug("config") const defaultConfig = { listen: { ip: "0.0.0.0", - port: 2222 + port: DEFAULTS.LISTEN_PORT }, http: { origins: ["*:*"] @@ -22,8 +23,8 @@ const defaultConfig = { }, ssh: { host: null, - port: 22, - term: "xterm-color", + port: DEFAULTS.SSH_PORT, + term: DEFAULTS.SSH_TERM, readyTimeout: 20000, keepaliveInterval: 120000, keepaliveCountMax: 10 @@ -61,7 +62,7 @@ const defaultConfig = { compress: ["none", "zlib@openssh.com", "zlib"] }, session: { - secret: generateSecureSecret(), + secret: process.env.WEBSSH_SESSION_SECRET || generateSecureSecret(), name: "webssh2.sid" } } diff --git a/app/connectionHandler.js b/app/connectionHandler.js index a04623d..378eef6 100644 --- a/app/connectionHandler.js +++ b/app/connectionHandler.js @@ -4,6 +4,7 @@ const fs = require("fs") const path = require("path") const { createNamespacedDebug } = require("./logger") +const { HTTP, MESSAGES, DEFAULTS } = require("./constants") const debug = createNamespacedDebug("connectionHandler") /** @@ -34,7 +35,9 @@ function handleFileRead(filePath, config, res) { // eslint-disable-next-line consistent-return fs.readFile(filePath, "utf8", function(err, data) { if (err) { - return res.status(500).send("Error loading client file") + return res + .status(HTTP.INTERNAL_SERVER_ERROR) + .send(MESSAGES.CLIENT_FILE_ERROR) } const modifiedHtml = modifyHtml(data, config) @@ -67,7 +70,7 @@ function handleConnection(req, res) { autoConnect: req.path.startsWith("/host/") // Automatically connect if path starts with /host/ } - const filePath = path.join(clientPath, "client.htm") + const filePath = path.join(clientPath, DEFAULTS.CLIENT_FILE) handleFileRead(filePath, tempConfig, res) } diff --git a/app/constants.js b/app/constants.js new file mode 100644 index 0000000..dd546ea --- /dev/null +++ b/app/constants.js @@ -0,0 +1,77 @@ +// server +// app/constants.js + +const crypto = require("crypto") +const path = require("path") + +/** + * Error messages + */ +const MESSAGES = { + INVALID_CREDENTIALS: "Invalid credentials format", + SSH_CONNECTION_ERROR: "SSH CONNECTION ERROR", + SHELL_ERROR: "SHELL ERROR", + CONFIG_ERROR: "CONFIG_ERROR", + UNEXPECTED_ERROR: "An unexpected error occurred", + EXPRESS_APP_CONFIG_ERROR: "Failed to configure Express app", + CLIENT_FILE_ERROR: "Error loading client file" +} + +/** + * Default values + */ +const DEFAULTS = { + SSH_PORT: 22, + LISTEN_PORT: 2222, + SSH_TERM: "xterm-color", + IO_PING_TIMEOUT: 60000, // 1 minute + IO_PING_INTERVAL: 25000, // 25 seconds + IO_PATH: "/ssh/socket.io", + WEBSSH2_CLIENT_PATH: path.resolve( + __dirname, + "..", + "node_modules", + "webssh2_client", + "client", + "public" + ), + CLIENT_FILE: "client.htm" +} + +/** + * Socket events + */ +const SOCKET_EVENTS = { + CONNECTION: "connection", + DISCONNECT: "disconnect", + AUTHENTICATE: "authenticate", + AUTHENTICATION: "authentication", + TERMINAL: "terminal", + DATA: "data", + RESIZE: "resize", + CONTROL: "control" +} + +/** + * HTTP Related + */ +const HTTP = { + OK: 200, + UNAUTHORIZED: 401, + INTERNAL_SERVER_ERROR: 500, + AUTHENTICATE: "WWW-Authenticate", + REALM: 'Basic realm="WebSSH2"', + AUTH_REQUIRED: "Authentication required.", + COOKIE: "basicauth", + PATH: "/ssh/host/", + SAMESITE: "Strict", + SESSION_SID: "webssh2_sid", + CREDS_CLEARED: "Credentials cleared." +} + +module.exports = { + MESSAGES, + DEFAULTS, + SOCKET_EVENTS, + HTTP +} diff --git a/app/errors.js b/app/errors.js index 1da58d8..0accbf4 100644 --- a/app/errors.js +++ b/app/errors.js @@ -3,6 +3,7 @@ const util = require("util") const { logError, createNamespacedDebug } = require("./logger") +const { HTTP, MESSAGES } = require("./constants") const debug = createNamespacedDebug("errors") @@ -25,7 +26,7 @@ util.inherits(WebSSH2Error, Error) * @param {string} message - The error message */ function ConfigError(message) { - WebSSH2Error.call(this, message, "CONFIG_ERROR") + WebSSH2Error.call(this, message, MESSAGES.CONFIG_ERROR) } util.inherits(ConfigError, WebSSH2Error) @@ -35,7 +36,7 @@ util.inherits(ConfigError, WebSSH2Error) * @param {string} message - The error message */ function SSHConnectionError(message) { - WebSSH2Error.call(this, message, "SSH_CONNECTION_ERROR") + WebSSH2Error.call(this, message, MESSAGES.SSH_CONNECTION_ERROR) } util.inherits(SSHConnectionError, WebSSH2Error) @@ -50,13 +51,17 @@ function handleError(err, res) { logError(err.message, err) debug(err.message) if (res) { - res.status(500).json({ error: err.message, code: err.code }) + res + .status(HTTP.INTERNAL_SERVER_ERROR) + .json({ error: err.message, code: err.code }) } } else { - logError("An unexpected error occurred", err) - debug("Unexpected error: %O", err) + logError(MESSAGES.UNEXPECTED_ERROR, err) + debug(`handleError: ${MESSAGES.UNEXPECTED_ERROR}: %O`, err) if (res) { - res.status(500).json({ error: "An unexpected error occurred" }) + res + .status(HTTP.INTERNAL_SERVER_ERROR) + .json({ error: MESSAGES.UNEXPECTED_ERROR }) } } } diff --git a/app/io.js b/app/io.js index 45c9bc2..58b1eaf 100644 --- a/app/io.js +++ b/app/io.js @@ -1,6 +1,7 @@ const socketIo = require("socket.io") const sharedsession = require("express-socket.io-session") const { createNamespacedDebug } = require("./logger") +const { DEFAULTS } = require("./constants") const debug = createNamespacedDebug("app") @@ -14,9 +15,9 @@ const debug = createNamespacedDebug("app") function configureSocketIO(server, sessionMiddleware, config) { const io = socketIo(server, { serveClient: false, - path: "/ssh/socket.io", - pingTimeout: 60000, // 1 minute - pingInterval: 25000, // 25 seconds + path: DEFAULTS.IO_PATH, + pingTimeout: DEFAULTS.IO_PING_TIMEOUT, + pingInterval: DEFAULTS.IO_PING_INTERVAL, cors: config.getCorsConfig() }) @@ -27,7 +28,7 @@ function configureSocketIO(server, sessionMiddleware, config) { }) ) - debug("Socket.IO configured") + debug("IO configured") return io } diff --git a/app/logger.js b/app/logger.js index 49fcb2e..9fe5c89 100644 --- a/app/logger.js +++ b/app/logger.js @@ -20,7 +20,7 @@ function createNamespacedDebug(namespace) { function logError(message, error) { console.error(message) if (error) { - console.error(`ERROR:\n\n ${error}`) + console.error(`ERROR: ${error}`) } } diff --git a/app/middleware.js b/app/middleware.js index c16bafc..9f32a29 100644 --- a/app/middleware.js +++ b/app/middleware.js @@ -6,6 +6,7 @@ const session = require("express-session") const bodyParser = require("body-parser") const debug = createDebug("webssh2:middleware") +const { HTTP } = require("./constants") /** * Creates and configures session middleware @@ -14,10 +15,10 @@ const debug = createDebug("webssh2:middleware") */ function createSessionMiddleware(config) { return session({ - secret: config.session.secret || "webssh2_secret", + secret: config.session.secret, resave: false, saveUninitialized: true, - name: config.session.name || "webssh2.sid" + name: config.session.name }) } @@ -40,10 +41,10 @@ function createCookieMiddleware() { host: req.session.sshCredentials.host, port: req.session.sshCredentials.port } - res.cookie("basicauth", JSON.stringify(cookieData), { + res.cookie(HTTP.COOKIE, JSON.stringify(cookieData), { httpOnly: false, - path: "/ssh/host/", - sameSite: "Strict" + path: HTTP.PATH, + sameSite: HTTP.SAMESITE }) } next() @@ -63,7 +64,7 @@ function applyMiddleware(app, config) { app.use(createBodyParserMiddleware()) app.use(createCookieMiddleware()) - debug("Middleware applied") + debug("applyMiddleware") return { sessionMiddleware } } diff --git a/app/routes.js b/app/routes.js index 28b9107..37e7c4a 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,5 +1,5 @@ // server -// /app/routes.js +// app/routes.js const express = require("express") const basicAuth = require("basic-auth") @@ -13,6 +13,7 @@ const { const handleConnection = require("./connectionHandler") const { createNamespacedDebug } = require("./logger") const { ConfigError, handleError } = require("./errors") +const { HTTP } = require("./constants") const debug = createNamespacedDebug("routes") const router = express.Router() @@ -22,8 +23,8 @@ function auth(req, res, next) { debug("auth: Basic Auth") const credentials = basicAuth(req) if (!credentials) { - res.setHeader("WWW-Authenticate", 'Basic realm="WebSSH2"') - return res.status(401).send("Authentication required.") + res.setHeader(HTTP.AUTHENTICATE, HTTP.REALM) + return res.status(HTTP.UNAUTHORIZED).send(HTTP.AUTH_REQUIRED) } // Validate and sanitize credentials req.session.sshCredentials = { @@ -75,12 +76,12 @@ router.get("/host/:host", auth, function(req, res) { // Clear credentials route router.get("/clear-credentials", function(req, res) { req.session.sshCredentials = null - res.status(200).send("Credentials cleared.") + res.status(HTTP.OK).send(HTTP.CREDENTIALS_CLEARED) }) router.get("/force-reconnect", function(req, res) { req.session.sshCredentials = null - res.status(401).send("Authentication required.") + res.status(HTTP.UNAUTHORIZED).send(HTTP.AUTH_REQUIRED) }) module.exports = router