From 4185df77f6fbce0985b31e195485eb275556ac75 Mon Sep 17 00:00:00 2001 From: Bill Church Date: Thu, 21 Nov 2024 16:24:57 +0000 Subject: [PATCH] fix: username/password in config file no longer honored #374 --- .tool-versions | 2 +- app/app.js | 2 +- app/routes.js | 144 +++++++++++++++++++++++++++++-------------------- 3 files changed, 89 insertions(+), 59 deletions(-) diff --git a/.tool-versions b/.tool-versions index ebe704a3..7c5995d0 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 6.9.1 +nodejs 23.2.0 diff --git a/app/app.js b/app/app.js index c64ca1e5..4e0086a8 100644 --- a/app/app.js +++ b/app/app.js @@ -4,7 +4,7 @@ const express = require("express") const config = require("./config") const socketHandler = require("./socket") -const sshRoutes = require("./routes") +const sshRoutes = require("./routes")(config) const { applyMiddleware } = require("./middleware") const { createServer, startServer } = require("./server") const { configureSocketIO } = require("./io") diff --git a/app/routes.js b/app/routes.js index 90695cd8..c336a5d3 100644 --- a/app/routes.js +++ b/app/routes.js @@ -16,72 +16,102 @@ const { ConfigError, handleError } = require("./errors") const { HTTP } = require("./constants") const debug = createNamespacedDebug("routes") -const router = express.Router() -// eslint-disable-next-line consistent-return -function auth(req, res, next) { - debug("auth: Basic Auth") - const credentials = basicAuth(req) - if (!credentials) { - res.setHeader(HTTP.AUTHENTICATE, HTTP.REALM) - return res.status(HTTP.UNAUTHORIZED).send(HTTP.AUTH_REQUIRED) - } - // Validate and sanitize credentials - req.session.sshCredentials = { - username: validator.escape(credentials.name), - password: credentials.pass // We don't sanitize the password as it might contain special characters +module.exports = function(config) { + const router = express.Router() + + /** + * Middleware function that handles HTTP Basic Authentication for the application. + * + * If the `config.user.name` and `config.user.password` are set, it will use those + * credentials to authenticate the request and set the `req.session.sshCredentials` + * object with the username and password. + * + * If the `config.user.name` and `config.user.password` are not set, it will attempt + * to use HTTP Basic Authentication to authenticate the request. It will validate and + * sanitize the credentials, and set the `req.session.sshCredentials` object with the + * username and password. + * + * The function will also set the `req.session.usedBasicAuth` flag to indicate that + * Basic Authentication was used. + * + * If the authentication fails, the function will send a 401 Unauthorized response + * with the appropriate WWW-Authenticate header. + */ + // eslint-disable-next-line consistent-return + function auth(req, res, next) { + if (config.user.name && config.user.password) { + req.session.sshCredentials = { + username: config.user.name, + password: config.user.password + } + req.session.usedBasicAuth = true + return next() + } + // Scenario 2: Basic Auth + debug("auth: Basic Auth") + const credentials = basicAuth(req) + if (!credentials) { + res.setHeader(HTTP.AUTHENTICATE, HTTP.REALM) + return res.status(HTTP.UNAUTHORIZED).send(HTTP.AUTH_REQUIRED) + } + // Validate and sanitize credentials + req.session.sshCredentials = { + username: validator.escape(credentials.name), + password: credentials.pass // We don't sanitize the password as it might contain special characters + } + req.session.usedBasicAuth = true // Set this flag when Basic Auth is used + next() } - req.session.usedBasicAuth = true // Set this flag when Basic Auth is used - next() -} -// Scenario 1: No auth required, uses websocket authentication instead -router.get("/", (req, res) => { - debug("router.get./: Accessed / route") - handleConnection(req, res) -}) + // Scenario 1: No auth required, uses websocket authentication instead + router.get("/", (req, res) => { + debug("router.get./: Accessed / route") + handleConnection(req, res) + }) -// Scenario 2: Auth required, uses HTTP Basic Auth -router.get("/host/:host", auth, (req, res) => { - debug(`router.get.host: /ssh/host/${req.params.host} route`) + // Scenario 2: Auth required, uses HTTP Basic Auth + router.get("/host/:host", auth, (req, res) => { + debug(`router.get.host: /ssh/host/${req.params.host} route`) - try { - const host = getValidatedHost(req.params.host) - const port = getValidatedPort(req.query.port) + try { + const host = getValidatedHost(req.params.host) + const port = getValidatedPort(req.query.port) - // Validate and sanitize sshterm parameter if it exists - const sshterm = validateSshTerm(req.query.sshterm) + // 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 - } - req.session.usedBasicAuth = true + 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 + } + req.session.usedBasicAuth = true - // Sanitize and log the sshCredentials object - const sanitizedCredentials = maskSensitiveData( - JSON.parse(JSON.stringify(req.session.sshCredentials)) - ) - debug("/ssh/host/ Credentials: ", sanitizedCredentials) + // Sanitize and log the sshCredentials object + const sanitizedCredentials = maskSensitiveData( + 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) - } -}) + handleConnection(req, res, { host: host }) + } catch (err) { + const error = new ConfigError(`Invalid configuration: ${err.message}`) + handleError(error, res) + } + }) -// Clear credentials route -router.get("/clear-credentials", (req, res) => { - req.session.sshCredentials = null - res.status(HTTP.OK).send(HTTP.CREDENTIALS_CLEARED) -}) + // Clear credentials route + router.get("/clear-credentials", (req, res) => { + req.session.sshCredentials = null + res.status(HTTP.OK).send(HTTP.CREDENTIALS_CLEARED) + }) -router.get("/force-reconnect", (req, res) => { - req.session.sshCredentials = null - res.status(HTTP.UNAUTHORIZED).send(HTTP.AUTH_REQUIRED) -}) + router.get("/force-reconnect", (req, res) => { + req.session.sshCredentials = null + res.status(HTTP.UNAUTHORIZED).send(HTTP.AUTH_REQUIRED) + }) -module.exports = router + return router +}