chore: linting

This commit is contained in:
Bill Church 2024-08-21 12:25:32 +00:00
parent da21c89f20
commit d387cb796d
No known key found for this signature in database
4 changed files with 175 additions and 100 deletions

View file

@ -1,13 +1,55 @@
// server // server
// app/connectionHandler.js // app/connectionHandler.js
const createDebug = require("debug") const createDebug = require("debug")
const path = require("path")
const fs = require("fs") const fs = require("fs")
const path = require("path")
const debug = createDebug("webssh2:connectionHandler") const debug = createDebug("webssh2:connectionHandler")
/**
* Modify the HTML content by replacing certain placeholders with dynamic values.
* @param {string} html - The original HTML content.
* @param {Object} config - The configuration object to inject into the HTML.
* @returns {string} - The modified HTML content.
*/
function modifyHtml(html, config) {
const modifiedHtml = html.replace(
/(src|href)="(?!http|\/\/)/g,
'$1="/ssh/assets/'
)
return modifiedHtml.replace(
"window.webssh2Config = null;",
`window.webssh2Config = ${JSON.stringify(config)};`
)
}
/**
* Handle reading the file and processing the response.
* @param {string} filePath - The path to the HTML file.
* @param {Object} config - The configuration object to inject into the HTML.
* @param {Object} res - The Express response object.
*/
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")
}
const modifiedHtml = modifyHtml(data, config)
res.send(modifiedHtml)
})
}
/**
* Handle the connection request and send the modified client HTML.
* @param {Object} req - The Express request object.
* @param {Object} res - The Express response object.
*/
function handleConnection(req, res) { function handleConnection(req, res) {
debug("Handling connection") debug("Handling connection req.path:", req.path)
const clientPath = path.resolve( const clientPath = path.resolve(
__dirname, __dirname,
@ -23,33 +65,11 @@ function handleConnection(req, res) {
url: `${req.protocol}://${req.get("host")}`, url: `${req.protocol}://${req.get("host")}`,
path: "/ssh/socket.io" path: "/ssh/socket.io"
}, },
autoConnect: false // Default to false autoConnect: req.path.startsWith("/host/") // Automatically connect if path starts with /host/
} }
// Check if the current route is /host/:host const filePath = path.join(clientPath, "client.htm")
debug("handleConnection req.path:", req.path) handleFileRead(filePath, tempConfig, res)
if (req.path.startsWith("/host/")) {
tempConfig.autoConnect = true
}
fs.readFile(path.join(clientPath, "client.htm"), "utf8", function(err, data) {
if (err) {
return res.status(500).send("Error loading client file")
}
let modifiedHtml = data.replace(
/(src|href)="(?!http|\/\/)/g,
'$1="/ssh/assets/'
)
modifiedHtml = modifiedHtml.replace(
"window.webssh2Config = null;",
`window.webssh2Config = ${JSON.stringify(tempConfig)};`
)
res.send(modifiedHtml)
// Explicitly return to satisfy the linter
})
} }
module.exports = handleConnection module.exports = handleConnection

View file

