webssh2/app/server/app.js
2021-05-20 13:18:59 -04:00

174 lines
5 KiB
JavaScript

// app.js
// eslint-disable-next-line import/order
const config = require('./config');
const path = require('path');
const debug = require('debug')('webssh2');
require('colors');
// allow for color property extensions in log messages
const nodeRoot = path.dirname(require.main.filename);
const publicPath = path.join(nodeRoot, 'client', 'public');
const express = require('express');
const logger = require('morgan');
const passport = require('passport');
const { BasicStrategy } = require('passport-http');
const CustomStrategy = require('passport-custom').Strategy;
const LocalStrategy = require('passport-local').Strategy;
const app = express();
const server = require('http').Server(app);
const favicon = require('serve-favicon');
const io = require('socket.io')(server, {
serveClient: false,
path: '/ssh/socket.io',
origins: config.http.origins,
});
const session = require('express-session')(config.session);
const { setupSession } = require('./setupSession');
const appSocket = require('./socket');
const expressOptions = require('./expressOptions');
const safeShutdown = require('./safeShutdown');
// Static credentials strategy
// when config.user.overridebasic is true, those credentials
// are used instead of HTTP basic auth.
passport.use(
'overridebasic',
new CustomStrategy((req, done) => {
if (config.user.overridebasic) {
const user = {
username: config.user.name,
password: config.user.password,
privatekey: config.user.privatekey,
};
debug(
`overridebasic username: ${user.username.yellow.bold.underline} and password ${
user.password ? 'exists'.yellow.bold.underline : 'is blank'.underline.red.bold
}`
);
return done(null, user);
}
return done(null, false);
})
);
// Basic auth strategy
passport.use(
new BasicStrategy((username, password, done) => {
const user = {
username,
password,
};
debug(
`HTTP Basic username: ${user.username.yellow.bold.underline} and password ${
user.password ? 'exists'.yellow.bold.underline : 'is blank'.underline.red.bold
}`
);
return done(null, user);
})
);
// Local auth strategy
// for taking credentials from GET/POST
passport.use(
new LocalStrategy((username, password, done) => {
const user = {
username,
password,
};
debug(
`HTTP GET/POST username: ${user.username.yellow.bold.underline} and password ${
user.password ? 'exists'.yellow.bold.underline : 'is blank'.underline.red.bold
}`
);
return done(null, user);
})
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
module.exports = { server, config };
// express
app.use(safeShutdown.safeShutdownGuard);
app.use(session);
app.use(passport.initialize());
app.use(passport.session());
if (config.accesslog) app.use(logger('common'));
app.disable('x-powered-by');
// static files
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')));
// this is currently broken due to the way passport works with Basic Auth...
// maybe this should never have worked in the first place
app.get('/ssh/reauth', (req, res) => {
const r = req.headers.referer || '/';
req.logout();
req.session.destroy();
res
.status(401)
.send(
`<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${r}"></head><body bgcolor="#000"></body></html>`
);
});
// This route allows for collection of credentials from POST/GET
app.get(
'/ssh/login/host/:host?',
passport.authenticate(['overridebasic', 'local'], { session: true }),
(req, res) => {
setupSession(req, config);
res.sendFile(path.join(path.join(publicPath, 'client.htm')));
}
);
// This route allows for collection of credentials from HTTP Basic
app.get(
'/ssh/host/:host?',
passport.authenticate(['overridebasic', 'basic'], { session: true }),
(req, res) => {
setupSession(req, config);
res.sendFile(path.join(path.join(publicPath, 'client.htm')));
}
);
// express error handling
app.use((req, res) => {
res.status(404).send("Sorry, can't find that!");
});
app.use((err, req, res) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
// bring up socket
io.on('connection', appSocket);
// socket.io
// expose express session with socket.request.session
io.use((socket, next) => {
socket.request.res ? session(socket.request, socket.request.res, next) : next(next);
});
io.on('connection', (socket) => {
socket.on('disconnect', () => {
if (io.of('/').sockets.size <= 1 && safeShutdown.shutdownMode) {
safeShutdown.stopApp(io, server, 'All clients disconnected');
}
});
});
// trap SIGTERM and SIGINT (CTRL-C) and handle shutdown gracefully
const signals = ['SIGTERM', 'SIGINT'];
signals.forEach((signal) => process.on(signal, () => safeShutdown.doShutdown(io, server, config)));