Files
cloudron-box/src/server.js

333 lines
19 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
start: start,
stop: stop
};
2016-05-24 09:40:26 -07:00
var assert = require('assert'),
async = require('async'),
auth = require('./auth.js'),
2016-04-25 10:26:26 -07:00
clients = require('./clients.js'),
cloudron = require('./cloudron.js'),
config = require('./config.js'),
database = require('./database.js'),
eventlog = require('./eventlog.js'),
express = require('express'),
2017-04-13 11:05:46 -07:00
hat = require('hat'),
http = require('http'),
middleware = require('./middleware'),
passport = require('passport'),
path = require('path'),
2017-04-13 11:05:46 -07:00
routes = require('./routes/index.js');
var gHttpServer = null;
2016-04-15 12:33:54 -07:00
var gSysadminHttpServer = null;
function initializeExpressSync() {
var app = express();
var httpServer = http.createServer(app);
// enabled websocket handling
require('express-ws')(app, httpServer);
var QUERY_LIMIT = '1mb', // max size for json and urlencoded queries (see also client_max_body_size in nginx)
FIELD_LIMIT = 2 * 1024 * 1024; // max fields that can appear in multipart
2016-01-18 12:20:09 -08:00
var REQUEST_TIMEOUT = 10000; // timeout for all requests (see also setTimeout on the httpServer)
var json = middleware.json({ strict: true, limit: QUERY_LIMIT }), // application/json
urlencoded = middleware.urlencoded({ extended: false, limit: QUERY_LIMIT }); // application/x-www-form-urlencoded
app.set('views', path.join(__dirname, 'oauth2views'));
2016-02-18 18:00:23 +01:00
app.set('view options', { layout: true, debug: false });
app.set('view engine', 'ejs');
2016-05-31 11:14:59 -07:00
app.set('json spaces', 2); // pretty json
// for rate limiting
app.enable('trust proxy');
2017-04-10 14:17:26 +02:00
if (process.env.BOX_ENV !== 'test') {
app.use(middleware.morgan('Box :method :url :status :response-time ms - :res[content-length]', {
immediate: false,
// only log failed requests by default
skip: function (req, res) { return res.statusCode < 400; }
}));
}
var router = new express.Router();
router.del = router.delete; // amend router.del for readability further on
app
.use(middleware.timeout(REQUEST_TIMEOUT))
.use(json)
.use(urlencoded)
.use(middleware.cookieParser())
.use(middleware.cors({ origins: [ '*' ], allowCredentials: false }))
2017-04-13 15:13:05 +02:00
.use(middleware.session({
2017-04-13 11:05:46 -07:00
secret: hat(128), // we only use the session during oauth, and already have an in-memory session store, so we can safely change that during restarts
2017-04-13 15:13:05 +02:00
resave: true,
saveUninitialized: true,
cookie: {
path: '/',
httpOnly: true,
secure: process.env.BOX_ENV !== 'test',
2017-04-13 15:13:05 +02:00
maxAge: 600000
}
}))
.use(passport.initialize())
.use(passport.session())
.use(router)
.use(middleware.lastMile());
// NOTE: these limits have to be in sync with nginx limits
var FILE_SIZE_LIMIT = '1mb', // max file size that can be uploaded (see also client_max_body_size in nginx)
FILE_TIMEOUT = 60 * 1000; // increased timeout for file uploads (1 min)
var multipart = middleware.multipart({ maxFieldsSize: FIELD_LIMIT, limit: FILE_SIZE_LIMIT, timeout: FILE_TIMEOUT });
// scope middleware implicitly also adds bearer token verification
var cloudronScope = routes.oauth2.scope(clients.SCOPE_CLOUDRON);
2016-04-25 10:26:26 -07:00
var profileScope = routes.oauth2.scope(clients.SCOPE_PROFILE);
var usersScope = routes.oauth2.scope(clients.SCOPE_USERS);
var appsScope = routes.oauth2.scope(clients.SCOPE_APPS);
var developerScope = routes.oauth2.scope(clients.SCOPE_DEVELOPER);
var settingsScope = routes.oauth2.scope(clients.SCOPE_SETTINGS);
// csrf protection
var csrf = routes.oauth2.csrf;
// public routes
router.post('/api/v1/cloudron/activate', routes.cloudron.setupTokenAuth, routes.cloudron.activate);
router.post('/api/v1/cloudron/dns_setup', routes.cloudron.providerTokenAuth, routes.cloudron.dnsSetup); // only available until no-domain
router.get ('/api/v1/cloudron/progress', routes.cloudron.getProgress);
router.get ('/api/v1/cloudron/status', routes.cloudron.getStatus);
router.get ('/api/v1/cloudron/avatar', routes.settings.getCloudronAvatar); // this is a public alias for /api/v1/settings/cloudron_avatar
// developer routes
router.post('/api/v1/developer', developerScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.developer.setEnabled);
router.get ('/api/v1/developer', developerScope, routes.developer.enabled, routes.developer.status);
router.post('/api/v1/developer/login', routes.developer.enabled, routes.developer.login);
router.get ('/api/v1/developer/apps', developerScope, routes.developer.enabled, routes.developer.apps);
// cloudron routes
router.get ('/api/v1/cloudron/config', cloudronScope, routes.cloudron.getConfig);
router.post('/api/v1/cloudron/update', cloudronScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.cloudron.update);
2016-06-08 00:07:41 -07:00
router.post('/api/v1/cloudron/check_for_updates', cloudronScope, routes.user.requireAdmin, routes.cloudron.checkForUpdates);
router.post('/api/v1/cloudron/reboot', cloudronScope, routes.user.requireAdmin, routes.cloudron.reboot);
2016-06-27 22:24:30 -05:00
router.post('/api/v1/cloudron/migrate', cloudronScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.cloudron.migrate);
router.get ('/api/v1/cloudron/graphs', cloudronScope, routes.user.requireAdmin, routes.graphs.getGraphs);
router.get ('/api/v1/cloudron/disks', cloudronScope, routes.user.requireAdmin, routes.cloudron.getDisks);
router.get ('/api/v1/cloudron/logs', cloudronScope, routes.user.requireAdmin, routes.cloudron.getLogs);
2017-08-07 16:49:37 +02:00
router.get ('/api/v1/cloudron/logstream', cloudronScope, routes.user.requireAdmin, routes.cloudron.getLogStream);
router.get ('/api/v1/cloudron/ssh/authorized_keys', cloudronScope, routes.user.requireAdmin, routes.ssh.getAuthorizedKeys);
router.put ('/api/v1/cloudron/ssh/authorized_keys', cloudronScope, routes.user.requireAdmin, routes.ssh.addAuthorizedKey);
router.get ('/api/v1/cloudron/ssh/authorized_keys/:identifier', cloudronScope, routes.user.requireAdmin, routes.ssh.getAuthorizedKey);
router.del ('/api/v1/cloudron/ssh/authorized_keys/:identifier', cloudronScope, routes.user.requireAdmin, routes.ssh.delAuthorizedKey);
router.get ('/api/v1/cloudron/eventlog', settingsScope, routes.user.requireAdmin, routes.eventlog.get);
2016-04-17 18:38:49 +02:00
// profile api, working off the user behind the provided token
2016-04-17 16:22:39 +02:00
router.get ('/api/v1/profile', profileScope, routes.profile.get);
2016-06-02 00:31:41 -07:00
router.post('/api/v1/profile', profileScope, routes.profile.update);
router.post('/api/v1/profile/password', profileScope, routes.user.verifyPassword, routes.profile.changePassword);
2016-06-02 00:20:33 -07:00
// user routes
2016-02-25 13:43:15 +01:00
router.get ('/api/v1/users', usersScope, routes.user.requireAdmin, routes.user.list);
router.post('/api/v1/users', usersScope, routes.user.requireAdmin, routes.user.create);
2016-04-17 18:38:49 +02:00
router.get ('/api/v1/users/:userId', usersScope, routes.user.requireAdmin, routes.user.get);
router.del ('/api/v1/users/:userId', usersScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.user.remove);
2016-05-31 11:51:56 -07:00
router.post('/api/v1/users/:userId', usersScope, routes.user.requireAdmin, routes.user.update);
2016-06-02 12:01:48 -07:00
router.put ('/api/v1/users/:userId/groups', usersScope, routes.user.requireAdmin, routes.user.setGroups);
router.get ('/api/v1/users/:userId/aliases', usersScope, routes.user.requireAdmin, routes.user.getAliases);
router.put ('/api/v1/users/:userId/aliases', usersScope, routes.user.requireAdmin, routes.user.setAliases);
2016-01-18 15:37:03 +01:00
router.post('/api/v1/users/:userId/invite', usersScope, routes.user.requireAdmin, routes.user.sendInvite);
2016-02-09 13:34:01 -08:00
// Group management
router.get ('/api/v1/groups', usersScope, routes.user.requireAdmin, routes.groups.list);
2016-02-09 13:34:01 -08:00
router.post('/api/v1/groups', usersScope, routes.user.requireAdmin, routes.groups.create);
router.get ('/api/v1/groups/:groupId', usersScope, routes.user.requireAdmin, routes.groups.get);
router.put ('/api/v1/groups/:groupId/members', usersScope, routes.user.requireAdmin, routes.groups.updateMembers);
2016-02-09 13:34:01 -08:00
router.del ('/api/v1/groups/:groupId', usersScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.groups.remove);
// form based login routes used by oauth2 frame
router.get ('/api/v1/session/login', csrf, routes.oauth2.loginForm);
router.post('/api/v1/session/login', csrf, routes.oauth2.login);
router.get ('/api/v1/session/logout', routes.oauth2.logout);
router.get ('/api/v1/session/callback', routes.oauth2.callback);
router.get ('/api/v1/session/password/resetRequest.html', csrf, routes.oauth2.passwordResetRequestSite);
router.post('/api/v1/session/password/resetRequest', csrf, routes.oauth2.passwordResetRequest);
router.get ('/api/v1/session/password/sent.html', routes.oauth2.passwordSentSite);
router.get ('/api/v1/session/password/reset.html', csrf, routes.oauth2.passwordResetSite);
router.post('/api/v1/session/password/reset', csrf, routes.oauth2.passwordReset);
router.get ('/api/v1/session/account/setup.html', csrf, routes.oauth2.accountSetupSite);
router.post('/api/v1/session/account/setup', csrf, routes.oauth2.accountSetup);
// oauth2 routes
router.get ('/api/v1/oauth/dialog/authorize', routes.oauth2.authorization);
router.post('/api/v1/oauth/token', routes.oauth2.token);
router.get ('/api/v1/oauth/clients', settingsScope, routes.clients.getAll);
router.post('/api/v1/oauth/clients', routes.developer.enabled, settingsScope, routes.clients.add);
router.get ('/api/v1/oauth/clients/:clientId', routes.developer.enabled, settingsScope, routes.clients.get);
router.post('/api/v1/oauth/clients/:clientId', routes.developer.enabled, settingsScope, routes.clients.add);
router.del ('/api/v1/oauth/clients/:clientId', routes.developer.enabled, settingsScope, routes.clients.del);
router.get ('/api/v1/oauth/clients/:clientId/tokens', settingsScope, routes.clients.getClientTokens);
router.post('/api/v1/oauth/clients/:clientId/tokens', routes.developer.enabled, settingsScope, routes.clients.addClientToken);
router.del ('/api/v1/oauth/clients/:clientId/tokens', settingsScope, routes.clients.delClientTokens);
2016-06-07 15:34:27 +02:00
router.del ('/api/v1/oauth/clients/:clientId/tokens/:tokenId', settingsScope, routes.clients.delToken);
// app routes
router.get ('/api/v1/apps', appsScope, routes.apps.getApps);
router.get ('/api/v1/apps/:id', appsScope, routes.apps.getApp);
router.get ('/api/v1/apps/:id/icon', routes.apps.getAppIcon);
router.post('/api/v1/apps/install', appsScope, routes.user.requireAdmin, routes.apps.installApp);
router.post('/api/v1/apps/:id/uninstall', appsScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.apps.uninstallApp);
router.post('/api/v1/apps/:id/configure', appsScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.apps.configureApp);
router.post('/api/v1/apps/:id/update', appsScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.apps.updateApp);
router.post('/api/v1/apps/:id/restore', appsScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.apps.restoreApp);
router.post('/api/v1/apps/:id/backup', appsScope, routes.user.requireAdmin, routes.apps.backupApp);
2016-01-19 13:35:28 +01:00
router.get ('/api/v1/apps/:id/backups', appsScope, routes.user.requireAdmin, routes.apps.listBackups);
router.post('/api/v1/apps/:id/stop', appsScope, routes.user.requireAdmin, routes.apps.stopApp);
router.post('/api/v1/apps/:id/start', appsScope, routes.user.requireAdmin, routes.apps.startApp);
router.get ('/api/v1/apps/:id/logstream', appsScope, routes.user.requireAdmin, routes.apps.getLogStream);
router.get ('/api/v1/apps/:id/logs', appsScope, routes.user.requireAdmin, routes.apps.getLogs);
router.get ('/api/v1/apps/:id/exec', routes.developer.enabled, appsScope, routes.user.requireAdmin, routes.apps.exec);
2017-08-17 09:33:00 +02:00
router.ws ('/api/v1/apps/:id/execws', routes.oauth2.websocketAuth, routes.apps.execWebSocket);
2016-06-17 17:12:55 -05:00
router.post('/api/v1/apps/:id/clone', appsScope, routes.user.requireAdmin, routes.apps.cloneApp);
2016-05-06 08:42:27 -07:00
// settings routes (these are for the settings tab - avatar & name have public routes for normal users. see above)
router.get ('/api/v1/settings/autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.getAutoupdatePattern);
router.post('/api/v1/settings/autoupdate_pattern', settingsScope, routes.user.requireAdmin, routes.settings.setAutoupdatePattern);
router.get ('/api/v1/settings/cloudron_name', settingsScope, routes.user.requireAdmin, routes.settings.getCloudronName);
router.post('/api/v1/settings/cloudron_name', settingsScope, routes.user.requireAdmin, routes.settings.setCloudronName);
router.get ('/api/v1/settings/cloudron_avatar', settingsScope, routes.user.requireAdmin, routes.settings.getCloudronAvatar);
router.post('/api/v1/settings/cloudron_avatar', settingsScope, routes.user.requireAdmin, multipart, routes.settings.setCloudronAvatar);
2017-03-09 15:43:13 -08:00
router.get ('/api/v1/settings/email_status', settingsScope, routes.user.requireAdmin, routes.settings.getEmailStatus);
router.get ('/api/v1/settings/dns_config', settingsScope, routes.user.requireAdmin, routes.settings.getDnsConfig);
router.post('/api/v1/settings/dns_config', settingsScope, routes.user.requireAdmin, routes.settings.setDnsConfig);
router.get ('/api/v1/settings/backup_config', settingsScope, routes.user.requireAdmin, routes.settings.getBackupConfig);
router.post('/api/v1/settings/backup_config', settingsScope, routes.user.requireAdmin, routes.settings.setBackupConfig);
2017-01-17 09:51:04 -08:00
router.post('/api/v1/settings/certificate', settingsScope, routes.user.requireAdmin, routes.settings.setFallbackCertificate);
2017-01-17 10:01:05 -08:00
router.post('/api/v1/settings/admin_certificate', settingsScope, routes.user.requireAdmin, routes.settings.setAdminCertificate);
router.get ('/api/v1/settings/time_zone', settingsScope, routes.user.requireAdmin, routes.settings.getTimeZone);
2016-06-02 13:36:47 -07:00
router.post('/api/v1/settings/time_zone', settingsScope, routes.user.requireAdmin, routes.settings.setTimeZone);
2016-07-26 15:32:36 +02:00
router.get ('/api/v1/settings/appstore_config', settingsScope, routes.user.requireAdmin, routes.settings.getAppstoreConfig);
router.post('/api/v1/settings/appstore_config', settingsScope, routes.user.requireAdmin, routes.settings.setAppstoreConfig);
2017-06-28 17:06:12 -05:00
// email routes
router.get ('/api/v1/settings/mail_config', settingsScope, routes.user.requireAdmin, routes.settings.getMailConfig);
router.post('/api/v1/settings/mail_config', settingsScope, routes.user.requireAdmin, routes.settings.setMailConfig);
2017-06-27 10:54:24 -05:00
router.get ('/api/v1/settings/mail_relay', settingsScope, routes.user.requireAdmin, routes.settings.getMailRelay);
router.post('/api/v1/settings/mail_relay', settingsScope, routes.user.requireAdmin, routes.settings.setMailRelay);
router.get ('/api/v1/settings/catch_all_address', settingsScope, routes.user.requireAdmin, routes.settings.getCatchAllAddress);
router.put ('/api/v1/settings/catch_all_address', settingsScope, routes.user.requireAdmin, routes.settings.setCatchAllAddress);
router.get ('/api/v1/settings/mail_from_validation', settingsScope, routes.user.requireAdmin, routes.settings.getMailFromValidation);
router.post('/api/v1/settings/mail_from_validation', settingsScope, routes.user.requireAdmin, routes.settings.setMailFromValidation);
// feedback
router.post('/api/v1/feedback', usersScope, routes.cloudron.feedback);
// backup routes
router.get ('/api/v1/backups', settingsScope, routes.user.requireAdmin, routes.backups.get);
router.post('/api/v1/backups', settingsScope, routes.user.requireAdmin, routes.backups.create);
2016-06-01 16:31:47 -07:00
// disable server socket "idle" timeout. we use the timeout middleware to handle timeouts on a route level
2016-06-01 17:33:04 -07:00
// we rely on nginx for timeouts on the TCP level (see client_header_timeout)
2016-01-18 12:20:09 -08:00
httpServer.setTimeout(0);
// upgrade handler
httpServer.on('upgrade', function (req, socket, head) {
2017-08-17 11:56:51 +02:00
if (req.headers.upgrade === 'websocket') {
// websocket connections should never time out
req.clearTimeout();
} else {
// create a node response object for express
var res = new http.ServerResponse({});
res.assignSocket(socket);
res.sendUpgradeHandshake = function () { // could extend express.response as well
console.log('----- now send the upgrade handshake')
socket.write('HTTP/1.1 101 TCP Handshake\r\n' +
'Upgrade: tcp\r\n' +
'Connection: Upgrade\r\n' +
'\r\n');
};
// route through express middleware. if we provide no callback, express will provide a 'finalhandler'
// TODO: it's not clear if socket needs to be destroyed
app(req, res);
}
});
return httpServer;
}
// provides local webhooks for sysadmins
2016-04-15 12:33:54 -07:00
function initializeSysadminExpressSync() {
var app = express();
var httpServer = http.createServer(app);
var QUERY_LIMIT = '1mb'; // max size for json and urlencoded queries
var REQUEST_TIMEOUT = 10000; // timeout for all requests
var json = middleware.json({ strict: true, limit: QUERY_LIMIT }), // application/json
urlencoded = middleware.urlencoded({ extended: false, limit: QUERY_LIMIT }); // application/x-www-form-urlencoded
2016-04-15 12:33:54 -07:00
if (process.env.BOX_ENV !== 'test') app.use(middleware.morgan('Box Sysadmin :method :url :status :response-time ms - :res[content-length]', { immediate: false }));
var router = new express.Router();
router.del = router.delete; // amend router.del for readability further on
app
.use(middleware.timeout(REQUEST_TIMEOUT))
.use(json)
.use(urlencoded)
.use(router)
.use(middleware.lastMile());
2016-04-15 12:33:54 -07:00
// Sysadmin routes
router.post('/api/v1/backup', routes.sysadmin.backup);
router.post('/api/v1/update', routes.sysadmin.update);
router.post('/api/v1/retire', routes.sysadmin.retire);
return httpServer;
}
function start(callback) {
assert.strictEqual(typeof callback, 'function');
assert.strictEqual(gHttpServer, null, 'Server is already up and running.');
gHttpServer = initializeExpressSync();
2016-04-15 12:33:54 -07:00
gSysadminHttpServer = initializeSysadminExpressSync();
async.series([
auth.initialize,
database.initialize,
2017-01-09 12:35:39 -08:00
cloudron.initialize,
gHttpServer.listen.bind(gHttpServer, config.get('port'), '127.0.0.1'),
gSysadminHttpServer.listen.bind(gSysadminHttpServer, config.get('sysadminPort'), '127.0.0.1'),
eventlog.add.bind(null, eventlog.ACTION_START, { userId: null, username: 'boot' }, { version: config.version() })
], callback);
}
function stop(callback) {
assert.strictEqual(typeof callback, 'function');
if (!gHttpServer) return callback(null);
async.series([
cloudron.uninitialize,
database.uninitialize,
2017-01-09 10:49:34 -08:00
auth.uninitialize,
gHttpServer.close.bind(gHttpServer),
2016-04-15 12:33:54 -07:00
gSysadminHttpServer.close.bind(gSysadminHttpServer)
], function (error) {
if (error) console.error(error);
gHttpServer = null;
2016-04-15 12:33:54 -07:00
gSysadminHttpServer = null;
callback(null);
});
}