chore: refactor debugging, logging and error handling.
This commit is contained in:
parent
e30c0c1c9b
commit
66ce643dd9
13 changed files with 450 additions and 309 deletions
160
app/app.js
160
app/app.js
|
@ -1,149 +1,75 @@
|
||||||
// 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()
|
||||||
|
|
||||||
// Resolve the correct path to the webssh2_client module
|
try {
|
||||||
const clientPath = path.resolve(
|
// Resolve the correct path to the webssh2_client module
|
||||||
__dirname,
|
const clientPath = path.resolve(
|
||||||
"..",
|
__dirname,
|
||||||
"node_modules",
|
"..",
|
||||||
"webssh2_client",
|
"node_modules",
|
||||||
"client",
|
"webssh2_client",
|
||||||
"public"
|
"client",
|
||||||
)
|
"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
|
// Serve static files from the webssh2_client module with a custom prefix
|
||||||
app.use(bodyParser.urlencoded({ extended: true }))
|
app.use("/ssh/assets", express.static(clientPath))
|
||||||
app.use(bodyParser.json())
|
|
||||||
|
|
||||||
// Add cookie-setting middleware
|
// Use the SSH routes
|
||||||
app.use((req, res, next) => {
|
app.use("/ssh", sshRoutes)
|
||||||
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
|
return { app: app, sessionMiddleware: sessionMiddleware }
|
||||||
app.use("/ssh/assets", express.static(clientPath))
|
} catch (err) {
|
||||||
|
throw new ConfigError(`Failed to configure Express app: ${err.message}`)
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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() {
|
||||||
const { app, sessionMiddleware } = createApp()
|
try {
|
||||||
const server = createServer(app)
|
const { app, sessionMiddleware } = createApp()
|
||||||
const io = configureSocketIO(server, sessionMiddleware)
|
const server = createServer(app)
|
||||||
|
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 }
|
|
||||||
|
|
159
app/config.js
159
app/config.js
|
@ -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
|
||||||
|
|
|
@ -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
69
app/errors.js
Normal 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
35
app/io.js
Normal 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
30
app/logger.js
Normal 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
76
app/middleware.js
Normal 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
|
||||||
|
}
|
|
@ -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,32 +40,36 @@ 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`)
|
||||||
|
|
||||||
const host = getValidatedHost(req.params.host)
|
try {
|
||||||
const port = getValidatedPort(req.query.port)
|
const host = getValidatedHost(req.params.host)
|
||||||
|
const port = getValidatedPort(req.query.port)
|
||||||
|
|
||||||
// Validate and sanitize sshterm parameter if it exists
|
// Validate and sanitize sshterm parameter if it exists
|
||||||
const sshterm = validateSshTerm(req.query.sshterm)
|
const sshterm = validateSshTerm(req.query.sshterm)
|
||||||
|
|
||||||
req.session.sshCredentials = req.session.sshCredentials || {}
|
req.session.sshCredentials = req.session.sshCredentials || {}
|
||||||
req.session.sshCredentials.host = host
|
req.session.sshCredentials.host = host
|
||||||
req.session.sshCredentials.port = port
|
req.session.sshCredentials.port = port
|
||||||
if (req.query.sshterm) {
|
if (req.query.sshterm) {
|
||||||
req.session.sshCredentials.term = 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
|
// Clear credentials route
|
||||||
|
|
37
app/server.js
Normal file
37
app/server.js
Normal 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 }
|
110
app/socket.js
110
app/socket.js
|
@ -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,40 +91,47 @@ class WebSSH2Socket {
|
||||||
|
|
||||||
this.ssh
|
this.ssh
|
||||||
.connect(creds)
|
.connect(creds)
|
||||||
.then(() => {
|
.then(
|
||||||
this.sessionState = Object.assign({}, this.sessionState, {
|
function() {
|
||||||
authenticated: true,
|
this.sessionState = Object.assign({}, this.sessionState, {
|
||||||
username: creds.username,
|
authenticated: true,
|
||||||
password: creds.password,
|
username: creds.username,
|
||||||
host: creds.host,
|
password: creds.password,
|
||||||
port: creds.port
|
host: creds.host,
|
||||||
})
|
port: creds.port
|
||||||
|
})
|
||||||
|
|
||||||
const authResult = { action: "auth_result", success: true }
|
const authResult = { action: "auth_result", success: true }
|
||||||
this.socket.emit("authentication", authResult)
|
this.socket.emit("authentication", authResult)
|
||||||
|
|
||||||
const permissions = {
|
const permissions = {
|
||||||
autoLog: this.config.options.autoLog || false,
|
autoLog: this.config.options.autoLog || false,
|
||||||
allowReplay: this.config.options.allowReplay || false,
|
allowReplay: this.config.options.allowReplay || false,
|
||||||
allowReconnect: this.config.options.allowReconnect || false,
|
allowReconnect: this.config.options.allowReconnect || false,
|
||||||
allowReauth: this.config.options.allowReauth || false
|
allowReauth: this.config.options.allowReauth || false
|
||||||
}
|
}
|
||||||
this.socket.emit("permissions", permissions)
|
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) {
|
if (this.config.header && this.config.header.text !== null) {
|
||||||
this.updateElement("header", this.config.header.text)
|
this.updateElement("header", this.config.header.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.socket.emit("getTerminal", true)
|
this.socket.emit("getTerminal", true)
|
||||||
})
|
}.bind(this)
|
||||||
.catch(err => {
|
)
|
||||||
console.error(
|
.catch(
|
||||||
`initializeConnection: SSH CONNECTION ERROR: ${this.socket.id}, Host: ${creds.host}, Error: ${err.message}`
|
function(err) {
|
||||||
)
|
debug(
|
||||||
this.handleError("SSH CONNECTION ERROR", err)
|
`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) {
|
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()
|
||||||
}
|
}
|
||||||
|
|
14
app/ssh.js
14
app/ssh.js
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
9
index.js
9
index.js
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue