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

1010 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,
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,
listEventlog,
2020-10-27 17:11:50 -07:00
listBackups,
repair,
2020-10-27 17:11:50 -07:00
setAccessRestriction,
setOperators,
setCrontab,
2020-10-27 17:11:50 -07:00
setLabel,
setTags,
setIcon,
setTurn,
2023-07-13 16:37:33 +05:30
setRedis,
2020-10-27 17:11:50 -07:00
setMemoryLimit,
setCpuQuota,
2020-10-27 17:11:50 -07:00
setAutomaticBackup,
setAutomaticUpdate,
setReverseProxyConfig,
setCertificate,
setDebugMode,
setEnvironment,
setMailbox,
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,
2020-10-27 17:11:50 -07:00
uploadFile,
downloadFile,
updateBackup,
2022-11-03 22:13:57 +01:00
downloadBackup,
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'),
2023-08-04 15:34:38 +05:30
appstore = require('../appstore.js'),
assert = require('assert'),
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;
2021-08-20 09:19:44 -07:00
next();
}
function getApp(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
const result = apps.removeInternalFields(req.app);
result.accessLevel = apps.accessLevel(req.app, req.user);
next(new HttpSuccess(200, result));
}
2021-08-20 09:19:44 -07:00
async function listByUser(req, res, next) {
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));
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
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'));
if ('sso' in data && typeof data.sso !== 'boolean') return next(new HttpError(400, 'sso must be a boolean'));
if ('enableBackup' in data && typeof data.enableBackup !== 'boolean') return next(new HttpError(400, 'enableBackup must be a boolean'));
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'));
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'));
}
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'));
}
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'));
}
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'));
if ('enableTurn' in data && typeof data.enableTurn !== 'boolean') return next(new HttpError(400, 'enableTurn must be boolean'));
2023-08-04 15:34:38 +05:30
let [error, result] = await safe(appstore.downloadManifest(data.appStoreId, data.manifest));
2021-08-20 09:19:44 -07:00
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;
[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'));
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
}
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'));
const [error] = await safe(apps.setOperators(req.app, req.body.operators, AuditSource.fromRequest(req)));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
}
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'));
const [error] = await safe(apps.setCrontab(req.app, req.body.crontab, AuditSource.fromRequest(req)));
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'));
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'));
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'));
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'));
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 setCpuQuota(req, res, next) {
2020-01-28 21:30:35 -08:00
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
2020-01-28 21:30:35 -08:00
if (typeof req.body.cpuQuota !== 'number') return next(new HttpError(400, 'cpuQuota is not a number'));
2020-01-28 21:30:35 -08:00
apps.setCpuQuota(req.app, req.body.cpuQuota, 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'));
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'));
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'));
if (req.body.csp !== null && typeof req.body.csp !== 'string') return next(new HttpError(400, 'csp is not a string'));
if (typeof req.body.hstsPreload !== 'boolean') return next(new HttpError(400, 'hstsPreload must be a boolean'));
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
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'));
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
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'));
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
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
}
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 }));
}
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 }));
}
2023-07-13 16:37:33 +05:30
async function setRedis(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.setRedis(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
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'));
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'));
}
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
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) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
const data = req.body;
Fix repair If a task fails, we can either: * allow other task ops to be called - we cannot do this because the ops are fine-grained. for example, a restore failure removes many things and calling set-memory or set-location in that state won't make sense. * provide a generic repair route - this allows one to override args and call the failed task again. this is what we have now but has the issue that this repair function has to know about all the other op functions. for example, for argument validation. we can do some complicated refactoring to make it work if we want. * just a generic total re-configure - this does not work because clone/restore/backup/datadir/uninstall/update failure leaves the app in a state which re-configure cannot do anything about. * allow the failed op to be called again - this seems the easiest. we just allow the route to be called again in the error state. * if we hit a state where even providing extra args, cannot get you out of this "error" state, we have to provide some repair route. for example, maybe the container disappeared by some docke error. user clicks 'repair' to recreate the container. this route does not have to take any args. The final solution is: * a failed task can be called again via the route. so we can resubmit any args and we get validation * repair route just re-configures and can be called in any state to just rebuild container. re-configure is also doing only local changes (docker, nginx) * install/clone failures are fixed using repair route. updated manifest can be passed in. * UI shows backup selector for restore failures * UI shows domain selector for change location failulre
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'));
}
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));
2021-08-20 09:19:44 -07:00
next(new HttpSuccess(202, { taskId: result.taskId }));
}
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'));
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'));
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'));
// testing backup config can take sometime
req.clearTimeout();
}
}
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) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.app, 'object');
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));
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 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'));
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'));
}
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'));
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');
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');
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');
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');
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
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'));
2023-08-05 08:48:03 +05:30
let [error, result] = await safe(appstore.downloadManifest(data.appStoreId, data.manifest));
2021-08-20 09:19:44 -07:00
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;
[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'));
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,
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.destroy());
2021-10-04 10:08:11 -07:00
logStream.on('data', function (data) {
2022-04-14 17:41:41 -05:00
const obj = JSON.parse(data);
const sse = `data: ${JSON.stringify(obj)}\n\n`;
res.write(sse);
});
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');
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'));
const options = {
lines,
follow: false,
format: req.query.format || 'json'
};
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
});
res.on('close', () => logStream.destroy());
2021-10-01 09:23:20 -07:00
logStream.pipe(res);
}
function demuxStream(stream, stdin) {
2022-04-14 17:41:41 -05:00
let header = null;
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);
if (length === 0) {
header = null;
return stdin.end(); // EOF
}
2022-04-14 17:41:41 -05:00
const payload = stream.read(length);
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
// 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);
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');
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' ? true : false;
// 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));
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) {
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) {
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?
});
});
}
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
}
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(result.filename);
result.stream.pipe(res);
2022-11-03 22:13:57 +01:00
}
2021-10-21 15:15:39 -07:00
async function uploadFile(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
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
2021-10-21 15:15:39 -07:00
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, {}));
}
2021-10-21 15:25:15 -07:00
async function downloadFile(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
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));
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);
2021-10-21 15:25:15 -07:00
stream.pipe(res);
}
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
}
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
}
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');
if (req.app.manifest.id !== 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, {}));
}
async function listEventlog(req, res, next) {
2021-09-21 19:53:05 -07:00
assert.strictEqual(typeof req.app, 'object');
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));
}
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));
}