feat: add allowReconnect, allowReauth, and autoLog features, normalize debug logs
This commit is contained in:
parent
e3f97ad6b6
commit
e2ea06866e
2 changed files with 65 additions and 56 deletions
|
@ -35,7 +35,10 @@ const crypto = require("crypto")
|
||||||
* @property {string} header.background - Header background color
|
* @property {string} header.background - Header background color
|
||||||
* @property {Object} options - Options configuration
|
* @property {Object} options - Options configuration
|
||||||
* @property {boolean} options.challengeButton - Challenge button enabled
|
* @property {boolean} options.challengeButton - Challenge button enabled
|
||||||
|
* @property {boolean} options.autoLog - Auto log enabled
|
||||||
* @property {boolean} options.allowReauth - Allow reauthentication
|
* @property {boolean} options.allowReauth - Allow reauthentication
|
||||||
|
* @property {boolean} options.allowReconnect - Allow reconnection
|
||||||
|
* @property {boolean} options.allowReplay - Allow replay
|
||||||
* @property {Object} algorithms - Encryption algorithms
|
* @property {Object} algorithms - Encryption algorithms
|
||||||
* @property {string[]} algorithms.kex - Key exchange algorithms
|
* @property {string[]} algorithms.kex - Key exchange algorithms
|
||||||
* @property {string[]} algorithms.cipher - Cipher algorithms
|
* @property {string[]} algorithms.cipher - Cipher algorithms
|
||||||
|
@ -84,7 +87,9 @@ const defaultConfig = {
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
challengeButton: true,
|
challengeButton: true,
|
||||||
|
autoLog: false,
|
||||||
allowReauth: false,
|
allowReauth: false,
|
||||||
|
allowReconnect: false,
|
||||||
allowReplay: false
|
allowReplay: false
|
||||||
},
|
},
|
||||||
algorithms: {
|
algorithms: {
|
||||||
|
@ -193,7 +198,9 @@ const configSchema = {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
challengeButton: { type: "boolean" },
|
challengeButton: { type: "boolean" },
|
||||||
|
autoLog: { type: "boolean" },
|
||||||
allowReauth: { type: "boolean" },
|
allowReauth: { type: "boolean" },
|
||||||
|
allowReconnect: { type: "boolean" },
|
||||||
allowReplay: { type: "boolean" }
|
allowReplay: { type: "boolean" }
|
||||||
},
|
},
|
||||||
required: ["challengeButton", "allowReauth", "allowReplay"]
|
required: ["challengeButton", "allowReauth", "allowReplay"]
|
||||||
|
|
114
app/socket.js
114
app/socket.js
|
@ -7,7 +7,6 @@ const { header } = require("./config")
|
||||||
const debug = createDebug("webssh2:socket")
|
const debug = createDebug("webssh2:socket")
|
||||||
const SSH = require("ssh2").Client
|
const SSH = require("ssh2").Client
|
||||||
const { sanitizeObject } = require("./utils")
|
const { sanitizeObject } = require("./utils")
|
||||||
const session = require("express-session")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles WebSocket connections for SSH
|
* Handles WebSocket connections for SSH
|
||||||
|
@ -47,22 +46,18 @@ function handleConnection(socket, config) {
|
||||||
// Check for HTTP Basic Auth credentials
|
// Check for HTTP Basic Auth credentials
|
||||||
if (socket.handshake.session.sshCredentials) {
|
if (socket.handshake.session.sshCredentials) {
|
||||||
const creds = socket.handshake.session.sshCredentials
|
const creds = socket.handshake.session.sshCredentials
|
||||||
|
|
||||||
debug(
|
debug(
|
||||||
`handleConnection: creds from session: ${socket.id}, Host: ${creds.host}:`,
|
`handleConnection: ${socket.id}, Host: ${creds.host}: HTTP Basic Credentials Exist, creds: %O`,
|
||||||
sanitizeObject(creds)
|
sanitizeObject(creds)
|
||||||
)
|
)
|
||||||
|
|
||||||
handleAuthenticate(socket, creds)
|
handleAuthenticate(socket, creds)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit an event to the client to request authentication
|
|
||||||
const authenticated = sessionState.authenticated
|
const authenticated = sessionState.authenticated
|
||||||
if (!authenticated) {
|
if (!authenticated) {
|
||||||
debug(
|
// Emit an event to the client to request authentication
|
||||||
`Requesting authentication for ${socket.id} and authenticated is ${authenticated}`
|
debug(`handleConnection: ${socket.id}, emitting request_auth`)
|
||||||
)
|
|
||||||
socket.emit("authentication", { action: "request_auth" })
|
socket.emit("authentication", { action: "request_auth" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,23 +68,18 @@ function handleConnection(socket, config) {
|
||||||
*/
|
*/
|
||||||
function setupInitialSocketListeners(socket, sessionState) {
|
function setupInitialSocketListeners(socket, sessionState) {
|
||||||
config = sessionState.config
|
config = sessionState.config
|
||||||
|
debug(`setupInitialSocketListeners: ${socket.id}`)
|
||||||
|
|
||||||
socket.on("error", (error) =>
|
socket.on("error", (error) =>
|
||||||
console.error(`Socket error for ${socket.id}:`, error)
|
console.error(`Socket error for ${socket.id}:`, error)
|
||||||
)
|
)
|
||||||
|
|
||||||
socket.on("authenticate", (creds) =>
|
socket.on("authenticate", (creds) =>
|
||||||
handleAuthenticate(socket, creds, sessionState)
|
handleAuthenticate(socket, creds, sessionState)
|
||||||
)
|
)
|
||||||
socket.on("disconnect", (reason) => {
|
|
||||||
debug(`Client ${socket.id} disconnected. Reason: ${reason}`)
|
socket.on("disconnect", (socket, reason, conn, stream) => {
|
||||||
debug("Socket state at disconnect:", socket.conn.transport.readyState)
|
handleDisconnect(socket, reason, conn, stream)
|
||||||
if (conn) {
|
|
||||||
conn.end()
|
|
||||||
conn = null
|
|
||||||
}
|
|
||||||
if (stream) {
|
|
||||||
stream.end()
|
|
||||||
stream = null
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,18 +92,7 @@ function handleConnection(socket, config) {
|
||||||
*/
|
*/
|
||||||
function handleAuthenticate(socket, creds) {
|
function handleAuthenticate(socket, creds) {
|
||||||
const config = sessionState.config
|
const config = sessionState.config
|
||||||
// {
|
debug(`handleAuthenticate: ${socket.id}, %O`, sanitizeObject(creds))
|
||||||
// "host": "192.168.0.20",
|
|
||||||
// "port": 22,
|
|
||||||
// "username": "test123",
|
|
||||||
// "password": "Seven888!",
|
|
||||||
// "term": "xterm-color",
|
|
||||||
// "readyTimeout": 20000,
|
|
||||||
// "cursorBlink": "true",
|
|
||||||
// "cols": 151,
|
|
||||||
// "rows": 53
|
|
||||||
// }
|
|
||||||
debug("handleAuthenticate: ", JSON.stringify(sanitizeObject(creds)))
|
|
||||||
|
|
||||||
if (isValidCredentials(socket, creds)) {
|
if (isValidCredentials(socket, creds)) {
|
||||||
creds.term !== null && (sessionState.term = creds.term)
|
creds.term !== null && (sessionState.term = creds.term)
|
||||||
|
@ -122,7 +101,7 @@ function handleConnection(socket, config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle invalid credentials scenario
|
// Handle invalid credentials scenario
|
||||||
debug(`CREDENTIALS INVALID: ${socket.id}, Host: ${creds.host}`)
|
console.warn(`handleAuthenticate: ${socket.id}, CREDENTIALS INVALID`)
|
||||||
socket.emit("authentication", {
|
socket.emit("authentication", {
|
||||||
success: false,
|
success: false,
|
||||||
message: "Invalid credentials format"
|
message: "Invalid credentials format"
|
||||||
|
@ -138,7 +117,7 @@ function handleConnection(socket, config) {
|
||||||
function initializeConnection(socket, creds) {
|
function initializeConnection(socket, creds) {
|
||||||
const config = sessionState.config
|
const config = sessionState.config
|
||||||
debug(
|
debug(
|
||||||
`initializeConnection: INITIALIZING SSH CONNECTION: ${socket.id}, Host: ${creds.host}`
|
`initializeConnection: ${socket.id}, INITIALIZING SSH CONNECTION: Host: ${creds.host}`
|
||||||
)
|
)
|
||||||
if (conn) {
|
if (conn) {
|
||||||
conn.end()
|
conn.end()
|
||||||
|
@ -158,15 +137,28 @@ function handleConnection(socket, config) {
|
||||||
sessionState.host = creds.host
|
sessionState.host = creds.host
|
||||||
sessionState.port = creds.port
|
sessionState.port = creds.port
|
||||||
debug(
|
debug(
|
||||||
`initializeConnection conn.on ready: ${socket.id}, Host: ${creds.host}`
|
`initializeConnection: ${socket.id} conn.on ready: Host: ${creds.host}`
|
||||||
)
|
)
|
||||||
socket.emit("authentication", { action: "auth_result", success: true })
|
console.log(
|
||||||
|
`initializeConnection: ${socket.id} conn.on ready: ${creds.user}@${creds.host}:${creds.port} successfully connected`
|
||||||
|
)
|
||||||
|
const auth_result = { action: "auth_result", success: true }
|
||||||
|
debug(
|
||||||
|
`initializeConnection: ${socket.id} conn.on ready: emitting authentication: ${JSON.stringify(auth_result)}`
|
||||||
|
)
|
||||||
|
socket.emit("authentication", auth_result)
|
||||||
|
|
||||||
// Emit consolidated permissions
|
// Emit consolidated permissions
|
||||||
socket.emit("permissions", {
|
const permissions = {
|
||||||
|
autoLog: config.options.autoLog || false,
|
||||||
allowReplay: config.options.allowReplay || false,
|
allowReplay: config.options.allowReplay || false,
|
||||||
|
allowReconnect: config.options.allowReconnect || false,
|
||||||
allowReauth: config.options.allowReauth || false
|
allowReauth: config.options.allowReauth || false
|
||||||
})
|
}
|
||||||
|
debug(
|
||||||
|
`initializeConnection: ${socket.id} conn.on ready: emitting permissions: ${JSON.stringify(permissions)}`
|
||||||
|
)
|
||||||
|
socket.emit("permissions", permissions)
|
||||||
|
|
||||||
updateElement(socket, "footer", `ssh://${creds.host}:${creds.port}`)
|
updateElement(socket, "footer", `ssh://${creds.host}:${creds.port}`)
|
||||||
|
|
||||||
|
@ -174,8 +166,6 @@ function handleConnection(socket, config) {
|
||||||
debug(`initializeConnection header: ${config.header}`)
|
debug(`initializeConnection header: ${config.header}`)
|
||||||
updateElement(socket, "header", config.header.text)
|
updateElement(socket, "header", config.header.text)
|
||||||
}
|
}
|
||||||
debug(`initializeConnection: ${socket.id}, sessionState: ${JSON.stringify(sanitizeObject(sessionState))}`)
|
|
||||||
|
|
||||||
setupSSHListeners(socket)
|
setupSSHListeners(socket)
|
||||||
initializeShell(socket)
|
initializeShell(socket)
|
||||||
})
|
})
|
||||||
|
@ -217,8 +207,10 @@ function handleConnection(socket, config) {
|
||||||
* @param {Credentials} creds - The user credentials
|
* @param {Credentials} creds - The user credentials
|
||||||
*/
|
*/
|
||||||
function initializeShell(socket) {
|
function initializeShell(socket) {
|
||||||
debug(`initializeShell: INITIALIZING SHELL: ${socket.id}`)
|
debug(
|
||||||
debug(`initializeShell: sessionState: ${JSON.stringify(sanitizeObject(sessionState))}`)
|
`initializeShell: ${socket.id}, sessionState: %O`,
|
||||||
|
sanitizeObject(sessionState)
|
||||||
|
)
|
||||||
const { term, cols, rows } = sessionState
|
const { term, cols, rows } = sessionState
|
||||||
|
|
||||||
conn.shell(
|
conn.shell(
|
||||||
|
@ -262,7 +254,7 @@ function handleConnection(socket, config) {
|
||||||
* @param {string} signal - The signal associated with the close event.
|
* @param {string} signal - The signal associated with the close event.
|
||||||
*/
|
*/
|
||||||
function handleStreamClose(stream, socket, code, signal) {
|
function handleStreamClose(stream, socket, code, signal) {
|
||||||
debug(`handleStreamClose: STREAM CLOSE: ${socket.id}`)
|
debug(`handleStreamClose: ${socket.id}`)
|
||||||
handleError(socket, "STREAM CLOSE", {
|
handleError(socket, "STREAM CLOSE", {
|
||||||
message: code || signal ? `CODE: ${code} SIGNAL: ${signal}` : undefined
|
message: code || signal ? `CODE: ${code} SIGNAL: ${signal}` : undefined
|
||||||
})
|
})
|
||||||
|
@ -300,7 +292,7 @@ function handleConnection(socket, config) {
|
||||||
* @param {string} data - The banner data
|
* @param {string} data - The banner data
|
||||||
*/
|
*/
|
||||||
function handleBanner(socket, data) {
|
function handleBanner(socket, data) {
|
||||||
// todo: sanatize the data
|
// todo: sanitize the data
|
||||||
socket.emit("data", data.replace(/\r?\n/g, "\r\n"))
|
socket.emit("data", data.replace(/\r?\n/g, "\r\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +301,7 @@ function handleConnection(socket, config) {
|
||||||
* @param {import('socket.io').Socket} socket - The Socket.IO socket
|
* @param {import('socket.io').Socket} socket - The Socket.IO socket
|
||||||
*/
|
*/
|
||||||
function handleSSHEnd(socket) {
|
function handleSSHEnd(socket) {
|
||||||
debug(`handleSSHEnd: SSH CONNECTION ENDED: ${socket.id}`)
|
debug(`handleSSHEnd: ${socket.id}`)
|
||||||
handleConnectionClose(socket)
|
handleConnectionClose(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,7 +310,7 @@ function handleConnection(socket, config) {
|
||||||
* @param {import('socket.io').Socket} socket - The Socket.IO socket
|
* @param {import('socket.io').Socket} socket - The Socket.IO socket
|
||||||
*/
|
*/
|
||||||
function handleSSHClose(socket) {
|
function handleSSHClose(socket) {
|
||||||
debug(`handleSSHClose: SSH CONNECTION CLOSED: ${socket.id}`)
|
debug(`handleSSHClose: ${socket.id}`)
|
||||||
handleConnectionClose(socket)
|
handleConnectionClose(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +319,7 @@ function handleConnection(socket, config) {
|
||||||
* @param {import('socket.io').Socket} socket - The Socket.IO socket
|
* @param {import('socket.io').Socket} socket - The Socket.IO socket
|
||||||
*/
|
*/
|
||||||
function handleConnectionClose(socket) {
|
function handleConnectionClose(socket) {
|
||||||
debug(`handleConnectionClose: Closing connection for ${socket.id}`)
|
debug(`handleConnectionClose: ${socket.id}`)
|
||||||
sessionState.connected = false
|
sessionState.connected = false
|
||||||
if (stream) {
|
if (stream) {
|
||||||
stream.end()
|
stream.end()
|
||||||
|
@ -337,7 +329,6 @@ function handleConnection(socket, config) {
|
||||||
conn.end()
|
conn.end()
|
||||||
conn = null
|
conn = null
|
||||||
}
|
}
|
||||||
socket.emit("connection_closed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -345,8 +336,18 @@ function handleConnection(socket, config) {
|
||||||
* @param {import('socket.io').Socket} socket - The Socket.IO socket
|
* @param {import('socket.io').Socket} socket - The Socket.IO socket
|
||||||
* @param {string} reason - The reason for disconnection
|
* @param {string} reason - The reason for disconnection
|
||||||
*/
|
*/
|
||||||
function handleDisconnect(socket, reason) {
|
function handleDisconnect(socket, reason, conn, stream) {
|
||||||
debug(`DISCONNECT: ${socket.id}, Reason: ${reason}`)
|
debug(`handleDisconnect: ${socket.id}, Reason: ${reason}`)
|
||||||
|
debug(`handleDisconnect: Socket state: $(socket.conn.transport.readyState)`)
|
||||||
|
if (conn) {
|
||||||
|
conn.end()
|
||||||
|
conn = null
|
||||||
|
}
|
||||||
|
if (stream) {
|
||||||
|
stream.end()
|
||||||
|
stream = null
|
||||||
|
}
|
||||||
|
|
||||||
handleConnectionClose(socket)
|
handleConnectionClose(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,7 +413,7 @@ function handleConnection(socket, config) {
|
||||||
debug(`handleTerminal: Received terminal data: ${JSON.stringify(data)}`)
|
debug(`handleTerminal: Received terminal data: ${JSON.stringify(data)}`)
|
||||||
const { term, rows, cols } = data
|
const { term, rows, cols } = data
|
||||||
if (term != null) {
|
if (term != null) {
|
||||||
sessionState.term = term;
|
sessionState.term = term
|
||||||
}
|
}
|
||||||
sessionState.rows = rows
|
sessionState.rows = rows
|
||||||
sessionState.cols = cols
|
sessionState.cols = cols
|
||||||
|
@ -481,7 +482,7 @@ function handleConnection(socket, config) {
|
||||||
*/
|
*/
|
||||||
function updateElement(socket, element, value) {
|
function updateElement(socket, element, value) {
|
||||||
debug(`updateElement: ${socket.id}, Element: ${element}, Value: ${value}`)
|
debug(`updateElement: ${socket.id}, Element: ${element}, Value: ${value}`)
|
||||||
socket.emit("updateUI", { element, value });
|
socket.emit("updateUI", { element, value })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -501,7 +502,7 @@ function handleConnection(socket, config) {
|
||||||
|
|
||||||
// Single line debug log with ternary operator
|
// Single line debug log with ternary operator
|
||||||
debug(
|
debug(
|
||||||
`isValidCredentials: CREDENTIALS ${isValid ? "VALID" : "INVALID"}: ${socket.id}${
|
`isValidCredentials: ${socket.id}, CREDENTIALS ${isValid ? "VALID" : "INVALID"}: ${socket.id}${
|
||||||
isValid ? `, Host: ${creds.host}` : ""
|
isValid ? `, Host: ${creds.host}` : ""
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
@ -516,9 +517,7 @@ function handleConnection(socket, config) {
|
||||||
* @returns {import('ssh2').ConnectConfig} The SSH configuration object
|
* @returns {import('ssh2').ConnectConfig} The SSH configuration object
|
||||||
*/
|
*/
|
||||||
function getSSHConfig(creds, config) {
|
function getSSHConfig(creds, config) {
|
||||||
debug(
|
debug(`getSSHConfig: ${socket.id}, creds: %O`, sanitizeObject(creds))
|
||||||
`getSSHConfig: ${socket.id}, Host: ${JSON.stringify(sanitizeObject(creds))}`
|
|
||||||
)
|
|
||||||
|
|
||||||
const sshConfig = {
|
const sshConfig = {
|
||||||
host: creds.host,
|
host: creds.host,
|
||||||
|
@ -534,7 +533,10 @@ function handleConnection(socket, config) {
|
||||||
creds.keepaliveCountMax || config.ssh.keepaliveCountMax,
|
creds.keepaliveCountMax || config.ssh.keepaliveCountMax,
|
||||||
debug: createDebug("ssh")
|
debug: createDebug("ssh")
|
||||||
}
|
}
|
||||||
debug(`getSSHConfig: ${JSON.stringify(sanitizeObject(sshConfig))}`)
|
debug(
|
||||||
|
`getSSHConfig: ${socket.id}, sshConfig: %O`,
|
||||||
|
sanitizeObject(sshConfig)
|
||||||
|
)
|
||||||
return sshConfig
|
return sshConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue