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
|
||||
// app/connectionHandler.js
|
||||
|
||||
const createDebug = require("debug")
|
||||
const path = require("path")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
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) {
|
||||
debug("Handling connection")
|
||||
debug("Handling connection req.path:", req.path)
|
||||
|
||||
const clientPath = path.resolve(
|
||||
__dirname,
|
||||
|
@ -23,33 +65,11 @@ function handleConnection(req, res) {
|
|||
url: `${req.protocol}://${req.get("host")}`,
|
||||
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
|
||||
debug("handleConnection req.path:", req.path)
|
||||
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
|
||||
})
|
||||
const filePath = path.join(clientPath, "client.htm")
|
||||
handleFileRead(filePath, tempConfig, res)
|
||||
}
|
||||
|
||||
module.exports = handleConnection
|
||||
|
|
|
@ -9,9 +9,14 @@ const router = express.Router()
|
|||
const basicAuth = require("basic-auth")
|
||||
const maskObject = require("jsmasker")
|
||||
const validator = require("validator")
|
||||
const { validateSshTerm } = require("./utils")
|
||||
const {
|
||||
getValidatedHost,
|
||||
getValidatedPort,
|
||||
validateSshTerm
|
||||
} = require("./utils")
|
||||
const handleConnection = require("./connectionHandler")
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
function auth(req, res, next) {
|
||||
debug("auth: Basic Auth")
|
||||
const credentials = basicAuth(req)
|
||||
|
@ -30,38 +35,26 @@ function auth(req, res, next) {
|
|||
|
||||
// Scenario 1: No auth required, uses websocket authentication instead
|
||||
router.get("/", function(req, res) {
|
||||
debug("Accessed / route")
|
||||
debug("router.get./: Accessed / route")
|
||||
handleConnection(req, res)
|
||||
})
|
||||
|
||||
// Scenario 2: Auth required, uses HTTP Basic Auth
|
||||
// Scenario 2: Auth required, uses HTTP Basic Auth
|
||||
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 = validator.isIP(req.params.host)
|
||||
? req.params.host
|
||||
: validator.escape(req.params.host)
|
||||
const host = getValidatedHost(req.params.host)
|
||||
const port = getValidatedPort(req.query.port)
|
||||
|
||||
// Validate and sanitize port parameter if it exists
|
||||
const port = req.query.port
|
||||
? 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
|
||||
// Validate and sanitize sshterm parameter if it exists
|
||||
const sshterm = validateSshTerm(req.query.sshterm)
|
||||
|
||||
req.session.sshCredentials = req.session.sshCredentials || {}
|
||||
req.session.sshCredentials.host = host
|
||||
req.session.sshCredentials.port = port
|
||||
if (req.query.sshTerm) {
|
||||
req.session.sshCredentials.term = sshTerm
|
||||
if (req.query.sshterm) {
|
||||
req.session.sshCredentials.term = sshterm
|
||||
}
|
||||
req.session.usedBasicAuth = true
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ const debug = createDebug("webssh2:socket")
|
|||
const maskObject = require("jsmasker")
|
||||
const validator = require("validator")
|
||||
const SSHConnection = require("./ssh")
|
||||
const { validateSshTerm } = require("./utils")
|
||||
const { validateSshTerm, isValidCredentials } = require("./utils")
|
||||
|
||||
module.exports = function(io, config) {
|
||||
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`
|
||||
)
|
||||
|
||||
const auth_result = { action: "auth_result", success: true }
|
||||
const authResult = { action: "auth_result", success: true }
|
||||
debug(
|
||||
`initializeConnection: ${
|
||||
socket.id
|
||||
} conn.on ready: emitting authentication: ${JSON.stringify(
|
||||
auth_result
|
||||
authResult
|
||||
)}`
|
||||
)
|
||||
socket.emit("authentication", auth_result)
|
||||
socket.emit("authentication", authResult)
|
||||
|
||||
// Emit consolidated permissions
|
||||
const permissions = {
|
||||
|
@ -189,7 +189,7 @@ module.exports = function(io, config) {
|
|||
})
|
||||
|
||||
stream.on("close", function(code, signal) {
|
||||
debug(`handleStreamClose: ${socket.id}`)
|
||||
debug(`handleStreamClose: ${socket.id}: ${code}, ${signal}`)
|
||||
handleConnectionClose()
|
||||
})
|
||||
|
||||
|
@ -339,15 +339,20 @@ module.exports = function(io, config) {
|
|||
debug(
|
||||
`clearSessionCredentials: Clearing session credentials for ${socket.id}`
|
||||
)
|
||||
if (socket.handshake.session.sshCredentials) {
|
||||
socket.handshake.session.sshCredentials.username = null
|
||||
socket.handshake.session.sshCredentials.password = null
|
||||
|
||||
const { session } = socket.handshake
|
||||
|
||||
if (session.sshCredentials) {
|
||||
session.sshCredentials.username = null
|
||||
session.sshCredentials.password = null
|
||||
}
|
||||
socket.handshake.session.usedBasicAuth = false
|
||||
|
||||
session.usedBasicAuth = false
|
||||
sessionState.authenticated = false
|
||||
sessionState.username = null
|
||||
sessionState.password = null
|
||||
socket.handshake.session.save(function(err) {
|
||||
|
||||
session.save(function(err) {
|
||||
if (err) {
|
||||
console.error(`Failed to save session for ${socket.id}:`, err)
|
||||
}
|
||||
|
@ -376,23 +381,3 @@ module.exports = function(io, config) {
|
|||
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")
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {Object} target - The target object to merge into
|
||||
|
@ -48,7 +29,7 @@ function deepMerge(target, source) {
|
|||
})
|
||||
return output
|
||||
}
|
||||
exports.deepMerge = deepMerge
|
||||
|
||||
/**
|
||||
* Generates a secure random session secret
|
||||
* @returns {string} A random 32-byte hex string
|
||||
|
@ -56,4 +37,100 @@ exports.deepMerge = deepMerge
|
|||
function generateSecureSecret() {
|
||||
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