-
Notifications
You must be signed in to change notification settings - Fork 537
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: lint ./app/server/app.js still TODO for stop function #242
- Loading branch information
1 parent
36e4e68
commit ec2fcfb
Showing
1 changed file
with
134 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,30 @@ | ||
'use strict' | ||
/* jshint esversion: 6, asi: true, node: true */ | ||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }] */ | ||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }], | ||
no-console: ["error", { allow: ["warn", "error"] }] */ | ||
// app.js | ||
|
||
const path = require('path') | ||
const fs = require('fs') | ||
const nodeRoot = path.dirname(require.main.filename) | ||
const configPath = path.join(nodeRoot, 'config.json') | ||
const publicPath = path.join(nodeRoot, 'client', 'public') | ||
console.log('WebSSH2 service reading config from: ' + configPath) | ||
const express = require('express') | ||
const logger = require('morgan') | ||
const path = require('path'); | ||
const fs = require('fs'); | ||
|
||
const nodeRoot = path.dirname(require.main.filename); | ||
const configPath = path.join(nodeRoot, 'config.json'); | ||
const publicPath = path.join(nodeRoot, 'client', 'public'); | ||
const express = require('express'); | ||
const logger = require('morgan'); | ||
|
||
// sane defaults if config.json or parts are missing | ||
let config = { | ||
listen: { | ||
ip: '0.0.0.0', | ||
port: 2222 | ||
port: 2222, | ||
}, | ||
http: { | ||
origins: ['localhost:2222'] | ||
origins: ['localhost:2222'], | ||
}, | ||
user: { | ||
name: null, | ||
password: null, | ||
privatekey: null | ||
privatekey: null, | ||
}, | ||
ssh: { | ||
host: null, | ||
|
@@ -33,33 +33,33 @@ let config = { | |
readyTimeout: 20000, | ||
keepaliveInterval: 120000, | ||
keepaliveCountMax: 10, | ||
allowedSubnets: [] | ||
allowedSubnets: [], | ||
}, | ||
terminal: { | ||
cursorBlink: true, | ||
scrollback: 10000, | ||
tabStopWidth: 8, | ||
bellStyle: 'sound' | ||
bellStyle: 'sound', | ||
}, | ||
header: { | ||
text: null, | ||
background: 'green' | ||
background: 'green', | ||
}, | ||
session: { | ||
name: 'WebSSH2', | ||
secret: 'mysecret' | ||
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' | ||
'diffie-hellman-group14-sha1', | ||
], | ||
cipher: [ | ||
'aes128-ctr', | ||
|
@@ -69,193 +69,202 @@ let config = { | |
'[email protected]', | ||
'aes256-gcm', | ||
'[email protected]', | ||
'aes256-cbc' | ||
'aes256-cbc', | ||
], | ||
hmac: [ | ||
'hmac-sha2-256', | ||
'hmac-sha2-512', | ||
'hmac-sha1' | ||
'hmac-sha1', | ||
], | ||
compress: [ | ||
'none', | ||
'[email protected]', | ||
'zlib' | ||
] | ||
'zlib', | ||
], | ||
}, | ||
serverlog: { | ||
client: false, | ||
server: false | ||
server: false, | ||
}, | ||
accesslog: false, | ||
verify: false, | ||
safeShutdownDuration: 300 | ||
} | ||
safeShutdownDuration: 300, | ||
}; | ||
|
||
// test if config.json exists, if not provide error message but try to run | ||
// anyway | ||
try { | ||
if (fs.existsSync(configPath)) { | ||
console.log('ephemeral_auth service reading config from: ' + configPath) | ||
// eslint-disable-next-line no-console | ||
console.log(`webssh2 service reading config from: ${configPath}`); | ||
config = require('read-config-ng')(configPath) // eslint-disable-line | ||
} 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') | ||
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) | ||
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}`); | ||
} | ||
|
||
const session = require('express-session')({ | ||
secret: config.session.secret, | ||
name: config.session.name, | ||
resave: true, | ||
saveUninitialized: false, | ||
unset: 'destroy' | ||
}) | ||
const app = express() | ||
const server = require('http').Server(app) | ||
const myutil = require('./util') | ||
myutil.setDefaultCredentials(config.user.name, config.user.password, config.user.privatekey) | ||
const validator = require('validator') | ||
const io = require('socket.io')(server, { serveClient: false, path: '/ssh/socket.io', origins: config.http.origins }) | ||
const socket = require('./socket') | ||
const expressOptions = require('./expressOptions') | ||
const favicon = require('serve-favicon') | ||
unset: 'destroy', | ||
}); | ||
|
||
const app = express(); | ||
const server = require('http').Server(app); | ||
|
||
const validator = require('validator'); | ||
const io = require('socket.io')(server, { serveClient: false, path: '/ssh/socket.io', origins: config.http.origins }); | ||
const favicon = require('serve-favicon'); | ||
const socket = require('./socket'); | ||
const expressOptions = require('./expressOptions'); | ||
const myutil = require('./util'); | ||
|
||
myutil.setDefaultCredentials(config.user.name, config.user.password, config.user.privatekey); | ||
// express | ||
app.use(safeShutdownGuard) | ||
app.use(session) | ||
app.use(myutil.basicAuth) | ||
if (config.accesslog) app.use(logger('common')) | ||
app.disable('x-powered-by') | ||
app.use(safeShutdownGuard); | ||
app.use(session); | ||
app.use(myutil.basicAuth); | ||
if (config.accesslog) app.use(logger('common')); | ||
app.disable('x-powered-by'); | ||
|
||
// static files | ||
app.use('/ssh', express.static(publicPath, expressOptions)) | ||
app.use('/ssh', express.static(publicPath, expressOptions)); | ||
|
||
// favicon from root if being pre-fetched by browser to prevent a 404 | ||
app.use(favicon(path.join(publicPath, 'favicon.ico'))) | ||
app.use(favicon(path.join(publicPath, 'favicon.ico'))); | ||
|
||
app.get('/ssh/reauth', function (req, res, next) { | ||
const r = req.headers.referer || '/' | ||
res.status(401).send('<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=' + r + '"></head><body bgcolor="#000"></body></html>') | ||
}) | ||
app.get('/ssh/reauth', (req, res, next) => { | ||
const r = req.headers.referer || '/'; | ||
res.status(401).send(`<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${r}"></head><body bgcolor="#000"></body></html>`); | ||
next(); | ||
}); | ||
|
||
// eslint-disable-next-line complexity | ||
app.get('/ssh/host/:host?', function (req, res, next) { | ||
res.sendFile(path.join(path.join(publicPath, 'client.htm'))) | ||
app.get('/ssh/host/:host?', (req, res, next) => { | ||
res.sendFile(path.join(path.join(publicPath, 'client.htm'))); | ||
// capture, assign, and validated variables | ||
req.session.ssh = { | ||
host: config.ssh.host || (validator.isIP(req.params.host + '') && req.params.host) || | ||
(validator.isFQDN(req.params.host) && req.params.host) || | ||
(/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.params.host) && | ||
req.params.host), | ||
port: (validator.isInt(req.query.port + '', { min: 1, max: 65535 }) && | ||
req.query.port) || config.ssh.port, | ||
host: config.ssh.host || (validator.isIP(`${req.params.host}`) && req.params.host) | ||
|| (validator.isFQDN(req.params.host) && req.params.host) | ||
|| (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.params.host) | ||
&& req.params.host), | ||
port: (validator.isInt(`${req.query.port}`, { min: 1, max: 65535 }) | ||
&& req.query.port) || config.ssh.port, | ||
localAddress: config.ssh.localAddress, | ||
localPort: config.ssh.localPort, | ||
header: { | ||
name: req.query.header || config.header.text, | ||
background: req.query.headerBackground || config.header.background | ||
background: req.query.headerBackground || config.header.background, | ||
}, | ||
algorithms: config.algorithms, | ||
keepaliveInterval: config.ssh.keepaliveInterval, | ||
keepaliveCountMax: config.ssh.keepaliveCountMax, | ||
allowedSubnets: config.ssh.allowedSubnets, | ||
term: (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm) && | ||
req.query.sshterm) || config.ssh.term, | ||
term: (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm) | ||
&& req.query.sshterm) || config.ssh.term, | ||
terminal: { | ||
cursorBlink: (validator.isBoolean(req.query.cursorBlink + '') ? myutil.parseBool(req.query.cursorBlink) : config.terminal.cursorBlink), | ||
scrollback: (validator.isInt(req.query.scrollback + '', { min: 1, max: 200000 }) && req.query.scrollback) ? req.query.scrollback : config.terminal.scrollback, | ||
tabStopWidth: (validator.isInt(req.query.tabStopWidth + '', { min: 1, max: 100 }) && req.query.tabStopWidth) ? req.query.tabStopWidth : config.terminal.tabStopWidth, | ||
bellStyle: ((req.query.bellStyle) && (['sound', 'none'].indexOf(req.query.bellStyle) > -1)) ? req.query.bellStyle : config.terminal.bellStyle | ||
cursorBlink: (validator.isBoolean(`${req.query.cursorBlink}`) ? myutil.parseBool(req.query.cursorBlink) : config.terminal.cursorBlink), | ||
scrollback: (validator.isInt(`${req.query.scrollback}`, { min: 1, max: 200000 }) && req.query.scrollback) ? req.query.scrollback : config.terminal.scrollback, | ||
tabStopWidth: (validator.isInt(`${req.query.tabStopWidth}`, { min: 1, max: 100 }) && req.query.tabStopWidth) ? req.query.tabStopWidth : config.terminal.tabStopWidth, | ||
bellStyle: ((req.query.bellStyle) && (['sound', 'none'].indexOf(req.query.bellStyle) > -1)) ? req.query.bellStyle : config.terminal.bellStyle, | ||
}, | ||
allowreplay: config.options.challengeButton || (validator.isBoolean(req.headers.allowreplay + '') ? myutil.parseBool(req.headers.allowreplay) : false), | ||
allowreplay: config.options.challengeButton || (validator.isBoolean(`${req.headers.allowreplay}`) ? myutil.parseBool(req.headers.allowreplay) : false), | ||
allowreauth: config.options.allowreauth || false, | ||
mrhsession: ((validator.isAlphanumeric(req.headers.mrhsession + '') && req.headers.mrhsession) ? req.headers.mrhsession : 'none'), | ||
mrhsession: ((validator.isAlphanumeric(`${req.headers.mrhsession}`) && req.headers.mrhsession) ? req.headers.mrhsession : 'none'), | ||
serverlog: { | ||
client: config.serverlog.client || false, | ||
server: config.serverlog.server || false | ||
server: config.serverlog.server || false, | ||
}, | ||
readyTimeout: (validator.isInt(req.query.readyTimeout + '', { min: 1, max: 300000 }) && | ||
req.query.readyTimeout) || config.ssh.readyTimeout | ||
} | ||
if (req.session.ssh.header.name) validator.escape(req.session.ssh.header.name) | ||
if (req.session.ssh.header.background) validator.escape(req.session.ssh.header.background) | ||
}) | ||
readyTimeout: (validator.isInt(`${req.query.readyTimeout}`, { min: 1, max: 300000 }) | ||
&& req.query.readyTimeout) || config.ssh.readyTimeout, | ||
}; | ||
if (req.session.ssh.header.name) validator.escape(req.session.ssh.header.name); | ||
if (req.session.ssh.header.background) validator.escape(req.session.ssh.header.background); | ||
}); | ||
|
||
// express error handling | ||
app.use(function (req, res, next) { | ||
res.status(404).send("Sorry can't find that!") | ||
}) | ||
app.use((req, res, next) => { | ||
res.status(404).send("Sorry can't find that!"); | ||
next(); | ||
}); | ||
|
||
app.use(function (err, req, res, next) { | ||
console.error(err.stack) | ||
res.status(500).send('Something broke!') | ||
}) | ||
app.use((err, req, res, next) => { | ||
console.error(err.stack); | ||
res.status(500).send('Something broke!'); | ||
next(); | ||
}); | ||
|
||
// socket.io | ||
// expose express session with socket.request.session | ||
io.use(function (socket, next) { | ||
io.use((socket, next) => { | ||
(socket.request.res) | ||
? session(socket.request, socket.request.res, next) | ||
: next(next) // eslint disable-line | ||
}) | ||
: next(next); // eslint disable-line | ||
}); | ||
|
||
// bring up socket | ||
io.on('connection', socket) | ||
io.on('connection', socket); | ||
|
||
// safe shutdown | ||
let shutdownMode = false | ||
let shutdownInterval = 0 | ||
let connectionCount = 0 | ||
let shutdownMode = false; | ||
let shutdownInterval = 0; | ||
let connectionCount = 0; | ||
|
||
function safeShutdownGuard (req, res, next) { | ||
if (shutdownMode) res.status(503).end('Service unavailable: Server shutting down') | ||
else return next() | ||
function safeShutdownGuard(req, res, next) { | ||
if (shutdownMode) res.status(503).end('Service unavailable: Server shutting down'); | ||
else return next(); | ||
} | ||
|
||
io.on('connection', function (socket) { | ||
connectionCount++ | ||
io.on('connection', (socket) => { | ||
connectionCount += 1; | ||
|
||
socket.on('disconnect', function () { | ||
if ((--connectionCount <= 0) && shutdownMode) { | ||
stop('All clients disconnected') | ||
socket.on('disconnect', () => { | ||
connectionCount -= 1; | ||
if ((connectionCount <= 0) && shutdownMode) { | ||
stop('All clients disconnected'); | ||
} | ||
}) | ||
}) | ||
}); | ||
}); | ||
|
||
const signals = ['SIGTERM', 'SIGINT'] | ||
signals.forEach(signal => process.on(signal, function () { | ||
if (shutdownMode) stop('Safe shutdown aborted, force quitting') | ||
const signals = ['SIGTERM', 'SIGINT']; | ||
signals.forEach((signal) => process.on(signal, () => { | ||
if (shutdownMode) stop('Safe shutdown aborted, force quitting'); | ||
else if (connectionCount > 0) { | ||
let remainingSeconds = config.safeShutdownDuration | ||
shutdownMode = true | ||
let remainingSeconds = config.safeShutdownDuration; | ||
shutdownMode = true; | ||
const message = (connectionCount === 1) | ||
? ' client is still connected' | ||
: ' clients are still connected' | ||
console.error(connectionCount + message) | ||
console.error('Starting a ' + remainingSeconds + ' seconds countdown') | ||
console.error('Press Ctrl+C again to force quit') | ||
: ' clients are still connected'; | ||
console.error(connectionCount + message); | ||
console.error(`Starting a ${remainingSeconds} seconds countdown`); | ||
console.error('Press Ctrl+C again to force quit'); | ||
|
||
shutdownInterval = setInterval(function () { | ||
if ((remainingSeconds--) <= 0) { | ||
stop('Countdown is over') | ||
shutdownInterval = setInterval(() => { | ||
remainingSeconds -= 1; | ||
if ((remainingSeconds) <= 0) { | ||
stop('Countdown is over'); | ||
} else { | ||
io.sockets.emit('shutdownCountdownUpdate', remainingSeconds) | ||
io.sockets.emit('shutdownCountdownUpdate', remainingSeconds); | ||
} | ||
}, 1000) | ||
} else stop() | ||
})) | ||
}, 1000); | ||
} else stop(); | ||
})); | ||
|
||
// clean stop | ||
function stop (reason) { | ||
shutdownMode = false | ||
if (reason) console.log('Stopping: ' + reason) | ||
if (shutdownInterval) clearInterval(shutdownInterval) | ||
io.close() | ||
server.close() | ||
function stop(reason) { | ||
shutdownMode = false; | ||
// eslint-disable-next-line no-console | ||
if (reason) console.log(`Stopping: ${reason}`); | ||
if (shutdownInterval) clearInterval(shutdownInterval); | ||
io.close(); | ||
server.close(); | ||
} | ||
|
||
module.exports = { server: server, config: config } | ||
module.exports = { server, config }; |