chore: implement constants

This commit is contained in:
Bill Church 2024-08-21 17:42:22 +00:00
parent 13fd49e008
commit 5a65b6e91d
No known key found for this signature in database
9 changed files with 122 additions and 38 deletions

View file

@ -2,7 +2,6 @@
// app/app.js // app/app.js
const express = require("express") const express = require("express")
const path = require("path")
const config = require("./config") const config = require("./config")
const socketHandler = require("./socket") const socketHandler = require("./socket")
const sshRoutes = require("./routes") const sshRoutes = require("./routes")
@ -11,6 +10,7 @@ const { createServer, startServer } = require("./server")
const { configureSocketIO } = require("./io") const { configureSocketIO } = require("./io")
const { handleError, ConfigError } = require("./errors") const { handleError, ConfigError } = require("./errors")
const { createNamespacedDebug } = require("./logger") const { createNamespacedDebug } = require("./logger")
const { DEFAULTS, MESSAGES } = require("./constants")
const debug = createNamespacedDebug("app") const debug = createNamespacedDebug("app")
@ -23,14 +23,7 @@ function createApp() {
try { 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 = DEFAULTS.WEBSSH2_CLIENT_PATH
__dirname,
"..",
"node_modules",
"webssh2_client",
"client",
"public"
)
// Apply middleware // Apply middleware
const { sessionMiddleware } = applyMiddleware(app, config) const { sessionMiddleware } = applyMiddleware(app, config)
@ -43,7 +36,9 @@ function createApp() {
return { app: app, sessionMiddleware: sessionMiddleware } return { app: app, sessionMiddleware: sessionMiddleware }
} catch (err) { } catch (err) {
throw new ConfigError(`Failed to configure Express app: ${err.message}`) throw new ConfigError(
`${MESSAGES.EXPRESS_APP_CONFIG_ERROR}: ${err.message}`
)
} }
} }

View file

@ -5,13 +5,14 @@ const Ajv = require("ajv")
const { deepMerge, generateSecureSecret } = require("./utils") const { deepMerge, generateSecureSecret } = require("./utils")
const { createNamespacedDebug } = require("./logger") const { createNamespacedDebug } = require("./logger")
const { ConfigError, handleError } = require("./errors") const { ConfigError, handleError } = require("./errors")
const { DEFAULTS } = require("./constants")
const debug = createNamespacedDebug("config") const debug = createNamespacedDebug("config")
const defaultConfig = { const defaultConfig = {
listen: { listen: {
ip: "0.0.0.0", ip: "0.0.0.0",
port: 2222 port: DEFAULTS.LISTEN_PORT
}, },
http: { http: {
origins: ["*:*"] origins: ["*:*"]
@ -22,8 +23,8 @@ const defaultConfig = {
}, },
ssh: { ssh: {
host: null, host: null,
port: 22, port: DEFAULTS.SSH_PORT,
term: "xterm-color", term: DEFAULTS.SSH_TERM,
readyTimeout: 20000, readyTimeout: 20000,
keepaliveInterval: 120000, keepaliveInterval: 120000,
keepaliveCountMax: 10 keepaliveCountMax: 10
@ -61,7 +62,7 @@ const defaultConfig = {
compress: ["none", "zlib@openssh.com", "zlib"] compress: ["none", "zlib@openssh.com", "zlib"]
}, },
session: { session: {
secret: generateSecureSecret(), secret: process.env.WEBSSH_SESSION_SECRET || generateSecureSecret(),
name: "webssh2.sid" name: "webssh2.sid"
} }
} }

View file

@ -4,6 +4,7 @@
const fs = require("fs") const fs = require("fs")
const path = require("path") const path = require("path")
const { createNamespacedDebug } = require("./logger") const { createNamespacedDebug } = require("./logger")
const { HTTP, MESSAGES, DEFAULTS } = require("./constants")
const debug = createNamespacedDebug("connectionHandler") const debug = createNamespacedDebug("connectionHandler")
/** /**
@ -34,7 +35,9 @@ function handleFileRead(filePath, config, res) {
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
fs.readFile(filePath, "utf8", function(err, data) { fs.readFile(filePath, "utf8", function(err, data) {
if (err) { 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) 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/ 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) handleFileRead(filePath, tempConfig, res)
} }

77
app/constants.js Normal file
View file

@ -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
}

View file

@ -3,6 +3,7 @@
const util = require("util") const util = require("util")
const { logError, createNamespacedDebug } = require("./logger") const { logError, createNamespacedDebug } = require("./logger")
const { HTTP, MESSAGES } = require("./constants")
const debug = createNamespacedDebug("errors") const debug = createNamespacedDebug("errors")
@ -25,7 +26,7 @@ util.inherits(WebSSH2Error, Error)
* @param {string} message - The error message * @param {string} message - The error message
*/ */
function ConfigError(message) { function ConfigError(message) {
WebSSH2Error.call(this, message, "CONFIG_ERROR") WebSSH2Error.call(this, message, MESSAGES.CONFIG_ERROR)
} }
util.inherits(ConfigError, WebSSH2Error) util.inherits(ConfigError, WebSSH2Error)
@ -35,7 +36,7 @@ util.inherits(ConfigError, WebSSH2Error)
* @param {string} message - The error message * @param {string} message - The error message
*/ */
function SSHConnectionError(message) { function SSHConnectionError(message) {
WebSSH2Error.call(this, message, "SSH_CONNECTION_ERROR") WebSSH2Error.call(this, message, MESSAGES.SSH_CONNECTION_ERROR)
} }
util.inherits(SSHConnectionError, WebSSH2Error) util.inherits(SSHConnectionError, WebSSH2Error)
@ -50,13 +51,17 @@ function handleError(err, res) {
logError(err.message, err) logError(err.message, err)
debug(err.message) debug(err.message)
if (res) { 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 { } else {
logError("An unexpected error occurred", err) logError(MESSAGES.UNEXPECTED_ERROR, err)
debug("Unexpected error: %O", err) debug(`handleError: ${MESSAGES.UNEXPECTED_ERROR}: %O`, err)
if (res) { if (res) {
res.status(500).json({ error: "An unexpected error occurred" }) res
.status(HTTP.INTERNAL_SERVER_ERROR)
.json({ error: MESSAGES.UNEXPECTED_ERROR })
} }
} }
} }

View file

@ -1,6 +1,7 @@
const socketIo = require("socket.io") const socketIo = require("socket.io")
const sharedsession = require("express-socket.io-session") const sharedsession = require("express-socket.io-session")
const { createNamespacedDebug } = require("./logger") const { createNamespacedDebug } = require("./logger")
const { DEFAULTS } = require("./constants")
const debug = createNamespacedDebug("app") const debug = createNamespacedDebug("app")
@ -14,9 +15,9 @@ const debug = createNamespacedDebug("app")
function configureSocketIO(server, sessionMiddleware, config) { function configureSocketIO(server, sessionMiddleware, config) {
const io = socketIo(server, { const io = socketIo(server, {
serveClient: false, serveClient: false,
path: "/ssh/socket.io", path: DEFAULTS.IO_PATH,
pingTimeout: 60000, // 1 minute pingTimeout: DEFAULTS.IO_PING_TIMEOUT,
pingInterval: 25000, // 25 seconds pingInterval: DEFAULTS.IO_PING_INTERVAL,
cors: config.getCorsConfig() cors: config.getCorsConfig()
}) })
@ -27,7 +28,7 @@ function configureSocketIO(server, sessionMiddleware, config) {
}) })
) )
debug("Socket.IO configured") debug("IO configured")
return io return io
} }