@ -9,9 +9,14 @@ 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")
const { validateSshTerm } = require("./utils") const {
getValidatedHost,
getValidatedPort,
validateSshTerm
} = require("./utils")
const handleConnection = require("./connectionHandler") const handleConnection = require("./connectionHandler")
// eslint-disable-next-line consistent-return
function auth(req, res, next) { function auth(req, res, next) {
debug("auth: Basic Auth") debug("auth: Basic Auth")
const credentials = basicAuth(req) const credentials = basicAuth(req)
@ -30,38 +35,26 @@ function auth(req, res, next) {
// Scenario 1: No auth required, uses websocket authentication instead // Scenario 1: No auth required, uses websocket authentication instead
router.get("/", function(req, res) { router.get("/", function(req, res) {
debug("Accessed / route") debug("router.get./: Accessed / route")
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(`Accessed /ssh/host/${req.params.host} route`) debug(`router.get.host: /ssh/host/${req.params.host} route`)
// Validate and sanitize host parameter const host = getValidatedHost(req.params.host)
const host = validator.isIP(req.params.host) const port = getValidatedPort(req.query.port)
? req.params.host
: validator.escape(req.params.host)
// Validate and sanitize port parameter if it exists // Validate and sanitize sshterm parameter if it exists
const port = req.query.port const sshterm = validateSshTerm(req.query.sshterm)
? validator.isPort(req.query.port)
? parseInt(req.query.port, 10)
: 22
: 22 // Default to 22 if port is not provided
// Validate and sanitize sshTerm parameter if it exists
const sshTerm = req.query.sshTerm
? validateSshTerm(req.query.sshTerm)
? req.query.sshTerm
: null
: null // Default to 'xterm-color' if sshTerm is not provided
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 req.session.usedBasicAuth = true

View file

@ -7,7 +7,7 @@ const debug = createDebug("webssh2:socket")
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 { validateSshTerm } = require("./utils") const { validateSshTerm, isValidCredentials } = require("./utils")
module.exports = function(io, config) { module.exports = function(io, config) {
io.on("connection", function(socket) { io.on("connection", function(socket) {
@ -79,15 +79,15 @@ module.exports = function(io, config) {
`initializeConnection: ${socket.id} conn.on ready: ${creds.username}@${creds.host}:${creds.port} successfully connected` `initializeConnection: ${socket.id} conn.on ready: ${creds.username}@${creds.host}:${creds.port} successfully connected`
) )
const auth_result = { action: "auth_result", success: true } const authResult = { action: "auth_result", success: true }
debug( debug(
`initializeConnection: ${ `initializeConnection: ${
socket.id socket.id
} conn.on ready: emitting authentication: ${JSON.stringify( } conn.on ready: emitting authentication: ${JSON.stringify(
auth_result authResult
)}` )}`
) )
socket.emit("authentication", auth_result) socket.emit("authentication", authResult)
// Emit consolidated permissions // Emit consolidated permissions
const permissions = { const permissions = {
@ -189,7 +189,7 @@ module.exports = function(io, config) {
}) })
stream.on("close", function(code, signal) { stream.on("close", function(code, signal) {
debug(`handleStreamClose: ${socket.id}`) debug(`handleStreamClose: ${socket.id}: ${code}, ${signal}`)
handleConnectionClose() handleConnectionClose()
}) })
@ -339,15 +339,20 @@ module.exports = function(io, config) {
debug( debug(
`clearSessionCredentials: Clearing session credentials for ${socket.id}` `clearSessionCredentials: Clearing session credentials for ${socket.id}`
) )
if (socket.handshake.session.sshCredentials) {
socket.handshake.session.sshCredentials.username = null const { session } = socket.handshake
socket.handshake.session.sshCredentials.password = null
if (session.sshCredentials) {
session.sshCredentials.username = null
session.sshCredentials.password = null
} }
socket.handshake.session.usedBasicAuth = false
session.usedBasicAuth = false
sessionState.authenticated = false sessionState.authenticated = false
sessionState.username = null sessionState.username = null
sessionState.password = null sessionState.password = null
socket.handshake.session.save(function(err) {
session.save(function(err) {
if (err) { if (err) {
console.error(`Failed to save session for ${socket.id}:`, err) console.error(`Failed to save session for ${socket.id}:`, err)
} }
@ -376,23 +381,3 @@ module.exports = function(io, config) {
socket.on("disconnect", handleConnectionClose) socket.on("disconnect", handleConnectionClose)
}) })
} }
/**
* Checks if the provided credentials object is valid.
*
* @param {Object} creds - The credentials object.
* @param {string} creds.username - The username.
* @param {string} creds.password - The password.
* @param {string} creds.host - The host.
* @param {number} creds.port - The port.
* @returns {boolean} - Returns true if the credentials are valid, otherwise false.
*/
function isValidCredentials(creds) {
return (
creds &&
typeof creds.username === "string" &&
typeof creds.password === "string" &&
typeof creds.host === "string" &&
typeof creds.port === "number"
)
}

View file

@ -6,25 +6,6 @@ const crypto = require("crypto")
const debug = createDebug("webssh2:utils") const debug = createDebug("webssh2:utils")
/**
* Validates the SSH terminal name using validator functions.
* Allows alphanumeric characters, hyphens, and periods.
* @param {string} term - The terminal name to validate
* @returns {boolean} True if the terminal name is valid, false otherwise
*/
function validateSshTerm(term) {
debug(`validateSshTerm: %O`, term)
if (term === undefined || term === null) {
return false
}
return (
validator.isLength(term, { min: 1, max: 30 }) &&
validator.matches(term, /^[a-zA-Z0-9.-]+$/)
)
}
exports.validateSshTerm = validateSshTerm
/** /**
* Deep merges two objects * Deep merges two objects
* @param {Object} target - The target object to merge into * @param {Object} target - The target object to merge into
@ -48,7 +29,7 @@ function deepMerge(target, source) {
}) })
return output return output
} }
exports.deepMerge = deepMerge
/** /**
* Generates a secure random session secret * Generates a secure random session secret
* @returns {string} A random 32-byte hex string * @returns {string} A random 32-byte hex string
@ -56,4 +37,100 @@ exports.deepMerge = deepMerge
function generateSecureSecret() { function generateSecureSecret() {
return crypto.randomBytes(32).toString("hex") return crypto.randomBytes(32).toString("hex")
} }
exports.generateSecureSecret = generateSecureSecret
/**
* Determines if a given host is an IP address or a hostname.
* If it's a hostname, it escapes it for safety.
*
* @param {string} host - The host string to validate and escape.
* @returns {string} - The original IP or escaped hostname.
*/
function getValidatedHost(host) {
let validatedHost
if (validator.isIP(host)) {
validatedHost = host
} else {
validatedHost = validator.escape(host)
}
return validatedHost
}
/**
* Validates and sanitizes a port value.
* If no port is provided, defaults to port 22.
* If a port is provided, checks if it is a valid port number (1-65535).
* If the port is invalid, defaults to port 22.
*
* @param {string} [portInput] - The port string to validate and parse.
* @returns {number} - The validated port number.
*/
function getValidatedPort(portInput) {
const defaultPort = 22
const port = defaultPort
debug("getValidatedPort: input: %O", portInput)
if (portInput) {
if (validator.isInt(portInput, { min: 1, max: 65535 })) {
return parseInt(portInput, 10)
}
}
debug(
"getValidatedPort: port not specified or is invalid, setting port to: %O",
port
)
return port
}
/**
* Checks if the provided credentials object is valid.
*
* @param {Object} creds - The credentials object.
* @param {string} creds.username - The username.
* @param {string} creds.password - The password.
* @param {string} creds.host - The host.
* @param {number} creds.port - The port.
* @returns {boolean} - Returns true if the credentials are valid, otherwise false.
*/
function isValidCredentials(creds) {
return (
creds &&
typeof creds.username === "string" &&
typeof creds.password === "string" &&
typeof creds.host === "string" &&
typeof creds.port === "number"
)
}
/**
* Validates and sanitizes the SSH terminal name using validator functions.
* Allows alphanumeric characters, hyphens, and periods.
* Returns null if the terminal name is invalid or not provided.
*
* @param {string} [term] - The terminal name to validate.
* @returns {string|null} - The sanitized terminal name if valid, null otherwise.
*/
function validateSshTerm(term) {
debug(`validateSshTerm: %O`, term)
if (!term) {
return null
}
const validatedSshTerm =
validator.isLength(term, { min: 1, max: 30 }) &&
validator.matches(term, /^[a-zA-Z0-9.-]+$/)
return validatedSshTerm ? term : null
}
module.exports = {
deepMerge,
generateSecureSecret,
getValidatedHost,
getValidatedPort,
isValidCredentials,
validateSshTerm
}