chore: linting
This commit is contained in:
parent
da21c89f20
commit
d387cb796d
4 changed files with 175 additions and 100 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
119
app/utils.js
119
app/utils.js
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue