diff --git a/app/app.js b/app/app.js index 6700e17..13df855 100644 --- a/app/app.js +++ b/app/app.js @@ -6,30 +6,85 @@ const socketIo = require('socket.io') const config = require('./config') const socketHandler = require('./socket') -const server = http.createServer() +/** + * Creates and configures the HTTP server + * @returns {http.Server} The HTTP server instance + */ +function createServer() { + return http.createServer() +} -const io = socketIo(server, { - path: '/ssh/socket.io', - cors: { - origin: config.origin || ["*.*"], +/** + * Configures Socket.IO with the given server + * @param {http.Server} server - The HTTP server instance + * @returns {import('socket.io').Server} The Socket.IO server instance + */ +function configureSocketIO(server) { + return socketIo(server, { + path: '/ssh/socket.io', + cors: getCorsConfig() + }) +} + +/** + * Gets the CORS configuration + * @returns {Object} The CORS configuration object + */ +function getCorsConfig() { + return { + origin: config.origin || ['*.*'], methods: ['GET', 'POST'], credentials: true } -}) +} -io.on('connection', (socket) => { +/** + * Sets up Socket.IO event listeners + * @param {import('socket.io').Server} io - The Socket.IO server instance + */ +function setupSocketIOListeners(io) { + socketHandler(io, config) +} + +/** + * Handles a new Socket.IO connection + * @param {import('socket.io').Socket} socket - The Socket.IO socket + */ +function handleConnection(socket) { + logNewConnection(socket) + setupDisconnectListener(socket) +} + +/** + * Logs information about a new connection + * @param {import('socket.io').Socket} socket - The Socket.IO socket + */ +function logNewConnection(socket) { console.log( 'New connection:', socket.id, 'Transport:', socket.conn.transport.name ) +} - socketHandler(io, socket) - +/** + * Sets up the disconnect listener for a socket + * @param {import('socket.io').Socket} socket - The Socket.IO socket + */ +function setupDisconnectListener(socket) { socket.on('disconnect', (reason) => { console.log('Client disconnected:', socket.id, reason) }) -}) +} -module.exports = { server, config, io } +// Create and configure the server +const server = createServer() +const io = configureSocketIO(server) + +// Set up Socket.IO listeners +setupSocketIOListeners(io) + +// Log the config object to verify its contents + +module.exports = { server, config, io } \ No newline at end of file diff --git a/app/config.js b/app/config.js index 6d0f9fe..8f7a3d7 100644 --- a/app/config.js +++ b/app/config.js @@ -1,11 +1,55 @@ -// config.js +'use strict' + const path = require('path') const fs = require('fs') -const nodeRoot = path.dirname(require.main.filename) -const configPath = path.join(nodeRoot, 'config.json') +const readConfig = require('read-config-ng') +const Ajv = require('ajv') -// Default configuration -let config = { +/** + * @typedef {Object} Config + * @property {Object} listen - Listening configuration + * @property {string} listen.ip - IP address to listen on + * @property {number} listen.port - Port to listen on + * @property {Object} http - HTTP configuration + * @property {string[]} http.origins - Allowed origins + * @property {Object} user - User configuration + * @property {string|null} user.name - Username + * @property {string|null} user.password - Password + * @property {Object} ssh - SSH configuration + * @property {string|null} ssh.host - SSH host + * @property {number} ssh.port - SSH port + * @property {string} ssh.term - Terminal type + * @property {number} ssh.readyTimeout - Ready timeout + * @property {number} ssh.keepaliveInterval - Keepalive interval + * @property {number} ssh.keepaliveCountMax - Max keepalive count + * @property {Object} terminal - Terminal configuration + * @property {boolean} terminal.cursorBlink - Whether cursor blinks + * @property {number} terminal.scrollback - Scrollback limit + * @property {number} terminal.tabStopWidth - Tab stop width + * @property {string} terminal.bellStyle - Bell style + * @property {Object} header - Header configuration + * @property {string|null} header.text - Header text + * @property {string} header.background - Header background color + * @property {Object} options - Options configuration + * @property {boolean} options.challengeButton - Challenge button enabled + * @property {boolean} options.allowreauth - Allow reauthentication + * @property {Object} algorithms - Encryption algorithms + * @property {string[]} algorithms.kex - Key exchange algorithms + * @property {string[]} algorithms.cipher - Cipher algorithms + * @property {string[]} algorithms.hmac - HMAC algorithms + * @property {string[]} algorithms.compress - Compression algorithms + * @property {Object} serverlog - Server log configuration + * @property {boolean} serverlog.client - Client logging enabled + * @property {boolean} serverlog.server - Server logging enabled + * @property {boolean} accesslog - Access logging enabled + * @property {boolean} verify - Verification enabled + */ + +/** + * Default configuration + * @type {Config} + */ +const defaultConfig = { listen: { ip: '0.0.0.0', port: 2222 @@ -37,7 +81,8 @@ let config = { }, options: { challengeButton: true, - allowreauth: true + allowreauth: false, + allowReplay: false }, algorithms: { kex: [ @@ -68,24 +113,196 @@ let config = { verify: false } -try { - if (fs.existsSync(configPath)) { - console.log('WebSSH2 service reading config from: ' + configPath) - config = require('read-config-ng')(configPath) - } else { - console.error( - '\n\nERROR: Missing config.json for webssh. Current config: ' + - JSON.stringify(config) - ) - console.error('\n See config.json.sample for details\n\n') - } -} catch (err) { - console.error( - '\n\nERROR: Missing config.json for webssh. Current config: ' + - JSON.stringify(config) - ) - console.error('\n See config.json.sample for details\n\n') - console.error('ERROR:\n\n ' + err) +/** + * Schema for validating the config + */ +const configSchema = { + type: 'object', + properties: { + listen: { + type: 'object', + properties: { + ip: { type: 'string', format: 'ipv4' }, + port: { type: 'integer', minimum: 1, maximum: 65535 } + }, + required: ['ip', 'port'] + }, + http: { + type: 'object', + properties: { + origins: { + type: 'array', + items: { type: 'string' } + } + }, + required: ['origins'] + }, + user: { + type: 'object', + properties: { + name: { type: ['string', 'null'] }, + password: { type: ['string', 'null'] } + }, + required: ['name', 'password'] + }, + ssh: { + type: 'object', + properties: { + host: { type: ['string', 'null'] }, + port: { type: 'integer', minimum: 1, maximum: 65535 }, + term: { type: 'string' }, + readyTimeout: { type: 'integer' }, + keepaliveInterval: { type: 'integer' }, + keepaliveCountMax: { type: 'integer' } + }, + required: ['host', 'port', 'term', 'readyTimeout', 'keepaliveInterval', 'keepaliveCountMax'] + }, + terminal: { + type: 'object', + properties: { + cursorBlink: { type: 'boolean' }, + scrollback: { type: 'integer' }, + tabStopWidth: { type: 'integer' }, + bellStyle: { type: 'string' } + }, + required: ['cursorBlink', 'scrollback', 'tabStopWidth', 'bellStyle'] + }, + header: { + type: 'object', + properties: { + text: { type: ['string', 'null'] }, + background: { type: 'string' } + }, + required: ['text', 'background'] + }, + options: { + type: 'object', + properties: { + challengeButton: { type: 'boolean' }, + allowreauth: { type: 'boolean' }, + allowReplay: { type: 'boolean' } + }, + required: ['challengeButton', 'allowreauth', 'allowReplay'] + }, + algorithms: { + type: 'object', + properties: { + kex: { + type: 'array', + items: { type: 'string' } + }, + cipher: { + type: 'array', + items: { type: 'string' } + }, + hmac: { + type: 'array', + items: { type: 'string' } + }, + compress: { + type: 'array', + items: { type: 'string' } + } + }, + required: ['kex', 'cipher', 'hmac', 'compress'] + }, + serverlog: { + type: 'object', + properties: { + client: { type: 'boolean' }, + server: { type: 'boolean' } + }, + required: ['client', 'server'] + }, + accesslog: { type: 'boolean' }, + verify: { type: 'boolean' } + }, + required: ['listen', 'http', 'user', 'ssh', 'terminal', 'header', 'options', 'algorithms', 'serverlog', 'accesslog', 'verify'] } +/** + * Gets the path to the config file + * @returns {string} The path to the config file + */ +function getConfigPath() { + const nodeRoot = path.dirname(require.main.filename) + return path.join(nodeRoot, 'config.json') +} + +/** + * Reads the config file + * @param {string} configPath - The path to the config file + * @returns {Config} The configuration object + */ +function readConfigFile(configPath) { + console.log('WebSSH2 service reading config from: ' + configPath) + return readConfig(configPath) +} + +/** + * Validates the configuration against the schema + * @param {Object} config - The configuration object to validate + * @returns {Object} The validated configuration object + * @throws {Error} If the configuration is invalid + */ +function validateConfig(config) { + const ajv = new Ajv() + const validate = ajv.compile(configSchema) + const valid = validate(config) + console.log('WebSSH2 service validating config') + if (!valid) { + throw new Error('Config validation error: ' + ajv.errorsText(validate.errors)) + } + console.log("config: ", JSON.stringify(config, null, 2)) + return config +} + +/** + * Logs an error message + * @param {string} message - The error message + * @param {Error} [error] - The error object + */ +function logError(message, error) { + console.error(message) + if (error) { + console.error('ERROR:\n\n ' + error) + } +} + +/** + * Loads the configuration + * @returns {Config} The loaded configuration + */ +function loadConfig() { + const configPath = getConfigPath() + + try { + if (fs.existsSync(configPath)) { + const config = readConfigFile(configPath) + return validateConfig(config) + } else { + logError( + '\n\nERROR: Missing config.json for webssh. Current config: ' + + JSON.stringify(defaultConfig) + + '\n\n See config.json.sample for details\n\n' + ) + return defaultConfig + } + } catch (err) { + logError( + '\n\nERROR: Missing config.json for webssh. Current config: ' + + JSON.stringify(defaultConfig) + + '\n\n See config.json.sample for details\n\n', + err + ) + return defaultConfig + } +} + +/** + * The loaded configuration + * @type {Config} + */ +const config = loadConfig() + module.exports = config diff --git a/app/socket.js b/app/socket.js index eac9174..fcf9890 100644 --- a/app/socket.js +++ b/app/socket.js @@ -5,142 +5,316 @@ const debug = require('debug') const debugWebSSH2 = require('debug')('WebSSH2') const SSH = require('ssh2').Client -module.exports = function (io) { - io.on('connection', (socket) => { - let conn = null - let stream = null - console.log(`SOCKET CONNECT: ${socket.id}`) +let conn = null; +let stream = null; - // Remove existing listeners to prevent duplicates - socket.removeAllListeners('authenticate') - socket.removeAllListeners('data') - socket.removeAllListeners('resize') - socket.removeAllListeners('disconnect') - - // Authenticate user - socket.on('authenticate', (credentials) => { - console.log(`SOCKET AUTHENTICATE: ${socket.id}`) - if (isValidCredentials(credentials)) { - console.log(`SOCKET AUTHENTICATE SUCCESS: ${socket.id}`) - initializeConnection(socket, credentials) - } else { - console.log(`SOCKET AUTHENTICATE FAILED: ${socket.id}`) - socket.emit('auth_result', { - success: false, - message: 'Invalid credentials' - }) - } - }) - - socket.on('disconnect', (reason) => { - debugWebSSH2(`SOCKET DISCONNECT: ${socket.id}, Reason: ${reason}`) - if (conn) { - conn.end() - } - // Clean up listeners - socket.removeAllListeners() - }) - - socket.on('data', (data) => { - if (stream) { - stream.write(data) - } - }) - - socket.on('resize', (data) => { - if (stream) { - stream.setWindow(data.rows, data.cols) - } - }) - - function initializeConnection (socket, credentials) { - if (conn) { - // If there's an existing connection, end it before creating a new one - conn.end() - } - - conn = new SSH() - - conn.on('ready', () => { - console.log( - `WebSSH2 Login: user=${credentials.username} from=${socket.handshake.address} host=${credentials.host} port=${credentials.port} sessionID=${socket.id}` - ) - - socket.emit('auth_result', { success: true }) - socket.emit('allowreauth', true) - socket.emit('allowreplay', true) - socket.emit('title', `ssh://${credentials.host}`) - socket.emit('status', 'SSH CONNECTION ESTABLISHED') - socket.emit('statusBackground', 'green') - - conn.shell( - { - term: credentials.term, - cols: credentials.cols, - rows: credentials.rows - }, - (err, str) => { - if (err) { - return SSHerror('EXEC ERROR', err) - } - stream = str - - stream.on('data', (data) => { - socket.emit('data', data.toString('utf-8')) - }) - - stream.on('close', (code, signal) => { - SSHerror('STREAM CLOSE', { - message: - code || signal - ? `CODE: ${code} SIGNAL: ${signal}` - : undefined - }) - }) - - stream.stderr.on('data', (data) => { - console.log('STDERR: ' + data) - }) - } - ) - }) - - conn.on('banner', (data) => { - socket.emit('data', data.replace(/\r?\n/g, '\r\n')) - }) - - conn.on('end', () => SSHerror('CONN END BY HOST')) - conn.on('close', () => SSHerror('CONN CLOSE')) - conn.on('error', (err) => SSHerror('CONN ERROR', err)) - - conn.connect({ - host: credentials.host, - port: credentials.port, - username: credentials.username, - password: credentials.password, - tryKeyboard: true, - algorithms: credentials.algorithms, - readyTimeout: credentials.readyTimeout, - keepaliveInterval: credentials.keepaliveInterval, - keepaliveCountMax: credentials.keepaliveCountMax, - debug: debug('ssh2') - }) - } - - function SSHerror (myFunc, err) { - const errorMessage = err ? `: ${err.message}` : '' - console.log(`WebSSH2 error: ${myFunc}${errorMessage}`) - socket.emit('ssherror', `SSH ${myFunc}${errorMessage}`) - if (conn) { - conn.end() - } - // Don't disconnect the socket here, let the client handle reconnection if necessary - // socket.disconnect(true); - } - - function isValidCredentials (credentials) { - // Implement your credential validation logic here - return credentials && credentials.username && credentials.password - } - }) +/** + * Handles WebSocket connections for SSH + * @param {import('socket.io').Server} io - The Socket.IO server instance + * @param {Object} config - The configuration object + */ +module.exports = function (io, config) { + io.on('connection', (socket) => handleConnection(socket, config)) } + +/** + * Handles a new WebSocket connection + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {Object} config - The configuration object + */ +function handleConnection(socket, config) { + let isConnectionClosed = false; + + console.log(`SOCKET CONNECT: ${socket.id}`); + + removeExistingListeners(socket) + setupInitialSocketListeners(socket, config) + + /** + * Removes existing listeners to prevent duplicates + * @param {import('socket.io').Socket} socket - The Socket.IO socket + */ + function removeExistingListeners(socket) { + ['authenticate', 'data', 'resize', 'disconnect', 'control'].forEach(event => { + socket.removeAllListeners(event) + }) + } + + /** + * Sets up initial socket event listeners + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {Object} config - The configuration object + */ + function setupInitialSocketListeners(socket, config) { + socket.on('authenticate', creds => handleAuthentication(socket, creds, config)) + socket.on('disconnect', reason => handleDisconnect(socket, reason)) + } + + /** + * Handles authentication attempts + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {Credentials} creds - The credentials for authentication + * @param {Object} config - The configuration object + */ + function handleAuthentication(socket, creds, config) { + console.log(`SOCKET AUTHENTICATE: ${socket.id}`) + if (isValidCredentials(creds)) { + console.log(`SOCKET AUTHENTICATE SUCCESS: ${socket.id}`) + initializeConnection(socket, creds, config) + } else { + console.log(`SOCKET AUTHENTICATE FAILED: ${socket.id}`) + socket.emit('auth_result', { success: false, message: 'Invalid credentials' }) + } + } + + /** + * Initializes an SSH connection + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {Credentials} creds - The user credentials + * @param {Object} config - The configuration object + */ + function initializeConnection(socket, creds, config) { + if (conn) { + conn.end() + } + + conn = new SSH() + + conn.on('ready', () => { + console.log(`SSH CONNECTION READY: ${socket.id}`) + socket.emit('auth_result', { success: true }) + socket.emit('allowreplay', config.options.allowReplay || false) + socket.emit('allowreauth', config.options.allowreauth || false) + setupSSHListeners(socket, creds) + initializeShell(socket, creds) + }) + + conn.on('error', err => { + console.log(`SSH CONNECTION ERROR: ${socket.id}`, err) + handleError(socket, 'SSH CONNECTION ERROR', err) + }) + + conn.connect(getSSHConfig(creds, config)) + } + + /** + * Sets up SSH-specific event listeners + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {Credentials} creds - The user credentials + */ + function setupSSHListeners(socket, creds) { + conn.on('banner', data => handleBanner(socket, data)) + conn.on('end', () => handleSSHEnd(socket)) + conn.on('close', () => handleSSHClose(socket)) + + socket.on('data', data => handleData(socket, stream, data)) + socket.on('resize', data => handleResize(stream, data)) + socket.on('control', controlData => handleControl(socket, stream, creds, controlData, config)) + } + + /** + * Initializes the SSH shell + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {Credentials} creds - The user credentials + */ + function initializeShell(socket, creds) { + conn.shell( + { + term: creds.term, + cols: creds.cols, + rows: creds.rows + }, + (err, str) => { + if (err) { + return handleError(socket, 'EXEC ERROR', err) + } + stream = str + + stream.on('data', data => socket.emit('data', data.toString('utf-8'))) + stream.on('close', (code, signal) => { + handleError(socket, 'STREAM CLOSE', { + message: code || signal ? `CODE: ${code} SIGNAL: ${signal}` : undefined + }) + }) + stream.stderr.on('data', data => console.log('STDERR: ' + data)) + } + ) + } + + /** + * Handles the 'banner' event of the SSH connection + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {string} data - The banner data + */ + function handleBanner(socket, data) { + socket.emit('data', data.replace(/\r?\n/g, '\r\n')) + } + + /** + * Handles the SSH connection end event + * @param {import('socket.io').Socket} socket - The Socket.IO socket + */ + function handleSSHEnd(socket) { + console.log(`SSH CONNECTION ENDED: ${socket.id}`) + handleConnectionClose(socket) + } + + /** + * Handles the SSH connection close event + * @param {import('socket.io').Socket} socket - The Socket.IO socket + */ + function handleSSHClose(socket) { + console.log(`SSH CONNECTION CLOSED: ${socket.id}`) + handleConnectionClose(socket) + } + + /** + * Handles the closure of the SSH connection + * @param {import('socket.io').Socket} socket - The Socket.IO socket + */ + function handleConnectionClose(socket) { + isConnectionClosed = true + if (stream) { + stream.end() + stream = null + } + if (conn) { + conn.end() + conn = null + } + socket.emit('connection_closed') + } + + /** + * Handles socket disconnection + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {string} reason - The reason for disconnection + */ + function handleDisconnect(socket, reason) { + console.log(`SOCKET DISCONNECT: ${socket.id}, Reason: ${reason}`) + handleConnectionClose(socket) + } + + /** + * Handles incoming data from the client + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {import('ssh2').Channel} stream - The SSH stream + * @param {string} data - The incoming data + */ + function handleData(socket, stream, data) { + if (stream && !isConnectionClosed) { + try { + stream.write(data) + } catch (error) { + console.log('Error writing to stream:', error.message) + handleConnectionClose(socket) + } + } else if (isConnectionClosed) { + console.log('Attempted to write to closed connection') + socket.emit('connection_closed') + } + } + + /** + * Handles terminal resize events + * @param {import('ssh2').Channel} stream - The SSH stream + * @param {Object} data - The resize data + * @param {number} data.rows - The number of rows + * @param {number} data.cols - The number of columns + */ + function handleResize(stream, data) { + if (stream) { + stream.setWindow(data.rows, data.cols) + } + } + + /** + * Handles control commands from the client + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {import('ssh2').Channel} stream - The SSH stream + * @param {Credentials} credentials - The user credentials + * @param {string} controlData - The control command + * @param {Object} config - The configuration object + */ + function handleControl(socket, stream, credentials, controlData, config) { + console.log(`Received control data: ${controlData}`); + + if (controlData === 'replayCredentials' && stream && credentials) { + replayCredentials(socket, stream, credentials, config); + } else if (controlData === 'reauth' && config.options.allowreauth) { + handleReauth(socket); + } + } + + /** + * Replays the user credentials to the SSH stream + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {import('ssh2').Channel} stream - The SSH stream + * @param {Credentials} credentials - The user credentials + * @param {Object} config - The configuration object + */ + function replayCredentials(socket, stream, credentials, config) { + let allowReplay = config.options.allowReplay || false; + + if (allowReplay) { + console.log(`Replaying credentials for ${socket.id}`); + stream.write(credentials.password + '\n'); + } else { + console.log(`Credential replay not allowed for ${socket.id}`); + } + } + + /** + * Handles reauthentication request + * @param {import('socket.io').Socket} socket - The Socket.IO socket + */ + function handleReauth(socket) { + console.log(`Reauthentication requested for ${socket.id}`); + handleConnectionClose(socket); + socket.emit('reauth'); + } + + /** + * Handles SSH errors + * @param {import('socket.io').Socket} socket - The Socket.IO socket + * @param {string} context - The context where the error occurred + * @param {Error} [err] - The error object + */ + function handleError(socket, context, err) { + const errorMessage = err ? `: ${err.message}` : '' + console.log(`WebSSH2 error: ${context}${errorMessage}`) + socket.emit('ssherror', `SSH ${context}${errorMessage}`) + handleConnectionClose(socket) + } + + /** + * Validates the provided credentials + * @param {Credentials} credentials - The credentials to validate + * @returns {boolean} Whether the credentials are valid + */ + function isValidCredentials(credentials) { + // Implement your credential validation logic here + return credentials && credentials.username && credentials.password + } + + /** + * Generates the SSH configuration object + * @param {Credentials} credentials - The user credentials + * @param {Object} config - The configuration object + * @returns {import('ssh2').ConnectConfig} The SSH configuration object + */ + function getSSHConfig(credentials, config) { + return { + host: credentials.host, + port: credentials.port, + username: credentials.username, + password: credentials.password, + tryKeyboard: true, + algorithms: credentials.algorithms, + readyTimeout: credentials.readyTimeout, + keepaliveInterval: credentials.keepaliveInterval, + keepaliveCountMax: credentials.keepaliveCountMax, + debug: debug('ssh2') + } + } +} \ No newline at end of file diff --git a/index.js b/index.js index 533fae3..922325e 100644 --- a/index.js +++ b/index.js @@ -1,20 +1,51 @@ 'use strict' -/* jshint esversion: 6, asi: true, node: true */ -/* + +/** * index.js * * WebSSH2 - Web to SSH2 gateway * Bill Church - https://github.com/billchurch/WebSSH2 - May 2017 - * */ + const { server, config } = require('./app/app') -server.listen(config.listen.port, config.listen.ip, () => { - console.log( - `WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}` - ) -}) +/** + * Starts the server + * @param {Object} config - The server configuration + * @param {string} config.listen.ip - The IP address to listen on + * @param {number} config.listen.port - The port to listen on + * @param {import('http').Server} server - The HTTP server instance + */ +function startServer(config, server) { + server.listen(config.listen.port, config.listen.ip, () => { + console.log( + `WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}` + ) + }) -server.on('error', function (err) { - console.log('WebSSH2 server.listen ERROR: ' + err.code) -}) + server.on('error', handleServerError) +} + +/** + * Handles server errors + * @param {Error} err - The error object + */ +function handleServerError(err) { + console.error('WebSSH2 server.listen ERROR:', err.code) +} + +/** + * Main function to start the application + */ +function main() { + startServer(config, server) +} + +// Run the application +main() + +// For testing purposes, export the functions +module.exports = { + startServer, + handleServerError +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dc94ea4..c7ae212 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "webssh2-server", - "version": "0.2.13", + "version": "0.2.14", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -31,15 +31,12 @@ "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha512-I/bSHSNEcFFqXLf91nchoNB9D1Kie3QKcWdchYUaoIg1+1bdWDkdfdlvdIOJbi9U8xR0y+MWc5D+won9v95WlQ==", "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" } }, "ajv-keywords": { @@ -446,7 +443,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, "requires": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -580,6 +576,11 @@ "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==" + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -789,7 +790,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "requires": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -1015,7 +1015,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, "requires": { "get-intrinsic": "^1.2.4" } @@ -1023,8 +1022,7 @@ "es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, "es-object-atoms": { "version": "1.0.0", @@ -1109,6 +1107,18 @@ "text-table": "^0.2.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1675,8 +1685,7 @@ "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { "version": "1.1.6", @@ -1706,7 +1715,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, "requires": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -1808,7 +1816,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "requires": { "get-intrinsic": "^1.1.3" } @@ -1890,7 +1897,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "requires": { "es-define-property": "^1.0.0" } @@ -1898,14 +1904,12 @@ "has-proto": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.2", @@ -1952,7 +1956,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "requires": { "function-bind": "^1.1.2" } @@ -2405,6 +2408,24 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", + "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "requires": { + "call-bind": "^1.0.5", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -2416,6 +2437,11 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, + "jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==" + }, "jsx-ast-utils": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", @@ -2781,8 +2807,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -3419,7 +3444,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "requires": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -3988,6 +4012,20 @@ "lodash": "^4.17.4", "slice-ansi": "1.0.0", "string-width": "^2.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + } } }, "term-size": { diff --git a/package.json b/package.json index 402d84e..eda1c99 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "url": "https://github.com/billchurch/WebSSH2/issues" }, "dependencies": { + "ajv": "^4.11.8", "debug": "~4.1.0", "read-config-ng": "~3.0.7", "socket.io": "~2.2.0",