Files
cloudron-box/src/routes/cloudron.js
T

326 lines
12 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
2020-08-15 22:54:32 -07:00
login,
logout,
passwordResetRequest,
passwordReset,
setupAccount,
reboot,
isRebootRequired,
getConfig,
getDisks,
getMemory,
getUpdateInfo,
update,
checkForUpdates,
getLogs,
getLogStream,
updateDashboardDomain,
2020-08-15 22:54:32 -07:00
prepareDashboardDomain,
renewCerts,
getServerIp,
2020-11-18 00:10:06 +01:00
getLanguages,
2020-08-15 22:54:32 -07:00
syncExternalLdap
};
2018-12-19 10:54:33 -08:00
let assert = require('assert'),
2019-03-25 15:07:06 -07:00
auditSource = require('../auditsource.js'),
2019-10-22 14:06:19 -07:00
BoxError = require('../boxerror.js'),
cloudron = require('../cloudron.js'),
2020-02-04 18:05:12 +01:00
constants = require('../constants.js'),
2020-02-06 15:47:44 +01:00
eventlog = require('../eventlog.js'),
2019-10-25 15:58:11 -07:00
externalLdap = require('../externalldap.js'),
HttpError = require('connect-lastmile').HttpError,
HttpSuccess = require('connect-lastmile').HttpSuccess,
2019-11-07 10:41:15 -08:00
sysinfo = require('../sysinfo.js'),
2019-11-21 12:58:06 -08:00
system = require('../system.js'),
2020-02-04 14:35:25 +01:00
tokendb = require('../tokendb.js'),
2020-02-06 16:57:33 +01:00
tokens = require('../tokens.js'),
2020-11-19 23:38:59 +01:00
translation = require('../translation.js'),
2018-07-31 11:35:23 -07:00
updater = require('../updater.js'),
2020-02-04 15:27:22 +01:00
users = require('../users.js'),
2019-10-23 09:39:26 -07:00
updateChecker = require('../updatechecker.js');
2020-02-04 14:35:25 +01:00
function login(req, res, next) {
2020-02-06 14:50:12 +01:00
assert.strictEqual(typeof req.user, 'object');
2020-02-04 14:35:25 +01:00
if ('type' in req.body && typeof req.body.type !== 'string') return next(new HttpError(400, 'type must be a string'));
const type = req.body.type || tokens.ID_WEBADMIN;
2020-02-06 15:47:44 +01:00
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
const auditSource = { authType: 'basic', ip: ip };
2020-02-04 14:35:25 +01:00
const error = tokens.validateTokenType(type);
if (error) return next(new HttpError(400, error.message));
tokens.add(type, req.user.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
2020-02-06 14:50:12 +01:00
if (error) return next(new HttpError(500, error));
2020-02-04 14:35:25 +01:00
2020-02-06 15:47:44 +01:00
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource, { userId: req.user.id, user: users.removePrivateFields(req.user) });
2020-02-06 14:50:12 +01:00
next(new HttpSuccess(200, result));
});
2020-02-04 14:35:25 +01:00
}
function logout(req, res) {
var token;
// this determines the priority
if (req.body && req.body.access_token) token = req.body.access_token;
if (req.query && req.query.access_token) token = req.query.access_token;
if (req.headers && req.headers.authorization) {
var parts = req.headers.authorization.split(' ');
if (parts.length == 2) {
var scheme = parts[0];
var credentials = parts[1];
if (/^Bearer$/i.test(scheme)) token = credentials;
}
}
if (!token) return res.redirect('/login.html');
2020-02-04 15:59:12 +01:00
tokendb.delByAccessToken(token, function () { res.redirect('/login.html'); });
2020-02-04 14:35:25 +01:00
}
2020-02-04 16:47:57 +01:00
function passwordResetRequest(req, res, next) {
2020-02-04 15:27:22 +01:00
if (!req.body.identifier || typeof req.body.identifier !== 'string') return next(new HttpError(401, 'A identifier must be non-empty string'));
users.sendPasswordResetByIdentifier(req.body.identifier, function (error) {
2020-06-04 09:20:28 -07:00
if (error && error.reason !== BoxError.NOT_FOUND) return next(BoxError.toHttpError(error));
2020-02-04 15:27:22 +01:00
next(new HttpSuccess(202, {}));
});
}
2020-02-04 16:47:57 +01:00
function passwordReset(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
if (typeof req.body.resetToken !== 'string') return next(new HttpError(400, 'Missing resetToken'));
if (typeof req.body.password !== 'string') return next(new HttpError(400, 'Missing password'));
2020-02-04 17:05:08 +01:00
users.getByResetToken(req.body.resetToken, function (error, userObject) {
if (error) return next(new HttpError(401, 'Invalid resetToken'));
2020-02-04 16:47:57 +01:00
2020-07-16 15:08:36 -07:00
// if you fix the duration here, the emails and UI have to be fixed as well
2020-07-28 14:17:39 -07:00
if (Date.now() - userObject.resetTokenCreationTime > 7 * 24 * 60 * 60 * 1000) return next(new HttpError(401, 'Token expired'));
if (!userObject.username) return next(new HttpError(409, 'No username set'));
2020-02-04 16:47:57 +01:00
// setPassword clears the resetToken
2020-02-24 12:54:09 +01:00
users.setPassword(userObject, req.body.password, function (error) {
2020-03-06 11:59:31 -08:00
if (error && error.reason === BoxError.BAD_FIELD) return next(new HttpError(400, error.message));
2020-06-04 09:20:28 -07:00
if (error) return next(BoxError.toHttpError(error));
2020-02-04 16:47:57 +01:00
tokens.add(tokens.ID_WEBADMIN, userObject.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
2020-06-04 09:20:28 -07:00
if (error) return next(BoxError.toHttpError(error));
2020-02-04 16:47:57 +01:00
next(new HttpSuccess(202, { accessToken: result.accessToken }));
});
});
});
}
2020-02-05 15:04:57 +01:00
function setupAccount(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
if (!req.body.resetToken || typeof req.body.resetToken !== 'string') return next(new HttpError(400, 'resetToken must be a non-empty string'));
if (!req.body.password || typeof req.body.password !== 'string') return next(new HttpError(400, 'password must be a non-empty string'));
2020-07-09 16:39:29 -07:00
// only sent if profile is not locked
if ('username' in req.body && typeof req.body.username !== 'string') return next(new HttpError(400, 'username must be a non-empty string'));
if ('displayName' in req.body && typeof req.body.displayName !== 'string') return next(new HttpError(400, 'displayName must be a non-empty string'));
2020-02-05 15:04:57 +01:00
users.getByResetToken(req.body.resetToken, function (error, userObject) {
if (error) return next(new HttpError(401, 'Invalid Reset Token'));
2020-07-16 15:08:36 -07:00
// if you fix the duration here, the emails and UI have to be fixed as well
2020-03-30 16:47:18 -07:00
if (Date.now() - userObject.resetTokenCreationTime > 24 * 60 * 60 * 1000) return next(new HttpError(401, 'Token expired'));
2020-07-09 16:39:29 -07:00
users.setupAccount(userObject, req.body, auditSource.fromRequest(req), function (error, accessToken) {
if (error) return next(BoxError.toHttpError(error));
2020-02-05 15:04:57 +01:00
2020-07-09 16:39:29 -07:00
next(new HttpSuccess(201, { accessToken }));
2020-02-05 15:04:57 +01:00
});
});
}
function reboot(req, res, next) {
2018-11-25 17:02:29 +01:00
// Finish the request, to let the appstore know we triggered the reboot
next(new HttpSuccess(202, {}));
2018-11-25 17:02:29 +01:00
cloudron.reboot(function () {});
}
function isRebootRequired(req, res, next) {
cloudron.isRebootRequired(function (error, result) {
2019-10-24 18:05:14 -07:00
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, { rebootRequired: result }));
});
}
function getConfig(req, res, next) {
cloudron.getConfig(function (error, cloudronConfig) {
2019-10-24 18:05:14 -07:00
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, cloudronConfig));
});
}
function getDisks(req, res, next) {
2019-11-21 12:58:06 -08:00
system.getDisks(function (error, result) {
2019-10-24 18:05:14 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-10-22 11:11:41 -07:00
next(new HttpSuccess(200, result));
});
}
2019-11-21 12:55:17 -08:00
function getMemory(req, res, next) {
2019-11-21 12:58:06 -08:00
system.getMemory(function (error, result) {
2019-11-21 12:55:17 -08:00
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, result));
});
}
function update(req, res, next) {
2019-05-12 13:28:53 -07:00
if ('skipBackup' in req.body && typeof req.body.skipBackup !== 'boolean') return next(new HttpError(400, 'skipBackup must be a boolean'));
// this only initiates the update, progress can be checked via the progress route
2019-05-12 13:28:53 -07:00
updater.updateToLatest(req.body, auditSource.fromRequest(req), function (error, taskId) {
2019-10-23 09:39:26 -07:00
if (error && error.reason === BoxError.NOT_FOUND) return next(new HttpError(422, error.message));
if (error && error.reason === BoxError.BAD_STATE) return next(new HttpError(409, error.message));
if (error) return next(new HttpError(500, error));
2018-12-08 18:50:06 -08:00
next(new HttpSuccess(202, { taskId }));
});
}
function getUpdateInfo(req, res, next) {
next(new HttpSuccess(200, { update: updateChecker.getUpdateInfo() }));
}
2016-06-07 20:24:41 -07:00
function checkForUpdates(req, res, next) {
2018-11-13 10:38:15 -08:00
// it can take a while sometimes to get all the app updates one by one
req.clearTimeout();
2020-08-19 21:39:58 -07:00
updateChecker.checkForUpdates({ automatic: false }, function () {
2016-06-07 20:24:41 -07:00
next(new HttpSuccess(200, { update: updateChecker.getUpdateInfo() }));
});
}
2017-04-18 15:15:35 -07:00
function getLogs(req, res, next) {
2018-06-11 20:09:38 +02:00
assert.strictEqual(typeof req.params.unit, 'string');
2019-01-08 12:10:53 -08:00
var lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10; // we ignore last-event-id
2017-04-18 15:15:35 -07:00
if (isNaN(lines)) return next(new HttpError(400, 'lines must be a number'));
2017-04-18 20:32:57 -07:00
var options = {
lines: lines,
follow: false,
2019-01-08 12:10:53 -08:00
format: req.query.format || 'json'
2017-04-18 20:32:57 -07:00
};
2018-06-11 20:09:38 +02:00
cloudron.getLogs(req.params.unit, options, function (error, logStream) {
2019-10-24 18:05:14 -07:00
if (error) return next(BoxError.toHttpError(error));
2017-04-18 15:15:35 -07:00
res.writeHead(200, {
'Content-Type': 'application/x-logs',
2020-11-09 10:58:43 +01:00
'Content-Disposition': `attachment; filename="${req.params.unit}.log"`,
2017-04-18 15:15:35 -07:00
'Cache-Control': 'no-cache',
'X-Accel-Buffering': 'no' // disable nginx buffering
});
logStream.pipe(res);
});
}
2017-08-07 16:49:37 +02:00
function getLogStream(req, res, next) {
2018-06-11 20:09:38 +02:00
assert.strictEqual(typeof req.params.unit, 'string');
2019-01-08 12:10:53 -08:00
var lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10; // we ignore last-event-id
2017-08-07 16:49:37 +02:00
if (isNaN(lines)) return next(new HttpError(400, 'lines must be a valid number'));
function sse(id, data) { return 'id: ' + id + '\ndata: ' + data + '\n\n'; }
if (req.headers.accept !== 'text/event-stream') return next(new HttpError(400, 'This API call requires EventStream'));
var options = {
lines: lines,
2017-08-07 18:26:03 +02:00
follow: true,
2019-01-08 12:10:53 -08:00
format: req.query.format || 'json'
2017-08-07 16:49:37 +02:00
};
2018-06-11 20:09:38 +02:00
cloudron.getLogs(req.params.unit, options, function (error, logStream) {
2019-10-24 18:05:14 -07:00
if (error) return next(BoxError.toHttpError(error));
2017-08-07 16:49:37 +02:00
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no', // disable nginx buffering
'Access-Control-Allow-Origin': '*'
});
res.write('retry: 3000\n');
res.on('close', logStream.close);
logStream.on('data', function (data) {
var obj = JSON.parse(data);
res.write(sse(obj.monotonicTimestamp, JSON.stringify(obj))); // send timestamp as id
});
logStream.on('end', res.end.bind(res));
logStream.on('error', res.end.bind(res, null));
});
}
function updateDashboardDomain(req, res, next) {
if (!req.body.domain || typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
cloudron.updateDashboardDomain(req.body.domain, auditSource.fromRequest(req), function (error) {
2019-10-24 18:05:14 -07:00
if (error) return next(BoxError.toHttpError(error));
2018-12-07 16:39:22 -08:00
next(new HttpSuccess(204, {}));
});
}
function prepareDashboardDomain(req, res, next) {
2018-12-14 09:57:28 -08:00
if (!req.body.domain || typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
2019-03-25 15:07:06 -07:00
cloudron.prepareDashboardDomain(req.body.domain, auditSource.fromRequest(req), function (error, taskId) {
2019-10-24 18:05:14 -07:00
if (error) return next(BoxError.toHttpError(error));
2018-12-14 09:57:28 -08:00
next(new HttpSuccess(202, { taskId }));
});
}
2018-12-10 20:20:53 -08:00
function renewCerts(req, res, next) {
2019-03-25 15:07:06 -07:00
cloudron.renewCerts({ domain: req.body.domain || null }, auditSource.fromRequest(req), function (error, taskId) {
2019-10-24 18:05:14 -07:00
if (error) return next(BoxError.toHttpError(error));
2018-12-10 20:20:53 -08:00
2018-12-11 10:16:38 -08:00
next(new HttpSuccess(202, { taskId }));
2018-12-10 20:20:53 -08:00
});
}
2019-08-29 17:19:51 +02:00
function syncExternalLdap(req, res, next) {
2019-10-25 15:58:11 -07:00
externalLdap.startSyncer(function (error, taskId) {
2019-08-29 17:19:51 +02:00
if (error) return next(new HttpError(500, error.message));
next(new HttpSuccess(202, { taskId: taskId }));
});
}
2019-11-07 10:41:15 -08:00
function getServerIp(req, res, next) {
sysinfo.getServerIp(function (error, ip) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, { ip }));
});
}
2020-11-18 00:10:06 +01:00
function getLanguages(req, res, next) {
2020-11-19 23:38:59 +01:00
translation.getLanguages(function (error, languages) {
2020-11-18 00:10:06 +01:00
if (error) return next(new BoxError.toHttpError(error));
next(new HttpSuccess(200, { languages }));
});
}