chore: client / server bifurcation mostly complete
This commit is contained in:
parent
b6e5089ee6
commit
bf50fca786
9 changed files with 4610 additions and 270 deletions
101
app/app.js
101
app/app.js
|
@ -1,88 +1,35 @@
|
|||
// app/app.js
|
||||
"use strict";
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
'use strict'
|
||||
|
||||
const path = require("path");
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const logger = require("morgan");
|
||||
const socketIo = require("socket.io");
|
||||
const myutil = require("./util");
|
||||
const config = require("./config");
|
||||
const socketHandler = require("./socket");
|
||||
const http = require('http')
|
||||
const socketIo = require('socket.io')
|
||||
const config = require('./config')
|
||||
const socketHandler = require('./socket')
|
||||
|
||||
const app = express();
|
||||
const server = require("http").Server(app);
|
||||
const server = http.createServer()
|
||||
|
||||
// Session middleware
|
||||
const sessionMiddleware = session({
|
||||
secret: config.session.secret,
|
||||
name: config.session.name,
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
maxAge: 24 * 60 * 60 * 1000,
|
||||
},
|
||||
});
|
||||
|
||||
// Express middleware
|
||||
app.use(sessionMiddleware);
|
||||
if (config.accesslog) app.use(logger("common"));
|
||||
app.disable("x-powered-by");
|
||||
|
||||
// Socket.IO setup
|
||||
const io = socketIo(server, {
|
||||
path: "/ssh/socket.io",
|
||||
path: '/ssh/socket.io',
|
||||
cors: {
|
||||
origin: "http://localhost:8080",
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Socket.io middleware
|
||||
io.use((socket, next) => {
|
||||
sessionMiddleware(socket.request, socket.request.res || {}, next);
|
||||
});
|
||||
|
||||
// WebSocket handling
|
||||
io.on("connection", (socket) => {
|
||||
console.log(
|
||||
"New connection:",
|
||||
socket.id,
|
||||
"Transport:",
|
||||
socket.conn.transport.name
|
||||
);
|
||||
|
||||
// Call the imported socket handler function only once per connection
|
||||
if (!socket.handled) {
|
||||
socketHandler(io, socket);
|
||||
socket.handled = true;
|
||||
origin: 'http://localhost:8080',
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true
|
||||
}
|
||||
})
|
||||
|
||||
socket.on("disconnect", (reason) => {
|
||||
console.log("Client disconnected:", socket.id, reason);
|
||||
});
|
||||
});
|
||||
io.on('connection', (socket) => {
|
||||
console.log(
|
||||
'New connection:',
|
||||
socket.id,
|
||||
'Transport:',
|
||||
socket.conn.transport.name
|
||||
)
|
||||
|
||||
// Error handling
|
||||
app.use((req, res, next) => {
|
||||
res.status(404).send("Sorry can't find that!");
|
||||
});
|
||||
socketHandler(io, socket)
|
||||
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).send("Something broke!");
|
||||
});
|
||||
socket.on('disconnect', (reason) => {
|
||||
console.log('Client disconnected:', socket.id, reason)
|
||||
})
|
||||
})
|
||||
|
||||
// Graceful shutdown
|
||||
process.on("SIGINT", () => {
|
||||
console.log("SIGINT signal received: closing HTTP server");
|
||||
server.close(() => {
|
||||
console.log("HTTP server closed");
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = { server, config, io };
|
||||
module.exports = { server, config, io }
|
||||
|
|
|
@ -1,95 +1,95 @@
|
|||
// config.js
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const nodeRoot = path.dirname(require.main.filename);
|
||||
const configPath = path.join(nodeRoot, "config.json");
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const nodeRoot = path.dirname(require.main.filename)
|
||||
const configPath = path.join(nodeRoot, 'config.json')
|
||||
|
||||
// Default configuration
|
||||
let config = {
|
||||
listen: {
|
||||
ip: "0.0.0.0",
|
||||
port: 2222,
|
||||
ip: '0.0.0.0',
|
||||
port: 2222
|
||||
},
|
||||
http: {
|
||||
origins: ["*:*"],
|
||||
origins: ['*:*']
|
||||
},
|
||||
user: {
|
||||
name: null,
|
||||
password: null,
|
||||
password: null
|
||||
},
|
||||
ssh: {
|
||||
host: null,
|
||||
port: 22,
|
||||
term: "xterm-color",
|
||||
term: 'xterm-color',
|
||||
readyTimeout: 20000,
|
||||
keepaliveInterval: 120000,
|
||||
keepaliveCountMax: 10,
|
||||
keepaliveCountMax: 10
|
||||
},
|
||||
terminal: {
|
||||
cursorBlink: true,
|
||||
scrollback: 10000,
|
||||
tabStopWidth: 8,
|
||||
bellStyle: "sound",
|
||||
bellStyle: 'sound'
|
||||
},
|
||||
header: {
|
||||
text: null,
|
||||
background: "green",
|
||||
background: 'green'
|
||||
},
|
||||
session: {
|
||||
name: "WebSSH2",
|
||||
secret: "mysecret",
|
||||
name: 'WebSSH2',
|
||||
secret: 'mysecret'
|
||||
},
|
||||
options: {
|
||||
challengeButton: true,
|
||||
allowreauth: true,
|
||||
allowreauth: true
|
||||
},
|
||||
algorithms: {
|
||||
kex: [
|
||||
"ecdh-sha2-nistp256",
|
||||
"ecdh-sha2-nistp384",
|
||||
"ecdh-sha2-nistp521",
|
||||
"diffie-hellman-group-exchange-sha256",
|
||||
"diffie-hellman-group14-sha1",
|
||||
'ecdh-sha2-nistp256',
|
||||
'ecdh-sha2-nistp384',
|
||||
'ecdh-sha2-nistp521',
|
||||
'diffie-hellman-group-exchange-sha256',
|
||||
'diffie-hellman-group14-sha1'
|
||||
],
|
||||
cipher: [
|
||||
"aes128-ctr",
|
||||
"aes192-ctr",
|
||||
"aes256-ctr",
|
||||
"aes128-gcm",
|
||||
"aes128-gcm@openssh.com",
|
||||
"aes256-gcm",
|
||||
"aes256-gcm@openssh.com",
|
||||
"aes256-cbc",
|
||||
'aes128-ctr',
|
||||
'aes192-ctr',
|
||||
'aes256-ctr',
|
||||
'aes128-gcm',
|
||||
'aes128-gcm@openssh.com',
|
||||
'aes256-gcm',
|
||||
'aes256-gcm@openssh.com',
|
||||
'aes256-cbc'
|
||||
],
|
||||
hmac: ["hmac-sha2-256", "hmac-sha2-512", "hmac-sha1"],
|
||||
compress: ["none", "zlib@openssh.com", "zlib"],
|
||||
hmac: ['hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'],
|
||||
compress: ['none', 'zlib@openssh.com', 'zlib']
|
||||
},
|
||||
serverlog: {
|
||||
client: false,
|
||||
server: false,
|
||||
server: false
|
||||
},
|
||||
accesslog: false,
|
||||
verify: false,
|
||||
};
|
||||
verify: false
|
||||
}
|
||||
|
||||
try {
|
||||
if (fs.existsSync(configPath)) {
|
||||
console.log("WebSSH2 service reading config from: " + configPath);
|
||||
config = require("read-config-ng")(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: " +
|
||||
'\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('\n See config.json.sample for details\n\n')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"\n\nERROR: Missing config.json for webssh. Current config: " +
|
||||
'\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);
|
||||
)
|
||||
console.error('\n See config.json.sample for details\n\n')
|
||||
console.error('ERROR:\n\n ' + err)
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
module.exports = config
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
// app/expressOptions.js
|
||||
module.exports = {
|
||||
dotfiles: "ignore",
|
||||
etag: false,
|
||||
extensions: ["htm", "html"],
|
||||
index: false,
|
||||
maxAge: "1s",
|
||||
redirect: false,
|
||||
setHeaders: function (res, path, stat) {
|
||||
res.set("x-timestamp", Date.now());
|
||||
},
|
||||
};
|
144
app/socket.js
144
app/socket.js
|
@ -1,77 +1,77 @@
|
|||
// app/socket.js
|
||||
"use strict";
|
||||
'use strict'
|
||||
|
||||
const debug = require("debug");
|
||||
const debugWebSSH2 = require("debug")("WebSSH2");
|
||||
const SSH = require("ssh2").Client;
|
||||
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}`);
|
||||
io.on('connection', (socket) => {
|
||||
let conn = null
|
||||
let stream = null
|
||||
console.log(`SOCKET CONNECT: ${socket.id}`)
|
||||
|
||||
// Remove existing listeners to prevent duplicates
|
||||
socket.removeAllListeners("authenticate");
|
||||
socket.removeAllListeners("data");
|
||||
socket.removeAllListeners("resize");
|
||||
socket.removeAllListeners("disconnect");
|
||||
socket.removeAllListeners('authenticate')
|
||||
socket.removeAllListeners('data')
|
||||
socket.removeAllListeners('resize')
|
||||
socket.removeAllListeners('disconnect')
|
||||
|
||||
// Authenticate user
|
||||
socket.on("authenticate", (credentials) => {
|
||||
console.log(`SOCKET AUTHENTICATE: ${socket.id}`);
|
||||
socket.on('authenticate', (credentials) => {
|
||||
console.log(`SOCKET AUTHENTICATE: ${socket.id}`)
|
||||
if (isValidCredentials(credentials)) {
|
||||
console.log(`SOCKET AUTHENTICATE SUCCESS: ${socket.id}`);
|
||||
initializeConnection(socket, credentials);
|
||||
console.log(`SOCKET AUTHENTICATE SUCCESS: ${socket.id}`)
|
||||
initializeConnection(socket, credentials)
|
||||
} else {
|
||||
console.log(`SOCKET AUTHENTICATE FAILED: ${socket.id}`);
|
||||
socket.emit("auth_result", {
|
||||
console.log(`SOCKET AUTHENTICATE FAILED: ${socket.id}`)
|
||||
socket.emit('auth_result', {
|
||||
success: false,
|
||||
message: "Invalid credentials",
|
||||
});
|
||||
message: 'Invalid credentials'
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
socket.on("disconnect", (reason) => {
|
||||
debugWebSSH2(`SOCKET DISCONNECT: ${socket.id}, Reason: ${reason}`);
|
||||
socket.on('disconnect', (reason) => {
|
||||
debugWebSSH2(`SOCKET DISCONNECT: ${socket.id}, Reason: ${reason}`)
|
||||
if (conn) {
|
||||
conn.end();
|
||||
conn.end()
|
||||
}
|
||||
// Clean up listeners
|
||||
socket.removeAllListeners();
|
||||
});
|
||||
socket.removeAllListeners()
|
||||
})
|
||||
|
||||
socket.on("data", (data) => {
|
||||
socket.on('data', (data) => {
|
||||
if (stream) {
|
||||
stream.write(data);
|
||||
stream.write(data)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
socket.on("resize", (data) => {
|
||||
socket.on('resize', (data) => {
|
||||
if (stream) {
|
||||
stream.setWindow(data.rows, data.cols);
|
||||
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.end()
|
||||
}
|
||||
|
||||
conn = new SSH();
|
||||
conn = new SSH()
|
||||
|
||||
conn.on("ready", () => {
|
||||
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");
|
||||
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(
|
||||
{
|
||||
|
@ -81,37 +81,37 @@ module.exports = function (io) {
|
|||
},
|
||||
(err, str) => {
|
||||
if (err) {
|
||||
return SSHerror("EXEC ERROR", err);
|
||||
return SSHerror('EXEC ERROR', err)
|
||||
}
|
||||
stream = str;
|
||||
stream = str
|
||||
|
||||
stream.on("data", (data) => {
|
||||
socket.emit("data", data.toString("utf-8"));
|
||||
});
|
||||
stream.on('data', (data) => {
|
||||
socket.emit('data', data.toString('utf-8'))
|
||||
})
|
||||
|
||||
stream.on("close", (code, signal) => {
|
||||
SSHerror("STREAM CLOSE", {
|
||||
stream.on('close', (code, signal) => {
|
||||
SSHerror('STREAM CLOSE', {
|
||||
message:
|
||||
code || signal
|
||||
? `CODE: ${code} SIGNAL: ${signal}`
|
||||
: undefined,
|
||||
});
|
||||
});
|
||||
: undefined
|
||||
})
|
||||
})
|
||||
|
||||
stream.stderr.on("data", (data) => {
|
||||
console.log("STDERR: " + data);
|
||||
});
|
||||
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('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.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,
|
||||
|
@ -123,16 +123,16 @@ module.exports = function (io) {
|
|||
readyTimeout: credentials.readyTimeout,
|
||||
keepaliveInterval: credentials.keepaliveInterval,
|
||||
keepaliveCountMax: credentials.keepaliveCountMax,
|
||||
debug: debug("ssh2")
|
||||
});
|
||||
debug: debug('ssh2')
|
||||
})
|
||||
}
|
||||
|
||||
function SSHerror (myFunc, err) {
|
||||
const errorMessage = err ? `: ${err.message}` : "";
|
||||
console.log(`WebSSH2 error: ${myFunc}${errorMessage}`);
|
||||
socket.emit("ssherror", `SSH ${myFunc}${errorMessage}`);
|
||||
const errorMessage = err ? `: ${err.message}` : ''
|
||||
console.log(`WebSSH2 error: ${myFunc}${errorMessage}`)
|
||||
socket.emit('ssherror', `SSH ${myFunc}${errorMessage}`)
|
||||
if (conn) {
|
||||
conn.end();
|
||||
conn.end()
|
||||
}
|
||||
// Don't disconnect the socket here, let the client handle reconnection if necessary
|
||||
// socket.disconnect(true);
|
||||
|
@ -140,7 +140,7 @@ module.exports = function (io) {
|
|||
|
||||
function isValidCredentials (credentials) {
|
||||
// Implement your credential validation logic here
|
||||
return credentials && credentials.username && credentials.password;
|
||||
return credentials && credentials.username && credentials.password
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
13
app/util.js
13
app/util.js
|
@ -1,13 +0,0 @@
|
|||
"use strict";
|
||||
// app/util.js
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
|
||||
// private
|
||||
require("colors"); // allow for color property extensions in log messages
|
||||
var debug = require("debug")("WebSSH2");
|
||||
var Auth = require("basic-auth");
|
||||
|
||||
// takes a string, makes it boolean (true if the string is true, false otherwise)
|
||||
exports.parseBool = function parseBool(str) {
|
||||
return str.toLowerCase() === "true";
|
||||
};
|
24
index.js
24
index.js
|
@ -1,4 +1,4 @@
|
|||
"use strict";
|
||||
'use strict'
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
/*
|
||||
* index.js
|
||||
|
@ -7,24 +7,14 @@
|
|||
* Bill Church - https://github.com/billchurch/WebSSH2 - May 2017
|
||||
*
|
||||
*/
|
||||
const { server, config } = require("./app/app");
|
||||
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}`
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
server.on("error", function (err) {
|
||||
if (err.code === "EADDRINUSE") {
|
||||
config.listen.port++;
|
||||
console.warn(
|
||||
"WebSSH2 Address in use, retrying on port " + config.listen.port
|
||||
);
|
||||
setTimeout(function () {
|
||||
server.listen(config.listen.port);
|
||||
}, 250);
|
||||
} else {
|
||||
console.log("WebSSH2 server.listen ERROR: " + err.code);
|
||||
}
|
||||
});
|
||||
server.on('error', function (err) {
|
||||
console.log('WebSSH2 server.listen ERROR: ' + err.code)
|
||||
})
|
||||
|
|
4434
package-lock.json
generated
Normal file
4434
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "webssh2-server",
|
||||
"version": "0.2.13",
|
||||
"version": "0.2.14",
|
||||
"ignore": [
|
||||
".gitignore"
|
||||
],
|
||||
|
@ -32,16 +32,10 @@
|
|||
"url": "https://github.com/billchurch/WebSSH2/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"basic-auth": "~2.0.1",
|
||||
"debug": "~4.1.0",
|
||||
"express": "~4.16.4",
|
||||
"express-session": "~1.15.6",
|
||||
"morgan": "~1.9.1",
|
||||
"read-config-ng": "~3.0.7",
|
||||
"serve-favicon": "~2.5.0",
|
||||
"socket.io": "~2.2.0",
|
||||
"ssh2": "~0.8.9",
|
||||
"validator": "~10.9.0"
|
||||
"ssh2": "~0.8.9"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
var express = require("express");
|
||||
var app = express();
|
||||
var server = require("http").createServer(app);
|
||||
var io = require("socket.io")(server, {
|
||||
path: "/ssh/socket.io",
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
var server = require('http').createServer(app)
|
||||
var io = require('socket.io')(server, {
|
||||
path: '/ssh/socket.io',
|
||||
cors: {
|
||||
origin: "http://localhost:8080",
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
origin: 'http://localhost:8080',
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true
|
||||
}
|
||||
})
|
||||
|
||||
var PORT = 3000;
|
||||
var PORT = 3000
|
||||
|
||||
io.on("connection", function (socket) {
|
||||
console.log("A client connected");
|
||||
io.on('connection', function (socket) {
|
||||
console.log('A client connected')
|
||||
|
||||
socket.on("authenticate", function (credentials) {
|
||||
console.log("Received credentials:", credentials);
|
||||
socket.on('authenticate', function (credentials) {
|
||||
console.log('Received credentials:', credentials)
|
||||
|
||||
// Here you would typically validate the credentials
|
||||
// For this example, we'll just echo back a success message
|
||||
var authResult = {
|
||||
success: true,
|
||||
message: "Authentication successful",
|
||||
};
|
||||
message: 'Authentication successful'
|
||||
}
|
||||
|
||||
socket.emit("auth_result", authResult);
|
||||
});
|
||||
socket.emit('auth_result', authResult)
|
||||
})
|
||||
|
||||
socket.on("disconnect", function () {
|
||||
console.log("A client disconnected");
|
||||
});
|
||||
});
|
||||
socket.on('disconnect', function () {
|
||||
console.log('A client disconnected')
|
||||
})
|
||||
})
|
||||
|
||||
server.listen(PORT, function () {
|
||||
console.log("Server running on port " + PORT);
|
||||
});
|
||||
console.log('Server running on port ' + PORT)
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue