chore: refactor SSHConnection as a dependency injection into socket

This commit is contained in:
Bill Church 2024-12-04 00:27:37 +00:00
parent a6e74bf29a
commit 6b1930da03
No known key found for this signature in database
3 changed files with 32 additions and 16 deletions

View file

@ -3,6 +3,7 @@
const express = require("express") const express = require("express")
const config = require("./config") const config = require("./config")
const SSHConnection = require("./ssh")
const socketHandler = require("./socket") const socketHandler = require("./socket")
const sshRoutes = require("./routes")(config) const sshRoutes = require("./routes")(config)
const { applyMiddleware } = require("./middleware") const { applyMiddleware } = require("./middleware")
@ -53,7 +54,7 @@ function initializeServer() {
const io = configureSocketIO(server, sessionMiddleware, config) const io = configureSocketIO(server, sessionMiddleware, config)
// Set up Socket.IO listeners // Set up Socket.IO listeners
socketHandler(io, config) socketHandler(io, config, SSHConnection)
// Start the server // Start the server
startServer(server, config) startServer(server, config)

View file

@ -3,11 +3,8 @@
const validator = require("validator") const validator = require("validator")
const EventEmitter = require("events") const EventEmitter = require("events")
const SSHConnection = require("./ssh")
const { createNamespacedDebug } = require("./logger") const { createNamespacedDebug } = require("./logger")
const { SSHConnectionError, handleError } = require("./errors") const { SSHConnectionError, handleError } = require("./errors")
const debug = createNamespacedDebug("socket")
const { const {
isValidCredentials, isValidCredentials,
maskSensitiveData, maskSensitiveData,
@ -15,12 +12,21 @@ const {
} = require("./utils") } = require("./utils")
const { MESSAGES } = require("./constants") const { MESSAGES } = require("./constants")
const debug = createNamespacedDebug("socket")
class WebSSH2Socket extends EventEmitter { class WebSSH2Socket extends EventEmitter {
constructor(socket, config) { /**
* Creates a new WebSSH2Socket instance
* @param {Object} socket - The Socket.IO socket instance
* @param {Object} config - The application configuration
* @param {Function} SSHConnectionClass - The SSH connection class constructor
*/
constructor(socket, config, SSHConnectionClass) {
super() super()
this.socket = socket this.socket = socket
this.config = config this.config = config
this.ssh = new SSHConnection(config) this.SSHConnectionClass = SSHConnectionClass
this.ssh = null
this.sessionState = { this.sessionState = {
authenticated: false, authenticated: false,
username: null, username: null,
@ -62,10 +68,6 @@ class WebSSH2Socket extends EventEmitter {
this.socket.emit("authentication", { action: "request_auth" }) this.socket.emit("authentication", { action: "request_auth" })
} }
this.ssh.on("keyboard-interactive", data => {
this.handleKeyboardInteractive(data)
})
this.socket.on("authenticate", creds => { this.socket.on("authenticate", creds => {
this.handleAuthenticate(creds) this.handleAuthenticate(creds)
}) })
@ -112,7 +114,6 @@ class WebSSH2Socket extends EventEmitter {
debug(`handleAuthenticate: ${this.socket.id}, %O`, maskSensitiveData(creds)) debug(`handleAuthenticate: ${this.socket.id}, %O`, maskSensitiveData(creds))
if (isValidCredentials(creds)) { if (isValidCredentials(creds)) {
// Set term if provided, otherwise use config default
this.sessionState.term = validateSshTerm(creds.term) this.sessionState.term = validateSshTerm(creds.term)
? creds.term ? creds.term
: this.config.ssh.term : this.config.ssh.term
@ -135,9 +136,18 @@ class WebSSH2Socket extends EventEmitter {
// Add private key from config if available and not provided in creds // Add private key from config if available and not provided in creds
if (this.config.user.privateKey && !creds.privateKey) { if (this.config.user.privateKey && !creds.privateKey) {
// eslint-disable-next-line no-param-reassign
creds.privateKey = this.config.user.privateKey creds.privateKey = this.config.user.privateKey
} }
// Create new SSH connection instance
this.ssh = new this.SSHConnectionClass(this.config)
// Set up SSH event handlers
this.ssh.on("keyboard-interactive", data => {
this.handleKeyboardInteractive(data)
})
this.ssh this.ssh
.connect(creds) .connect(creds)
.then(() => { .then(() => {
@ -170,7 +180,7 @@ class WebSSH2Socket extends EventEmitter {
this.socket.emit("getTerminal", true) this.socket.emit("getTerminal", true)
}) })
.catch((err) => { .catch(err => {
debug( debug(
`initializeConnection: SSH CONNECTION ERROR: ${this.socket.id}, Host: ${creds.host}, Error: ${err.message}` `initializeConnection: SSH CONNECTION ERROR: ${this.socket.id}, Host: ${creds.host}, Error: ${err.message}`
) )
@ -217,8 +227,8 @@ class WebSSH2Socket extends EventEmitter {
}, },
envVars envVars
) )
.then((stream) => { .then(stream => {
stream.on("data", (data) => { stream.on("data", data => {
this.socket.emit("data", data.toString("utf-8")) this.socket.emit("data", data.toString("utf-8"))
}) })
// stream.stderr.on("data", data => debug(`STDERR: ${data}`)) // needed for shell.exec // stream.stderr.on("data", data => debug(`STDERR: ${data}`)) // needed for shell.exec
@ -353,6 +363,10 @@ class WebSSH2Socket extends EventEmitter {
} }
} }
module.exports = function(io, config) { // Modified export to include dependency injection
io.on("connection", socket => new WebSSH2Socket(socket, config)) module.exports = function(io, config, SSHConnectionClass) {
io.on(
"connection",
socket => new WebSSH2Socket(socket, config, SSHConnectionClass)
)
} }

View file

@ -239,6 +239,7 @@ function parseEnvVars(envString) {
for (let i = 0; i < pairs.length; i += 1) { for (let i = 0; i < pairs.length; i += 1) {
const pair = pairs[i].split(":") const pair = pairs[i].split(":")
// eslint-disable-next-line no-continue
if (pair.length !== 2) continue if (pair.length !== 2) continue
const key = pair[0].trim() const key = pair[0].trim()