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

1007 lines
42 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
2020-10-27 17:11:50 -07:00
getApp,
2021-08-20 09:19:44 -07:00
listByUser,
2020-10-27 17:11:50 -07:00
getAppIcon,
install,
uninstall,
restore,
importApp,
2020-12-06 19:38:50 -08:00
exportApp,
2020-10-27 17:11:50 -07:00
backup,
update,
2021-09-21 22:19:20 -07:00
getTask,
2020-10-27 17:11:50 -07:00
getLogs,
getLogStream,
2021-09-21 19:45:29 -07:00
listEventlog,
2020-10-27 17:11:50 -07:00
listBackups,
repair,
2020-10-27 17:11:50 -07:00
setAccessRestriction,
2021-09-21 10:11:27 -07:00
setOperators,
2021-09-27 14:21:42 -07:00
setCrontab,
2020-10-27 17:11:50 -07:00
setLabel,
setTags,
setIcon,
2023-07-13 15:06:07 +05:30
setTurn,
2020-10-27 17:11:50 -07:00
setMemoryLimit,
setCpuShares,
setAutomaticBackup,
setAutomaticUpdate,
setReverseProxyConfig,
setCertificate,
setDebugMode,
setEnvironment,
setMailbox,
2021-10-01 12:09:13 -07:00
setInbox,
2020-10-27 17:11:50 -07:00
setLocation,
2022-06-01 22:44:52 -07:00
setStorage,
2020-10-28 19:42:48 -07:00
setMounts,
2022-06-08 11:21:09 +02:00
setUpstreamUri,
2019-09-08 16:57:08 -07:00
2020-10-27 17:11:50 -07:00
stop,
start,
restart,
2022-05-16 10:26:30 -07:00
createExec,
startExec,
startExecWebSocket,
getExec,
2021-09-21 19:53:05 -07:00
checkForUpdates,
2016-06-17 17:12:55 -05:00
2020-10-27 17:11:50 -07:00
clone,
2017-08-18 20:45:52 -07:00
2020-10-27 17:11:50 -07:00
uploadFile,
downloadFile,
2020-03-29 17:11:10 -07:00
updateBackup,
2022-11-03 22:13:57 +01:00
downloadBackup,
getLimits,
2022-10-13 20:32:36 +02:00
getGraphs,
2020-10-27 17:11:50 -07:00
load
};
2021-08-30 09:27:35 -07:00
const apps = require('../apps.js'),
assert = require('assert'),
2021-09-30 09:50:30 -07:00
AuditSource = require('../auditsource.js'),
2019-10-24 10:39:47 -07:00
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:routes/apps'),
2022-10-13 20:32:36 +02:00
graphs = require('../graphs.js'),
HttpError = require('connect-lastmile').HttpError,
HttpSuccess = require('connect-lastmile').HttpSuccess,
safe = require('safetydance'),
2021-09-21 19:53:05 -07:00
updateChecker = require('../updatechecker.js'),
users = require('../users.js'),
WebSocket = require('ws');
2021-08-20 09:19:44 -07:00
async function load(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
2021-08-23 15:44:23 -07:00
const [error, result] = await safe(apps.get(req.params.id));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2021-08-30 09:27:35 -07:00
if (!result) return next(new HttpError(404, 'App not found'));
req.app = result;
2020-03-29 17:11:10 -07:00
2021-08-20 09:19:44 -07:00
next();
}
2020-03-29 17:11:10 -07:00
function getApp(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2020-03-29 17:11:10 -07:00
2021-09-21 17:28:58 -07:00
const result = apps.removeInternalFields(req.app);
result.accessLevel = apps.accessLevel(req.app, req.user);
next(new HttpSuccess(200, result));
2020-03-29 17:11:10 -07:00
}
2021-08-20 09:19:44 -07:00
async function listByUser(req, res, next) {
2016-02-25 12:20:11 +01:00
assert.strictEqual(typeof req.user, 'object');
2021-08-20 09:19:44 -07:00
let [error, result] = await safe(apps.listByUser(req.user));
if (error) return next(BoxError.toHttpError(error));
2018-04-26 20:07:03 -07:00
2021-09-21 17:28:58 -07:00
result = result.map(r => {
const app = apps.removeRestrictedFields(r);
app.accessLevel = apps.accessLevel(r, req.user);
return app;
});
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(200, { apps: result }));
}
2021-08-20 09:19:44 -07:00
async function getAppIcon(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
const [error, icon] = await safe(apps.getIcon(req.app, { original: req.query.original }));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-05-17 09:47:11 -07:00
2021-08-20 09:19:44 -07:00
res.send(icon);
}
2021-08-20 09:19:44 -07:00
async function install(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
const data = req.body;
// atleast one
2016-06-04 19:19:00 -07:00
if ('manifest' in data && typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest must be an object'));
if ('appStoreId' in data && typeof data.appStoreId !== 'string') return next(new HttpError(400, 'appStoreId must be a string'));
if (!data.manifest && !data.appStoreId) return next(new HttpError(400, 'appStoreId or manifest is required'));
2016-06-03 23:22:38 -07:00
// required
2022-01-16 12:32:12 -08:00
if (typeof data.subdomain !== 'string') return next(new HttpError(400, 'subdomain is required'));
2017-11-02 22:17:44 +01:00
if (typeof data.domain !== 'string') return next(new HttpError(400, 'domain is required'));
if (typeof data.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction is required'));
2016-06-03 23:22:38 -07:00
// optional
if (('portBindings' in data) && typeof data.portBindings !== 'object') return next(new HttpError(400, 'portBindings must be an object'));
if ('icon' in data && typeof data.icon !== 'string') return next(new HttpError(400, 'icon is not a string'));
2019-03-22 07:48:31 -07:00
if ('label' in data && typeof data.label !== 'string') return next(new HttpError(400, 'label must be a string'));
2016-02-11 17:00:21 +01:00
if ('memoryLimit' in data && typeof data.memoryLimit !== 'number') return next(new HttpError(400, 'memoryLimit is not a number'));
2016-11-11 10:55:44 +05:30
if ('sso' in data && typeof data.sso !== 'boolean') return next(new HttpError(400, 'sso must be a boolean'));
2017-08-16 14:12:07 -07:00
if ('enableBackup' in data && typeof data.enableBackup !== 'boolean') return next(new HttpError(400, 'enableBackup must be a boolean'));
2018-12-07 09:03:28 -08:00
if ('enableAutomaticUpdate' in data && typeof data.enableAutomaticUpdate !== 'boolean') return next(new HttpError(400, 'enableAutomaticUpdate must be a boolean'));
if (('debugMode' in data) && typeof data.debugMode !== 'object') return next(new HttpError(400, 'debugMode must be an object'));
2017-01-19 11:20:24 -08:00
2022-01-14 22:40:51 -08:00
if ('secondaryDomains' in data) {
if (!data.secondaryDomains || typeof data.secondaryDomains !== 'object') return next(new HttpError(400, 'secondaryDomains must be an object'));
if (Object.keys(data.secondaryDomains).some(function (key) { return typeof data.secondaryDomains[key].domain !== 'string' || typeof data.secondaryDomains[key].subdomain !== 'string'; })) return next(new HttpError(400, 'secondaryDomain object must contain domain and subdomain strings'));
}
2022-01-14 22:29:47 -08:00
if ('redirectDomains' in data) {
if (!Array.isArray(data.redirectDomains)) return next(new HttpError(400, 'redirectDomains must be an array'));
if (data.redirectDomains.some(function (d) { return (typeof d.domain !== 'string' || typeof d.subdomain !== 'string'); })) return next(new HttpError(400, 'redirectDomains array must contain objects with domain and subdomain strings'));
2018-09-04 16:21:10 -07:00
}
2021-01-18 17:26:26 -08:00
if ('aliasDomains' in data) {
if (!Array.isArray(data.aliasDomains)) return next(new HttpError(400, 'aliasDomains must be an array'));
if (data.aliasDomains.some(function (d) { return (typeof d.domain !== 'string' || typeof d.subdomain !== 'string'); })) return next(new HttpError(400, 'aliasDomains array must contain objects with domain and subdomain strings'));
}
2018-10-11 14:07:43 -07:00
if ('env' in data) {
if (!data.env || typeof data.env !== 'object') return next(new HttpError(400, 'env must be an object'));
if (Object.keys(data.env).some(function (key) { return typeof data.env[key] !== 'string'; })) return next(new HttpError(400, 'env must contain values as strings'));
}
2021-10-15 11:20:09 -07:00
if ('overwriteDns' in data && typeof data.overwriteDns !== 'boolean') return next(new HttpError(400, 'overwriteDns must be boolean'));
if ('skipDnsSetup' in data && typeof data.skipDnsSetup !== 'boolean') return next(new HttpError(400, 'skipDnsSetup must be boolean'));
if ('enableMailbox' in data && typeof data.enableMailbox !== 'boolean') return next(new HttpError(400, 'enableMailbox must be boolean'));
2019-09-16 09:31:34 -07:00
2023-07-13 15:06:07 +05:30
if ('enableTurn' in data && typeof data.enableTurn !== 'boolean') return next(new HttpError(400, 'enableTurn must be boolean'));
2021-08-20 09:19:44 -07:00
let [error, result] = await safe(apps.downloadManifest(data.appStoreId, data.manifest));
if (error) return next(BoxError.toHttpError(error));
2022-11-23 12:53:21 +01:00
if (result.appStoreId === constants.PROXY_APP_APPSTORE_ID && typeof data.upstreamUri !== 'string') return next(new HttpError(400, 'upstreamUri must be a non empty string'));
2021-08-20 09:19:44 -07:00
if (safe.query(result.manifest, 'addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is required to install app with docker addon'));
2021-08-20 09:19:44 -07:00
data.appStoreId = result.appStoreId;
data.manifest = result.manifest;
2021-09-30 09:50:30 -07:00
[error, result] = await safe(apps.install(data, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { id: result.id, taskId: result.taskId }));
}
2021-08-20 09:19:44 -07:00
async function setAccessRestriction(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
if (typeof req.body.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction must be an object'));
2021-09-30 09:50:30 -07:00
const [error] = await safe(apps.setAccessRestriction(req.app, req.body.accessRestriction, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(200, {}));
2019-09-08 16:57:08 -07:00
}
2021-09-21 10:11:27 -07:00
async function setOperators(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
if (typeof req.body.operators !== 'object') return next(new HttpError(400, 'operators must be an object'));
2021-09-30 09:50:30 -07:00
const [error] = await safe(apps.setOperators(req.app, req.body.operators, AuditSource.fromRequest(req)));
2021-09-21 10:11:27 -07:00
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
}
2021-09-27 14:21:42 -07:00
async function setCrontab(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
if (req.body.crontab !== null && typeof req.body.crontab !== 'string') return next(new HttpError(400, 'crontab must be a string'));
2021-09-30 09:50:30 -07:00
const [error] = await safe(apps.setCrontab(req.app, req.body.crontab, AuditSource.fromRequest(req)));
2021-09-27 14:21:42 -07:00
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
}
2021-08-20 09:19:44 -07:00
async function setLabel(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
if (typeof req.body.label !== 'string') return next(new HttpError(400, 'label must be a string'));
2021-09-30 09:50:30 -07:00
const [error] = await safe(apps.setLabel(req.app, req.body.label, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(200, {}));
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setTags(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
if (!Array.isArray(req.body.tags)) return next(new HttpError(400, 'tags must be an array'));
if (req.body.tags.some((t) => typeof t !== 'string')) return next(new HttpError(400, 'tags array must contain strings'));
2021-09-30 09:50:30 -07:00
const [error] = await safe(apps.setTags(req.app, req.body.tags, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(200, {}));
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setIcon(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
if (req.body.icon !== null && typeof req.body.icon !== 'string') return next(new HttpError(400, 'icon is null or a base-64 image string'));
2021-09-30 09:50:30 -07:00
const [error] = await safe(apps.setIcon(req.app, req.body.icon || null /* empty string means null */, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(200, {}));
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setMemoryLimit(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
if (typeof req.body.memoryLimit !== 'number') return next(new HttpError(400, 'memoryLimit is not a number'));
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.setMemoryLimit(req.app, req.body.memoryLimit, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2020-01-28 21:30:35 -08:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
2020-01-28 21:30:35 -08:00
}
function setCpuShares(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2020-01-28 21:30:35 -08:00
if (typeof req.body.cpuShares !== 'number') return next(new HttpError(400, 'cpuShares is not a number'));
2021-09-30 09:50:30 -07:00
apps.setCpuShares(req.app, req.body.cpuShares, AuditSource.fromRequest(req), function (error, result) {
2020-01-28 21:30:35 -08:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
2021-08-20 09:19:44 -07:00
async function setAutomaticBackup(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
2021-09-30 09:50:30 -07:00
const [error] = await safe(apps.setAutomaticBackup(req.app, req.body.enable, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(200, {}));
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setAutomaticUpdate(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
2021-09-30 09:50:30 -07:00
const [error] = await safe(apps.setAutomaticUpdate(req.app, req.body.enable, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(200, {}));
2019-09-08 16:57:08 -07:00
}
2021-08-17 14:04:29 -07:00
async function setReverseProxyConfig(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
if (req.body.robotsTxt !== null && typeof req.body.robotsTxt !== 'string') return next(new HttpError(400, 'robotsTxt is not a string'));
2019-10-14 16:59:22 -07:00
if (req.body.csp !== null && typeof req.body.csp !== 'string') return next(new HttpError(400, 'csp is not a string'));
2019-10-13 18:22:03 -07:00
2023-03-06 11:15:55 +01:00
if (typeof req.body.hstsPreload !== 'boolean') return next(new HttpError(400, 'hstsPreload must be a boolean'));
2021-09-30 09:50:30 -07:00
const [error] = await safe(apps.setReverseProxyConfig(req.app, req.body, AuditSource.fromRequest(req)));
2021-08-17 14:04:29 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-17 14:04:29 -07:00
next(new HttpSuccess(200, {}));
2019-09-08 16:57:08 -07:00
}
2021-08-17 14:04:29 -07:00
async function setCertificate(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
2022-01-16 12:32:12 -08:00
if (typeof req.body.subdomain !== 'string') return next(new HttpError(400, 'subdomain must be string')); // subdomain may be an empty string
if (!req.body.domain) return next(new HttpError(400, 'domain is required'));
if (typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be string'));
2019-09-08 16:57:08 -07:00
if (req.body.key !== null && typeof req.body.cert !== 'string') return next(new HttpError(400, 'cert must be a string'));
if (req.body.cert !== null && typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string'));
if (req.body.cert && !req.body.key) return next(new HttpError(400, 'key must be provided'));
if (!req.body.cert && req.body.key) return next(new HttpError(400, 'cert must be provided'));
2021-09-30 09:50:30 -07:00
const [error] = await safe(apps.setCertificate(req.app, req.body, AuditSource.fromRequest(req)));
2021-08-17 14:04:29 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-17 14:04:29 -07:00
next(new HttpSuccess(200, {}));
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setEnvironment(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
if (!req.body.env || typeof req.body.env !== 'object') return next(new HttpError(400, 'env must be an object'));
2019-09-09 15:35:02 -07:00
if (Object.keys(req.body.env).some((key) => typeof req.body.env[key] !== 'string')) return next(new HttpError(400, 'env must contain values as strings'));
2019-09-08 16:57:08 -07:00
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.setEnvironment(req.app, req.body.env, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setDebugMode(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
if (req.body.debugMode !== null && typeof req.body.debugMode !== 'object') return next(new HttpError(400, 'debugMode must be an object'));
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.setDebugMode(req.app, req.body.debugMode, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function setMailbox(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
2021-03-16 22:38:59 -07:00
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
if (req.body.enable) {
if (req.body.mailboxName !== null && typeof req.body.mailboxName !== 'string') return next(new HttpError(400, 'mailboxName must be a string'));
if (typeof req.body.mailboxDomain !== 'string') return next(new HttpError(400, 'mailboxDomain must be a string'));
2022-06-08 14:25:16 -07:00
if ('mailboxDisplayName' in req.body && typeof req.body.mailboxDisplayName !== 'string') return next(new HttpError(400, 'mailboxDisplayName must be a string'));
2021-03-16 22:38:59 -07:00
}
2019-09-08 16:57:08 -07:00
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.setMailbox(req.app, req.body, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
2019-09-08 16:57:08 -07:00
}
2021-10-01 12:09:13 -07:00
async function setInbox(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
if (req.body.enable) {
if (typeof req.body.inboxName !== 'string') return next(new HttpError(400, 'inboxName must be a string'));
if (typeof req.body.inboxDomain !== 'string') return next(new HttpError(400, 'inboxDomain must be a string'));
}
const [error, result] = await safe(apps.setInbox(req.app, req.body, AuditSource.fromRequest(req)));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
}
2023-07-13 15:06:07 +05:30
async function setTurn(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
const [error, result] = await safe(apps.setTurn(req.app, req.body.enable, AuditSource.fromRequest(req)));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
}
2021-08-20 09:19:44 -07:00
async function setLocation(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
2022-01-16 12:32:12 -08:00
if (typeof req.body.subdomain !== 'string') return next(new HttpError(400, 'subdomain must be string')); // subdomain may be an empty string
2019-09-08 16:57:08 -07:00
if (typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be string'));
if ('portBindings' in req.body && typeof req.body.portBindings !== 'object') return next(new HttpError(400, 'portBindings must be an object'));
2022-01-14 22:40:51 -08:00
if ('secondaryDomains' in req.body) {
if (!req.body.secondaryDomains || typeof req.body.secondaryDomains !== 'object') return next(new HttpError(400, 'secondaryDomains must be an object'));
if (Object.keys(req.body.secondaryDomains).some(function (key) { return typeof req.body.secondaryDomains[key].domain !== 'string' || typeof req.body.secondaryDomains[key].subdomain !== 'string'; })) return next(new HttpError(400, 'secondaryDomain object must contain domain and subdomain strings'));
}
2022-01-14 22:29:47 -08:00
if ('redirectDomains' in req.body) {
if (!Array.isArray(req.body.redirectDomains)) return next(new HttpError(400, 'redirectDomains must be an array'));
if (req.body.redirectDomains.some(function (d) { return (typeof d.domain !== 'string' || typeof d.subdomain !== 'string'); })) return next(new HttpError(400, 'redirectDomains array must contain objects with domain and subdomain strings'));
2019-09-08 16:57:08 -07:00
}
2021-01-18 17:26:26 -08:00
if ('aliasDomains' in req.body) {
if (!Array.isArray(req.body.aliasDomains)) return next(new HttpError(400, 'aliasDomains must be an array'));
if (req.body.aliasDomains.some(function (d) { return (typeof d.domain !== 'string' || typeof d.subdomain !== 'string'); })) return next(new HttpError(400, 'aliasDomains array must contain objects with domain and subdomain strings'));
}
2019-09-10 15:23:47 -07:00
if ('overwriteDns' in req.body && typeof req.body.overwriteDns !== 'boolean') return next(new HttpError(400, 'overwriteDns must be boolean'));
if ('skipDnsSetup' in req.body && typeof req.body.skipDnsSetup !== 'boolean') return next(new HttpError(400, 'skipDnsSetup must be boolean'));
2019-09-10 15:23:47 -07:00
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.setLocation(req.app, req.body, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
2019-09-08 16:57:08 -07:00
}
2022-06-01 22:44:52 -07:00
async function setStorage(req, res, next) {
2019-09-08 16:57:08 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-08 16:57:08 -07:00
2022-06-03 10:17:34 -07:00
const { storageVolumeId, storageVolumePrefix } = req.body;
2019-09-08 16:57:08 -07:00
2022-06-03 10:17:34 -07:00
if (storageVolumeId !== null) {
if (typeof storageVolumeId !== 'string') return next(new HttpError(400, 'storageVolumeId must be a string'));
if (typeof storageVolumePrefix !== 'string') return next(new HttpError(400, 'storageVolumePrefix must be a string'));
}
const [error, result] = await safe(apps.setStorage(req.app, storageVolumeId, storageVolumePrefix, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-08 16:57:08 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
2019-09-08 16:57:08 -07:00
}
2021-08-20 09:19:44 -07:00
async function repair(req, res, next) {
2019-09-19 17:04:11 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2019-09-19 17:04:11 -07:00
2019-09-21 19:45:55 -07:00
const data = req.body;
2019-11-23 18:35:51 -08:00
if ('manifest' in data) {
if (!data.manifest || typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest must be an object'));
2020-03-29 20:12:59 -07:00
if (safe.query(data.manifest, 'addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is required to repair app with docker addon'));
}
if ('dockerImage' in data) {
if (!data.dockerImage || typeof data.dockerImage !== 'string') return next(new HttpError(400, 'dockerImage must be a string'));
2019-09-21 19:45:55 -07:00
}
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.repair(req.app, data, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-09-19 17:04:11 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
2019-09-19 17:04:11 -07:00
}
2021-07-14 11:07:19 -07:00
async function restore(req, res, next) {
2016-06-13 10:08:58 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2021-07-14 11:07:19 -07:00
const data = req.body;
2016-06-13 10:08:58 -07:00
2019-12-05 21:15:09 -08:00
if (!data.backupId || typeof data.backupId !== 'string') return next(new HttpError(400, 'backupId must be non-empty string'));
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.restore(req.app, data.backupId, AuditSource.fromRequest(req)));
2021-07-14 11:07:19 -07:00
if (error) return next(BoxError.toHttpError(error));
2021-07-14 11:07:19 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
}
2021-08-20 09:19:44 -07:00
async function importApp(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2021-08-20 09:19:44 -07:00
const data = req.body;
if ('remotePath' in data) { // if not provided, we import in-place
2022-10-02 10:08:50 +02:00
if (typeof data.remotePath !== 'string' || !data.remotePath) return next(new HttpError(400, 'remotePath must be non-empty string'));
if (typeof data.backupFormat !== 'string') return next(new HttpError(400, 'backupFormat must be string'));
if ('backupConfig' in data && typeof data.backupConfig !== 'object') return next(new HttpError(400, 'backupConfig must be an object'));
const backupConfig = req.body.backupConfig;
2022-10-02 10:08:50 +02:00
if (backupConfig) {
if (typeof backupConfig.provider !== 'string') return next(new HttpError(400, 'provider is required'));
2020-05-12 10:31:51 -07:00
if ('password' in backupConfig && typeof backupConfig.password !== 'string') return next(new HttpError(400, 'password must be a string'));
2022-06-27 09:17:01 -07:00
if ('encryptedFilenames' in backupConfig && typeof backupConfig.encryptedFilenames !== 'boolean') return next(new HttpError(400, 'encryptedFilenames must be a boolean'));
if ('acceptSelfSignedCerts' in backupConfig && typeof backupConfig.acceptSelfSignedCerts !== 'boolean') return next(new HttpError(400, 'format must be a boolean'));
// testing backup config can take sometime
req.clearTimeout();
}
}
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.importApp(req.app, data, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
}
2021-08-20 09:19:44 -07:00
async function exportApp(req, res, next) {
2020-12-06 19:38:50 -08:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2020-12-06 19:38:50 -08:00
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.exportApp(req.app, {}, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2020-12-06 19:38:50 -08:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
2020-12-06 19:38:50 -08:00
}
2021-08-20 09:19:44 -07:00
async function clone(req, res, next) {
2016-06-17 17:12:55 -05:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2016-06-17 17:12:55 -05:00
2022-04-14 17:41:41 -05:00
const data = req.body;
2016-06-17 17:12:55 -05:00
if (typeof data.backupId !== 'string') return next(new HttpError(400, 'backupId must be a string'));
2022-01-16 12:32:12 -08:00
if (typeof data.subdomain !== 'string') return next(new HttpError(400, 'subdomain is required'));
2017-11-02 22:17:44 +01:00
if (typeof data.domain !== 'string') return next(new HttpError(400, 'domain is required'));
2016-06-17 17:12:55 -05:00
if (('portBindings' in data) && typeof data.portBindings !== 'object') return next(new HttpError(400, 'portBindings must be an object'));
2022-02-01 23:36:41 -08:00
if ('secondaryDomains' in data) {
if (!data.secondaryDomains || typeof data.secondaryDomains !== 'object') return next(new HttpError(400, 'secondaryDomains must be an object'));
if (Object.keys(data.secondaryDomains).some(function (key) { return typeof data.secondaryDomains[key].domain !== 'string' || typeof data.secondaryDomains[key].subdomain !== 'string'; })) return next(new HttpError(400, 'secondaryDomain object must contain domain and subdomain strings'));
}
2019-09-16 09:31:34 -07:00
if ('overwriteDns' in req.body && typeof req.body.overwriteDns !== 'boolean') return next(new HttpError(400, 'overwriteDns must be boolean'));
if ('skipDnsSetup' in req.body && typeof req.body.skipDnsSetup !== 'boolean') return next(new HttpError(400, 'skipDnsSetup must be boolean'));
2019-09-16 09:31:34 -07:00
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.clone(req.app, data, req.user, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2016-06-17 17:12:55 -05:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(201, { id: result.id, taskId: result.taskId }));
2016-06-17 17:12:55 -05:00
}
2021-08-20 09:19:44 -07:00
async function backup(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2021-09-30 10:45:25 -07:00
const [error, result] = await safe(apps.backup(req.app, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
}
2021-08-20 09:19:44 -07:00
async function uninstall(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.uninstall(req.app, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
}
2021-08-20 09:19:44 -07:00
async function start(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.start(req.app, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
}
2021-08-20 09:19:44 -07:00
async function stop(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.stop(req.app, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
}
2021-08-20 09:19:44 -07:00
async function restart(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2019-12-20 10:29:29 -08:00
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.restart(req.app, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2019-12-20 10:29:29 -08:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
2019-12-20 10:29:29 -08:00
}
2021-08-20 09:19:44 -07:00
async function update(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2021-08-20 09:19:44 -07:00
const data = req.body;
2016-06-04 19:19:00 -07:00
// atleast one
if ('manifest' in data && typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest must be an object'));
if ('appStoreId' in data && typeof data.appStoreId !== 'string') return next(new HttpError(400, 'appStoreId must be a string'));
if (!data.manifest && !data.appStoreId) return next(new HttpError(400, 'appStoreId or manifest is required'));
if ('skipBackup' in data && typeof data.skipBackup !== 'boolean') return next(new HttpError(400, 'skipBackup must be a boolean'));
if ('force' in data && typeof data.force !== 'boolean') return next(new HttpError(400, 'force must be a boolean'));
2021-08-20 09:19:44 -07:00
let [error, result] = await safe(apps.downloadManifest(data.appStoreId, data.manifest));
if (error) return next(BoxError.toHttpError(error));
2021-08-20 09:19:44 -07:00
const { appStoreId, manifest } = result;
2021-08-20 09:19:44 -07:00
if (safe.query(manifest, 'addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is required to update app with docker addon'));
2021-08-20 09:19:44 -07:00
data.appStoreId = appStoreId;
data.manifest = manifest;
2021-09-30 09:50:30 -07:00
[error, result] = await safe(apps.updateApp(req.app, data, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
}
// this route is for streaming logs
2021-10-04 10:08:11 -07:00
async function getLogStream(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2021-10-04 10:08:11 -07:00
const lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10; // we ignore last-event-id
2015-11-02 11:20:50 -08: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'));
2021-10-04 10:08:11 -07:00
const options = {
2017-04-19 21:43:29 -07:00
lines: lines,
2019-01-08 12:10:53 -08:00
follow: true,
format: 'json'
2017-04-19 21:43:29 -07:00
};
2021-10-04 10:08:11 -07:00
const [error, logStream] = await safe(apps.getLogs(req.app, options));
if (error) return next(BoxError.toHttpError(error));
2021-10-04 10:08:11 -07: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) {
2022-04-14 17:41:41 -05:00
const obj = JSON.parse(data);
2021-10-04 10:08:11 -07:00
res.write(sse(obj.realtimeTimestamp, JSON.stringify(obj))); // send timestamp as id
});
2021-10-04 10:08:11 -07:00
logStream.on('end', res.end.bind(res));
logStream.on('error', res.end.bind(res, null));
}
2021-10-01 09:23:20 -07:00
async function getLogs(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2021-10-01 12:09:13 -07:00
const lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10;
2015-11-02 11:20:50 -08:00
if (isNaN(lines)) return next(new HttpError(400, 'lines must be a number'));
2021-10-01 12:09:13 -07:00
const options = {
lines,
2017-04-18 20:32:57 -07:00
follow: false,
2019-01-08 12:10:53 -08:00
format: req.query.format || 'json'
2017-04-18 20:32:57 -07:00
};
2021-10-01 09:23:20 -07:00
const [error, logStream] = await safe(apps.getLogs(req.app, options));
if (error) return next(BoxError.toHttpError(error));
2021-10-01 09:23:20 -07:00
res.writeHead(200, {
'Content-Type': 'application/x-logs',
'Content-Disposition': `attachment; filename="${req.app.id}.log"`,
'Cache-Control': 'no-cache',
'X-Accel-Buffering': 'no' // disable nginx buffering
});
2021-10-01 09:23:20 -07:00
logStream.pipe(res);
}
2016-05-22 21:07:05 -07:00
function demuxStream(stream, stdin) {
2022-04-14 17:41:41 -05:00
let header = null;
2016-05-22 21:07:05 -07:00
stream.on('readable', function() {
header = header || stream.read(4);
while (header !== null) {
2022-04-14 17:41:41 -05:00
const length = header.readUInt32BE(0);
2016-05-22 21:07:05 -07:00
if (length === 0) {
header = null;
return stdin.end(); // EOF
}
2022-04-14 17:41:41 -05:00
const payload = stream.read(length);
2016-05-22 21:07:05 -07:00
if (payload === null) break;
stdin.write(payload);
header = stream.read(4);
}
});
}
2022-05-16 10:26:30 -07:00
async function createExec(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2022-05-16 10:26:30 -07:00
assert.strictEqual(typeof req.body, 'object');
2022-05-16 10:26:30 -07:00
if ('cmd' in req.body) {
if (!Array.isArray(req.body.cmd) || req.body.cmd.length < 1) return next(new HttpError(400, 'cmd must be array with atleast size 1'));
}
2022-05-16 10:26:30 -07:00
const cmd = req.body.cmd || null;
if ('tty' in req.body && typeof req.body.tty !== 'boolean') return next(new HttpError(400, 'tty must be boolean'));
const tty = !!req.body.tty;
2022-11-16 16:08:54 +01:00
if ('lang' in req.body && typeof req.body.lang !== 'string') return next(new HttpError(400, 'lang must be a string'));
2022-05-16 10:26:30 -07:00
if (safe.query(req.app, 'manifest.addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is requied to exec app with docker addon'));
2022-11-16 16:08:54 +01:00
const [error, id] = await safe(apps.createExec(req.app, { cmd, tty, lang: req.body.lang }));
2022-05-16 10:26:30 -07:00
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, { id }));
}
async function startExec(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
assert.strictEqual(typeof req.params.execId, 'string');
2021-08-25 19:41:46 -07:00
const columns = req.query.columns ? parseInt(req.query.columns, 10) : null;
if (isNaN(columns)) return next(new HttpError(400, 'columns must be a number'));
2021-08-25 19:41:46 -07:00
const rows = req.query.rows ? parseInt(req.query.rows, 10) : null;
if (isNaN(rows)) return next(new HttpError(400, 'rows must be a number'));
2021-08-25 19:41:46 -07:00
const tty = req.query.tty === 'true';
if (safe.query(req.app, 'manifest.addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is requied to exec app with docker addon'));
2016-01-18 11:16:06 -08:00
2020-04-08 09:18:58 -07:00
// in a badly configured reverse proxy, we might be here without an upgrade
if (req.headers['upgrade'] !== 'tcp') return next(new HttpError(404, 'exec requires TCP upgrade'));
2022-05-16 10:26:30 -07:00
const [error, duplexStream] = await safe(apps.startExec(req.app, req.params.execId, { rows, columns, tty }));
2021-08-25 19:41:46 -07:00
if (error) return next(BoxError.toHttpError(error));
2021-08-25 19:41:46 -07:00
req.clearTimeout();
res.sendUpgradeHandshake();
2021-08-25 19:41:46 -07:00
// When tty is disabled, the duplexStream has 2 separate streams. When enabled, it has stdout/stderr merged.
duplexStream.pipe(res.socket);
2016-05-22 21:07:05 -07:00
2021-08-25 19:41:46 -07:00
if (tty) {
res.socket.pipe(duplexStream); // in tty mode, the client always waits for server to exit
} else {
demuxStream(res.socket, duplexStream);
res.socket.on('error', function () { duplexStream.end(); });
res.socket.on('end', function () { duplexStream.end(); });
}
}
2016-01-19 13:35:28 +01:00
2022-05-16 10:26:30 -07:00
async function startExecWebSocket(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2022-05-16 10:26:30 -07:00
assert.strictEqual(typeof req.params.execId, 'string');
2017-08-15 20:00:52 +02:00
2021-08-25 19:41:46 -07:00
const columns = req.query.columns ? parseInt(req.query.columns, 10) : null;
if (isNaN(columns)) return next(new HttpError(400, 'columns must be a number'));
2017-08-15 20:00:52 +02:00
2021-08-25 19:41:46 -07:00
const rows = req.query.rows ? parseInt(req.query.rows, 10) : null;
if (isNaN(rows)) return next(new HttpError(400, 'rows must be a number'));
2017-08-15 20:00:52 +02:00
2021-08-25 19:41:46 -07:00
const tty = req.query.tty === 'true' ? true : false;
2017-08-15 20:00:52 +02:00
2020-04-08 09:18:58 -07:00
// in a badly configured reverse proxy, we might be here without an upgrade
if (req.headers['upgrade'] !== 'websocket') return next(new HttpError(404, 'exec requires websocket'));
2022-05-16 11:37:25 -07:00
const [error, duplexStream] = await safe(apps.startExec(req.app, req.params.execId, { rows, columns, tty }));
2021-08-25 19:41:46 -07:00
if (error) return next(BoxError.toHttpError(error));
2017-08-15 20:00:52 +02:00
2021-08-25 19:41:46 -07:00
req.clearTimeout();
res.handleUpgrade(function (ws) {
duplexStream.on('end', function () { ws.close(); });
duplexStream.on('close', function () { ws.close(); });
duplexStream.on('error', function (error) {
2023-04-16 10:49:59 +02:00
debug('duplexStream error: %o', error);
2021-08-25 19:41:46 -07:00
});
duplexStream.on('data', function (data) {
if (ws.readyState !== WebSocket.OPEN) return;
ws.send(data.toString());
});
ws.on('error', function (error) {
2023-04-16 10:49:59 +02:00
debug('websocket error: %o', error);
2021-08-25 19:41:46 -07:00
});
ws.on('message', function (msg) {
duplexStream.write(msg);
});
ws.on('close', function () {
// Clean things up, if any?
2017-08-15 20:00:52 +02:00
});
});
}
2022-05-16 10:26:30 -07:00
async function getExec(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
assert.strictEqual(typeof req.params.execId, 'string');
const [error, result] = await safe(apps.getExec(req.app, req.params.execId));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, result)); // { exitCode, running }
}
2021-07-14 11:07:19 -07:00
async function listBackups(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2016-01-19 13:35:28 +01:00
2021-07-14 11:07:19 -07:00
const page = typeof req.query.page !== 'undefined' ? parseInt(req.query.page) : 1;
2016-03-08 08:57:28 -08:00
if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a postive number'));
2021-07-14 11:07:19 -07:00
const perPage = typeof req.query.per_page !== 'undefined'? parseInt(req.query.per_page) : 25;
2016-03-08 08:57:28 -08:00
if (!perPage || perPage < 0) return next(new HttpError(400, 'per_page query param has to be a postive number'));
const [error, result] = await safe(apps.listBackups(req.app, page, perPage));
2021-07-14 11:07:19 -07:00
if (error) return next(BoxError.toHttpError(error));
2016-01-19 13:35:28 +01:00
2021-07-14 11:07:19 -07:00
next(new HttpSuccess(200, { backups: result }));
2016-01-19 13:35:28 +01:00
}
2017-08-18 20:45:52 -07:00
async function updateBackup(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
assert.strictEqual(typeof req.params.backupId, 'string');
assert.strictEqual(typeof req.body, 'object');
const { label, preserveSecs } = req.body;
if (typeof label !== 'string') return next(new HttpError(400, 'label must be a string'));
if (typeof preserveSecs !== 'number') return next(new HttpError(400, 'preserveSecs must be a number'));
const [error] = await safe(apps.updateBackup(req.app, req.params.backupId, { label, preserveSecs }));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
}
2022-11-03 22:13:57 +01:00
async function downloadBackup(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
assert.strictEqual(typeof req.params.backupId, 'string');
const [error, result] = await safe(apps.getBackupDownloadStream(req.app, req.params.backupId));
if (error) return next(BoxError.toHttpError(error));
res.attachment(`${req.params.backupId}.tgz`);
result.pipe(res);
}
2021-10-21 15:15:39 -07:00
async function uploadFile(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2017-08-18 20:45:52 -07:00
if (typeof req.query.file !== 'string' || !req.query.file) return next(new HttpError(400, 'file query argument must be provided'));
if (!req.files.file) return next(new HttpError(400, 'file must be provided as multipart'));
req.clearTimeout();
2021-10-21 15:15:39 -07:00
const [error] = await safe(apps.uploadFile(req.app, req.files.file.path, req.query.file));
safe.fs.unlinkSync(req.files.file.path); // remove file in /tmp
2017-08-18 20:45:52 -07:00
2021-10-21 15:15:39 -07:00
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, {}));
2017-08-18 20:45:52 -07:00
}
2021-10-21 15:25:15 -07:00
async function downloadFile(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
2017-08-18 20:45:52 -07:00
if (typeof req.query.file !== 'string' || !req.query.file) return next(new HttpError(400, 'file query argument must be provided'));
req.clearTimeout();
2021-10-21 15:25:15 -07:00
const [error, result] = await safe(apps.downloadFile(req.app, req.query.file));
if (error) return next(BoxError.toHttpError(error));
2017-08-18 20:45:52 -07:00
2021-10-21 15:25:15 -07:00
const { stream, filename, size } = result;
const headers = {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename*=utf-8''${encodeURIComponent(filename)}` // RFC 2184 section 4
};
if (size) headers['Content-Length'] = size;
2017-08-20 18:44:26 -07:00
2021-10-21 15:25:15 -07:00
res.writeHead(200, headers);
2017-08-18 20:45:52 -07:00
2021-10-21 15:25:15 -07:00
stream.pipe(res);
2017-08-18 20:45:52 -07:00
}
2020-04-29 21:55:21 -07:00
2021-08-20 09:19:44 -07:00
async function setMounts(req, res, next) {
2020-04-29 21:55:21 -07:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2020-04-29 21:55:21 -07:00
2020-10-29 21:58:14 -07:00
if (!Array.isArray(req.body.mounts)) return next(new HttpError(400, 'mounts should be an array'));
2020-10-28 19:42:48 -07:00
for (let m of req.body.mounts) {
if (!m || typeof m !== 'object') return next(new HttpError(400, 'mounts must be an object'));
if (typeof m.volumeId !== 'string') return next(new HttpError(400, 'volumeId must be a string'));
if (typeof m.readOnly !== 'boolean') return next(new HttpError(400, 'readOnly must be a boolean'));
2020-04-29 21:55:21 -07:00
}
2021-09-30 09:50:30 -07:00
const [error, result] = await safe(apps.setMounts(req.app, req.body.mounts, AuditSource.fromRequest(req)));
2021-08-20 09:19:44 -07:00
if (error) return next(BoxError.toHttpError(error));
2020-04-29 21:55:21 -07:00
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
2020-04-29 21:55:21 -07:00
}
2021-09-21 19:45:29 -07:00
2022-06-08 11:21:09 +02:00
async function setUpstreamUri(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2022-11-23 12:53:21 +01:00
if (req.app.appStoreId !== constants.PROXY_APP_APPSTORE_ID) return next(new HttpError(400, 'upstreamUri can only be set for proxy app'));
2022-06-08 11:21:09 +02:00
if (typeof req.body.upstreamUri !== 'string') return next(new HttpError(400, 'upstreamUri must be a string'));
const [error] = await safe(apps.setUpstreamUri(req.app, req.body.upstreamUri, AuditSource.fromRequest(req)));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
}
2021-09-21 19:45:29 -07:00
async function listEventlog(req, res, next) {
2021-09-21 19:53:05 -07:00
assert.strictEqual(typeof req.app, 'object');
2021-09-21 19:45:29 -07:00
const page = typeof req.query.page !== 'undefined' ? parseInt(req.query.page) : 1;
if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a postive number'));
const perPage = typeof req.query.per_page !== 'undefined'? parseInt(req.query.per_page) : 25;
if (!perPage || perPage < 0) return next(new HttpError(400, 'per_page query param has to be a postive number'));
const [error, eventlogs] = await safe(apps.listEventlog(req.app, page, perPage));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, { eventlogs }));
}
2021-09-21 19:53:05 -07:00
async function checkForUpdates(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
// it can take a while sometimes to get all the app updates one by one
req.clearTimeout();
await updateChecker.checkForUpdates({ automatic: false }); // appId argument is ignored for the moment
next(new HttpSuccess(200, { update: updateChecker.getUpdateInfo() }));
}
2021-09-21 22:19:20 -07:00
async function getTask(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
const [error, result] = await safe(apps.getTask(req.app));
if (error) return next(BoxError.toHttpError(error));
if (result === null) return next(new HttpError(400, 'No active task'));
next(new HttpSuccess(200, result));
}
async function getLimits(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
const [error, limits] = await safe(apps.getLimits(req.app));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, { limits }));
}
2022-10-13 20:32:36 +02:00
async function getGraphs(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
if (!req.query.fromMinutes || !parseInt(req.query.fromMinutes)) return next(new HttpError(400, 'fromMinutes must be a number'));
const fromMinutes = parseInt(req.query.fromMinutes);
const noNullPoints = !!req.query.noNullPoints;
const [error, result] = await safe(graphs.getContainerStats(req.app.id, fromMinutes, noNullPoints));
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(200, result));
}