View file

@ -20,7 +20,7 @@ function createNamespacedDebug(namespace) {
function logError(message, error) { function logError(message, error) {
console.error(message) console.error(message)
if (error) { if (error) {
console.error(`ERROR:\n\n ${error}`) console.error(`ERROR: ${error}`)
} }
} }

View file

@ -6,6 +6,7 @@ const session = require("express-session")
const bodyParser = require("body-parser") const bodyParser = require("body-parser")
const debug = createDebug("webssh2:middleware") const debug = createDebug("webssh2:middleware")
const { HTTP } = require("./constants")
/** /**
* Creates and configures session middleware * Creates and configures session middleware
@ -14,10 +15,10 @@ const debug = createDebug("webssh2:middleware")
*/ */
function createSessionMiddleware(config) { function createSessionMiddleware(config) {
return session({ return session({
secret: config.session.secret || "webssh2_secret", secret: config.session.secret,
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: true,
name: config.session.name || "webssh2.sid" name: config.session.name
}) })
} }
@ -40,10 +41,10 @@ function createCookieMiddleware() {
host: req.session.sshCredentials.host, host: req.session.sshCredentials.host,
port: req.session.sshCredentials.port port: req.session.sshCredentials.port
} }
res.cookie("basicauth", JSON.stringify(cookieData), { res.cookie(HTTP.COOKIE, JSON.stringify(cookieData), {
httpOnly: false, httpOnly: false,
path: "/ssh/host/", path: HTTP.PATH,
sameSite: "Strict" sameSite: HTTP.SAMESITE
}) })
} }
next() next()
@ -63,7 +64,7 @@ function applyMiddleware(app, config) {
app.use(createBodyParserMiddleware()) app.use(createBodyParserMiddleware())
app.use(createCookieMiddleware()) app.use(createCookieMiddleware())
debug("Middleware applied") debug("applyMiddleware")
return { sessionMiddleware } return { sessionMiddleware }
} }

View file

@ -1,5 +1,5 @@
// server // server
// /app/routes.js // app/routes.js
const express = require("express") const express = require("express")
const basicAuth = require("basic-auth") const basicAuth = require("basic-auth")
@ -13,6 +13,7 @@ const {
const handleConnection = require("./connectionHandler") const handleConnection = require("./connectionHandler")
const { createNamespacedDebug } = require("./logger") const { createNamespacedDebug } = require("./logger")
const { ConfigError, handleError } = require("./errors") const { ConfigError, handleError } = require("./errors")
const { HTTP } = require("./constants")
const debug = createNamespacedDebug("routes") const debug = createNamespacedDebug("routes")
const router = express.Router() const router = express.Router()
@ -22,8 +23,8 @@ function auth(req, res, next) {
debug("auth: Basic Auth") debug("auth: Basic Auth")
const credentials = basicAuth(req) const credentials = basicAuth(req)
if (!credentials) { if (!credentials) {
res.setHeader("WWW-Authenticate", 'Basic realm="WebSSH2"') res.setHeader(HTTP.AUTHENTICATE, HTTP.REALM)
return res.status(401).send("Authentication required.") return res.status(HTTP.UNAUTHORIZED).send(HTTP.AUTH_REQUIRED)
} }
// Validate and sanitize credentials // Validate and sanitize credentials
req.session.sshCredentials = { req.session.sshCredentials = {
@ -75,12 +76,12 @@ router.get("/host/:host", auth, function(req, res) {
// Clear credentials route // Clear credentials route
router.get("/clear-credentials", function(req, res) { router.get("/clear-credentials", function(req, res) {
req.session.sshCredentials = null 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) { router.get("/force-reconnect", function(req, res) {
req.session.sshCredentials = null req.session.sshCredentials = null
res.status(401).send("Authentication required.") res.status(HTTP.UNAUTHORIZED).send(HTTP.AUTH_REQUIRED)
}) })
module.exports = router module.exports = router