diff --git a/src/dockerproxy.js b/src/dockerproxy.js index b03b243d7..915cc1c42 100644 --- a/src/dockerproxy.js +++ b/src/dockerproxy.js @@ -25,7 +25,7 @@ let gHttpServer = null; async function authorizeApp(req, res, next) { // make the tests pass for now if (constants.TEST) { - req.app = { id: 'testappid' }; + req.resources.app = { id: 'testappid' }; return next(); } @@ -34,7 +34,7 @@ async function authorizeApp(req, res, next) { if (!app) return next(new HttpError(401, 'Unauthorized')); if (!app.manifest.addons?.docker) return next(new HttpError(401, 'Unauthorized')); - req.app = app; + req.resources.app = app; next(); } @@ -65,10 +65,10 @@ function attachDockerRequest(req, res, next) { async function containersCreate(req, res, next) { safe.set(req.body, 'HostConfig.NetworkMode', 'cloudron'); // overwrite the network the container lives in safe.set(req.body, 'NetworkingConfig', {}); // drop any custom network configs - safe.set(req.body, 'Labels', Object.assign({}, safe.query(req.body, 'Labels'), { appId: req.app.id, isCloudronManaged: String(false) })); // overwrite the app id to track containers of an app - safe.set(req.body, 'HostConfig.LogConfig', { Type: 'syslog', Config: { 'tag': req.app.id, 'syslog-address': `unix://${paths.SYSLOG_SOCKET_FILE}`, 'syslog-format': 'rfc5424' }}); + safe.set(req.body, 'Labels', Object.assign({}, safe.query(req.body, 'Labels'), { appId: req.resources.app.id, isCloudronManaged: String(false) })); // overwrite the app id to track containers of an app + safe.set(req.body, 'HostConfig.LogConfig', { Type: 'syslog', Config: { 'tag': req.resources.app.id, 'syslog-address': `unix://${paths.SYSLOG_SOCKET_FILE}`, 'syslog-format': 'rfc5424' }}); - const appDataDir = path.join(paths.APPS_DATA_DIR, req.app.id, 'data'); + const appDataDir = path.join(paths.APPS_DATA_DIR, req.resources.app.id, 'data'); debug('containersCreate: original bind mounts:', req.body.HostConfig.Binds); @@ -137,6 +137,11 @@ async function start() { proxyServer.use(authorizeApp) .use(attachDockerRequest) .use(json) + .use((req, res , next) => { + // we store our route resources, like app,volumes,... in req.resources. Those are added in the load() routes + req.resources = {}; + next(); + }) .use(router) .use(process) .use(middleware.lastMile()); diff --git a/src/routes/accesscontrol.js b/src/routes/accesscontrol.js index 1f13513b8..b76c81aaa 100644 --- a/src/routes/accesscontrol.js +++ b/src/routes/accesscontrol.js @@ -90,11 +90,11 @@ function authorize(requiredRole) { async function authorizeOperator(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); assert.strictEqual(typeof req.user, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); assert.strictEqual(typeof req.token, 'object'); if (!tokens.hasScope(req.token, req.method, req.path)) return next(new HttpError(403, 'access token does not have this scope')); - if (apps.isOperator(req.app, req.user)) return next(); + if (apps.isOperator(req.resources.app, req.user)) return next(); return next(new HttpError(403, 'user is not an operator')); } diff --git a/src/routes/applinks.js b/src/routes/applinks.js index 2cbba1d75..e60eed88f 100644 --- a/src/routes/applinks.js +++ b/src/routes/applinks.js @@ -25,7 +25,7 @@ async function load(req, res, next) { if (error) return next(BoxError.toHttpError(error)); if (!result) return next(new HttpError(404, 'Applink not found')); - req.resource = result; + req.resources.applink = result; next(); } @@ -63,9 +63,9 @@ async function get(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); // we have a separate route for this - delete req.resource.icon; + delete req.resources.applink.icon; - next(new HttpSuccess(200, req.resource)); + next(new HttpSuccess(200, req.resources.applink)); } async function update(req, res, next) { @@ -78,7 +78,7 @@ async function update(req, res, next) { if ('tags' in req.body && !Array.isArray(req.body.tags)) return next(new HttpError(400, 'tags must be an array with strings')); if ('icon' in req.body && typeof req.body.icon !== 'string') return next(new HttpError(400, 'icon must be a string')); - const [error] = await safe(applinks.update(req.resource, req.body)); + const [error] = await safe(applinks.update(req.resources.applink, req.body)); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -87,14 +87,14 @@ async function update(req, res, next) { async function del(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - const [error] = await safe(applinks.del(req.resource)); + const [error] = await safe(applinks.del(req.resources.applink)); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); } -async function getIcon(req, res, next) { +async function getIcon(req, res) { assert.strictEqual(typeof req.params.id, 'string'); - res.send(req.resource.icon); + res.send(req.resources.applink.icon); } diff --git a/src/routes/apps.js b/src/routes/apps.js index 99ac1d999..95608a6a2 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -92,16 +92,16 @@ async function load(req, res, next) { if (error) return next(BoxError.toHttpError(error)); if (!result) return next(new HttpError(404, 'App not found')); - req.app = result; + req.resources.app = result; next(); } function getApp(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); - const accessLevel = apps.accessLevel(req.app, req.user); - const result = apps.pickFields(req.app, accessLevel); + const accessLevel = apps.accessLevel(req.resources.app, req.user); + const result = apps.pickFields(req.resources.app, accessLevel); result.accessLevel = accessLevel; next(new HttpSuccess(200, result)); @@ -124,9 +124,9 @@ async function listByUser(req, res, next) { } async function getAppIcon(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); - const [error, icon] = await safe(apps.getIcon(req.app, { original: req.query.original })); + const [error, icon] = await safe(apps.getIcon(req.resources.app, { original: req.query.original })); if (error) return next(BoxError.toHttpError(error)); res.send(icon); @@ -208,11 +208,11 @@ async function install(req, res, next) { async function setAccessRestriction(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); 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))); + const [error] = await safe(apps.setAccessRestriction(req.resources.app, req.body.accessRestriction, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -220,11 +220,11 @@ async function setAccessRestriction(req, res, next) { async function setOperators(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.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))); + const [error] = await safe(apps.setOperators(req.resources.app, req.body.operators, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -232,11 +232,11 @@ async function setOperators(req, res, next) { async function setCrontab(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.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))); + const [error] = await safe(apps.setCrontab(req.resources.app, req.body.crontab, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -244,11 +244,11 @@ async function setCrontab(req, res, next) { async function setLabel(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); 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))); + const [error] = await safe(apps.setLabel(req.resources.app, req.body.label, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -256,12 +256,12 @@ async function setLabel(req, res, next) { async function setTags(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); 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))); + const [error] = await safe(apps.setTags(req.resources.app, req.body.tags, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -269,11 +269,11 @@ async function setTags(req, res, next) { async function setNotes(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (typeof req.body.notes !== 'string') return next(new HttpError(400, 'notes must be a string')); - const [error] = await safe(apps.setNotes(req.app, req.body.notes, AuditSource.fromRequest(req))); + const [error] = await safe(apps.setNotes(req.resources.app, req.body.notes, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -281,11 +281,11 @@ async function setNotes(req, res, next) { async function setChecklistItem(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (typeof req.body.done !== 'boolean') return next(new HttpError(400, 'done must be a boolean')); - const [error] = await safe(apps.setChecklistItem(req.app, req.params.key, req.body.done, AuditSource.fromRequest(req))); + const [error] = await safe(apps.setChecklistItem(req.resources.app, req.params.key, req.body.done, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, {})); @@ -293,11 +293,11 @@ async function setChecklistItem(req, res, next) { async function setIcon(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); 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))); + const [error] = await safe(apps.setIcon(req.resources.app, req.body.icon || null /* empty string means null */, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -305,11 +305,11 @@ async function setIcon(req, res, next) { async function setMemoryLimit(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); 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))); + const [error, result] = await safe(apps.setMemoryLimit(req.resources.app, req.body.memoryLimit, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -317,11 +317,11 @@ async function setMemoryLimit(req, res, next) { async function setCpuQuota(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (typeof req.body.cpuQuota !== 'number') return next(new HttpError(400, 'cpuQuota is not a number')); - const [error, result] = await safe(apps.setCpuQuota(req.app, req.body.cpuQuota, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.setCpuQuota(req.resources.app, req.body.cpuQuota, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -329,11 +329,11 @@ async function setCpuQuota(req, res, next) { async function setAutomaticBackup(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); 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))); + const [error] = await safe(apps.setAutomaticBackup(req.resources.app, req.body.enable, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -341,11 +341,11 @@ async function setAutomaticBackup(req, res, next) { async function setAutomaticUpdate(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); 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))); + const [error] = await safe(apps.setAutomaticUpdate(req.resources.app, req.body.enable, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -353,7 +353,7 @@ async function setAutomaticUpdate(req, res, next) { async function setReverseProxyConfig(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (req.body.robotsTxt !== null && typeof req.body.robotsTxt !== 'string') return next(new HttpError(400, 'robotsTxt is not a string')); @@ -361,7 +361,7 @@ async function setReverseProxyConfig(req, res, next) { 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))); + const [error] = await safe(apps.setReverseProxyConfig(req.resources.app, req.body, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -369,7 +369,7 @@ async function setReverseProxyConfig(req, res, next) { async function setCertificate(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); 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')); @@ -380,7 +380,7 @@ async function setCertificate(req, res, next) { 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))); + const [error] = await safe(apps.setCertificate(req.resources.app, req.body, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -388,12 +388,12 @@ async function setCertificate(req, res, next) { async function setEnvironment(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (!req.body.env || typeof req.body.env !== 'object') return next(new HttpError(400, 'env must be an object')); 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')); - const [error, result] = await safe(apps.setEnvironment(req.app, req.body.env, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.setEnvironment(req.resources.app, req.body.env, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -401,11 +401,11 @@ async function setEnvironment(req, res, next) { async function setDebugMode(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); 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))); + const [error, result] = await safe(apps.setDebugMode(req.resources.app, req.body.debugMode, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -413,7 +413,7 @@ async function setDebugMode(req, res, next) { async function setMailbox(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean')); if (req.body.enable) { @@ -422,7 +422,7 @@ async function setMailbox(req, res, next) { if ('mailboxDisplayName' in req.body && typeof req.body.mailboxDisplayName !== 'string') return next(new HttpError(400, 'mailboxDisplayName must be a string')); } - const [error, result] = await safe(apps.setMailbox(req.app, req.body, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.setMailbox(req.resources.app, req.body, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -430,7 +430,7 @@ async function setMailbox(req, res, next) { async function setInbox(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean')); if (req.body.enable) { @@ -438,7 +438,7 @@ async function setInbox(req, res, next) { 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))); + const [error, result] = await safe(apps.setInbox(req.resources.app, req.body, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -446,11 +446,11 @@ async function setInbox(req, res, next) { async function setTurn(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.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))); + const [error, result] = await safe(apps.setTurn(req.resources.app, req.body.enable, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -458,11 +458,11 @@ async function setTurn(req, res, next) { async function setRedis(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.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))); + const [error, result] = await safe(apps.setRedis(req.resources.app, req.body.enable, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -470,7 +470,7 @@ async function setRedis(req, res, next) { async function setLocation(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (typeof req.body.subdomain !== 'string') return next(new HttpError(400, 'subdomain must be string')); // subdomain may be an empty string if (typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be string')); @@ -495,7 +495,7 @@ async function setLocation(req, res, next) { 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.setLocation(req.app, req.body, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.setLocation(req.resources.app, req.body, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -503,7 +503,7 @@ async function setLocation(req, res, next) { async function setStorage(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); const { storageVolumeId, storageVolumePrefix } = req.body; @@ -512,7 +512,7 @@ async function setStorage(req, res, next) { 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))); + const [error, result] = await safe(apps.setStorage(req.resources.app, storageVolumeId, storageVolumePrefix, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -520,7 +520,7 @@ async function setStorage(req, res, next) { async function repair(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); const data = req.body; @@ -534,7 +534,7 @@ async function repair(req, res, next) { 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))); + const [error, result] = await safe(apps.repair(req.resources.app, data, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -542,13 +542,13 @@ async function repair(req, res, next) { async function restore(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); const data = req.body; 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))); + const [error, result] = await safe(apps.restore(req.resources.app, data.backupId, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -567,7 +567,7 @@ async function restore(req, res, next) { // 3. backupConfig.password and backupConfig.encryptedFilenames async function importApp(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); const data = req.body; @@ -589,7 +589,7 @@ async function importApp(req, res, next) { } } - const [error, result] = await safe(apps.importApp(req.app, data, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.importApp(req.resources.app, data, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -597,9 +597,9 @@ async function importApp(req, res, next) { async function exportApp(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); - const [error, result] = await safe(apps.exportApp(req.app, {}, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.exportApp(req.resources.app, {}, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -607,7 +607,7 @@ async function exportApp(req, res, next) { async function clone(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); const data = req.body; @@ -624,63 +624,63 @@ async function clone(req, res, next) { 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))); + const [error, result] = await safe(apps.clone(req.resources.app, data, req.user, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(201, { id: result.id, taskId: result.taskId })); } async function backup(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); - const [error, result] = await safe(apps.backup(req.app, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.backup(req.resources.app, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); } async function uninstall(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); - const [error, result] = await safe(apps.uninstall(req.app, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.uninstall(req.resources.app, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); } async function archive(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (typeof req.body.backupId !== 'string') return next(new HttpError(400, 'backupId must be a string')); - const [error, result] = await safe(apps.archive(req.app, req.body.backupId, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.archive(req.resources.app, req.body.backupId, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId, id: result.id })); } async function start(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); - const [error, result] = await safe(apps.start(req.app, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.start(req.resources.app, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); } async function stop(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); - const [error, result] = await safe(apps.stop(req.app, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.stop(req.resources.app, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); } async function restart(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); - const [error, result] = await safe(apps.restart(req.app, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.restart(req.resources.app, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -688,7 +688,7 @@ async function restart(req, res, next) { async function update(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); const data = req.body; @@ -709,7 +709,7 @@ async function update(req, res, next) { data.appStoreId = appStoreId; data.manifest = manifest; - [error, result] = await safe(apps.updateApp(req.app, data, AuditSource.fromRequest(req))); + [error, result] = await safe(apps.updateApp(req.resources.app, data, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -717,7 +717,7 @@ async function update(req, res, next) { // this route is for streaming logs async function getLogStream(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); const lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10; // we ignore last-event-id if (isNaN(lines)) return next(new HttpError(400, 'lines must be a valid number')); @@ -730,7 +730,7 @@ async function getLogStream(req, res, next) { format: 'json' }; - const [error, logStream] = await safe(apps.getLogs(req.app, options)); + const [error, logStream] = await safe(apps.getLogs(req.resources.app, options)); if (error) return next(BoxError.toHttpError(error)); res.writeHead(200, { @@ -752,7 +752,7 @@ async function getLogStream(req, res, next) { } async function getLogs(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); const lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10; if (isNaN(lines)) return next(new HttpError(400, 'lines must be a number')); @@ -763,12 +763,12 @@ async function getLogs(req, res, next) { format: req.query.format || 'json' }; - const [error, logStream] = await safe(apps.getLogs(req.app, options)); + const [error, logStream] = await safe(apps.getLogs(req.resources.app, options)); if (error) return next(BoxError.toHttpError(error)); res.writeHead(200, { 'Content-Type': 'application/x-logs', - 'Content-Disposition': `attachment; filename="${req.app.id}.log"`, + 'Content-Disposition': `attachment; filename="${req.resources.app.id}.log"`, 'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no' // disable nginx buffering }); @@ -799,7 +799,7 @@ function demuxStream(stream, stdin) { } async function createExec(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); assert.strictEqual(typeof req.body, 'object'); if ('cmd' in req.body) { @@ -812,16 +812,16 @@ async function createExec(req, res, next) { if ('lang' in req.body && typeof req.body.lang !== 'string') return next(new HttpError(400, 'lang must be a string')); - 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')); + if (safe.query(req.resources.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')); - const [error, id] = await safe(apps.createExec(req.app, { cmd, tty, lang: req.body.lang })); + const [error, id] = await safe(apps.createExec(req.resources.app, { cmd, tty, lang: req.body.lang })); 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.resources.app, 'object'); assert.strictEqual(typeof req.params.execId, 'string'); const columns = req.query.columns ? parseInt(req.query.columns, 10) : null; @@ -832,12 +832,12 @@ async function startExec(req, res, next) { 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')); + if (safe.query(req.resources.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')); // 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')); - const [error, duplexStream] = await safe(apps.startExec(req.app, req.params.execId, { rows, columns, tty })); + const [error, duplexStream] = await safe(apps.startExec(req.resources.app, req.params.execId, { rows, columns, tty })); if (error) return next(BoxError.toHttpError(error)); req.clearTimeout(); @@ -856,7 +856,7 @@ async function startExec(req, res, next) { } async function startExecWebSocket(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); assert.strictEqual(typeof req.params.execId, 'string'); const columns = req.query.columns ? parseInt(req.query.columns, 10) : null; @@ -870,7 +870,7 @@ async function startExecWebSocket(req, res, next) { // 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')); - const [error, duplexStream] = await safe(apps.startExec(req.app, req.params.execId, { rows, columns, tty })); + const [error, duplexStream] = await safe(apps.startExec(req.resources.app, req.params.execId, { rows, columns, tty })); if (error) return next(BoxError.toHttpError(error)); req.clearTimeout(); @@ -899,16 +899,16 @@ async function startExecWebSocket(req, res, next) { } async function getExec(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); assert.strictEqual(typeof req.params.execId, 'string'); - const [error, result] = await safe(apps.getExec(req.app, req.params.execId)); + const [error, result] = await safe(apps.getExec(req.resources.app, req.params.execId)); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, result)); // { exitCode, running } } async function listBackups(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.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')); @@ -916,14 +916,14 @@ async function listBackups(req, res, next) { 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, result] = await safe(apps.listBackups(req.app, page, perPage)); + const [error, result] = await safe(apps.listBackups(req.resources.app, page, perPage)); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, { backups: result })); } async function updateBackup(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); assert.strictEqual(typeof req.params.backupId, 'string'); assert.strictEqual(typeof req.body, 'object'); @@ -931,17 +931,17 @@ async function updateBackup(req, res, next) { 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 })); + const [error] = await safe(apps.updateBackup(req.resources.app, req.params.backupId, { label, preserveSecs })); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); } async function downloadBackup(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); assert.strictEqual(typeof req.params.backupId, 'string'); - const [error, result] = await safe(apps.getBackupDownloadStream(req.app, req.params.backupId)); + const [error, result] = await safe(apps.getBackupDownloadStream(req.resources.app, req.params.backupId)); if (error) return next(BoxError.toHttpError(error)); res.attachment(result.filename); @@ -949,7 +949,7 @@ async function downloadBackup(req, res, next) { } async function uploadFile(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); assert.strictEqual(typeof req.files, 'object'); if (typeof req.query.file !== 'string' || !req.query.file) return next(new HttpError(400, 'file query argument must be provided')); @@ -957,7 +957,7 @@ async function uploadFile(req, res, next) { req.clearTimeout(); - const [error] = await safe(apps.uploadFile(req.app, req.files.file.path, req.query.file)); + const [error] = await safe(apps.uploadFile(req.resources.app, req.files.file.path, req.query.file)); safe.fs.unlinkSync(req.files.file.path); if (error) return next(BoxError.toHttpError(error)); @@ -965,13 +965,13 @@ async function uploadFile(req, res, next) { } async function downloadFile(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (typeof req.query.file !== 'string' || !req.query.file) return next(new HttpError(400, 'file query argument must be provided')); req.clearTimeout(); - const [error, result] = await safe(apps.downloadFile(req.app, req.query.file)); + const [error, result] = await safe(apps.downloadFile(req.resources.app, req.query.file)); if (error) return next(BoxError.toHttpError(error)); const { stream, filename, size } = result; @@ -988,7 +988,7 @@ async function downloadFile(req, res, next) { async function setMounts(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (!Array.isArray(req.body.mounts)) return next(new HttpError(400, 'mounts should be an array')); for (const m of req.body.mounts) { @@ -997,7 +997,7 @@ async function setMounts(req, res, next) { if (typeof m.readOnly !== 'boolean') return next(new HttpError(400, 'readOnly must be a boolean')); } - const [error, result] = await safe(apps.setMounts(req.app, req.body.mounts, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.setMounts(req.resources.app, req.body.mounts, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -1005,11 +1005,11 @@ async function setMounts(req, res, next) { async function setDevices(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (typeof req.body.devices !== 'object') return next(new HttpError(400, 'devices should be an object')); - const [error, result] = await safe(apps.setDevices(req.app, req.body.devices, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.setDevices(req.resources.app, req.body.devices, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { taskId: result.taskId })); @@ -1017,18 +1017,18 @@ async function setDevices(req, res, next) { async function setUpstreamUri(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); 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))); + const [error] = await safe(apps.setUpstreamUri(req.resources.app, req.body.upstreamUri, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); } async function listEventlog(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.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')); @@ -1036,14 +1036,14 @@ async function listEventlog(req, res, next) { 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)); + const [error, eventlogs] = await safe(apps.listEventlog(req.resources.app, page, perPage)); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, { eventlogs })); } async function checkForUpdates(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); // it can take a while sometimes to get all the app updates one by one req.clearTimeout(); @@ -1053,9 +1053,9 @@ async function checkForUpdates(req, res, next) { } async function getTask(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); - const [error, result] = await safe(apps.getTask(req.app)); + const [error, result] = await safe(apps.getTask(req.resources.app)); if (error) return next(BoxError.toHttpError(error)); if (result === null) return next(new HttpError(400, 'No active task')); @@ -1063,7 +1063,7 @@ async function getTask(req, res, next) { } async function getMetrics(req, res, next) { - assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.resources.app, 'object'); if (!req.query.fromSecs || !parseInt(req.query.fromSecs)) return next(new HttpError(400, 'fromSecs must be a number')); if (!req.query.intervalSecs || !parseInt(req.query.intervalSecs)) return next(new HttpError(400, 'intervalSecs must be a number')); @@ -1071,7 +1071,7 @@ async function getMetrics(req, res, next) { const fromSecs = parseInt(req.query.fromSecs); const intervalSecs = parseInt(req.query.intervalSecs); const noNullPoints = !!req.query.noNullPoints; - const [error, result] = await safe(metrics.getContainers(req.app.id, { fromSecs, noNullPoints, intervalSecs })); + const [error, result] = await safe(metrics.getContainers(req.resources.app.id, { fromSecs, noNullPoints, intervalSecs })); if (error) return next(new HttpError(500, error)); next(new HttpSuccess(200, result)); diff --git a/src/routes/archives.js b/src/routes/archives.js index 0b164bc9f..2a06f710d 100644 --- a/src/routes/archives.js +++ b/src/routes/archives.js @@ -26,7 +26,7 @@ async function load(req, res, next) { if (error) return next(BoxError.toHttpError(error)); if (!result) return next(new HttpError(404, 'Backup not found')); - req.resource = result; + req.resources.archive = result; next(); } @@ -47,12 +47,12 @@ async function list(req, res, next) { async function get(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - next(new HttpSuccess(200, req.resource)); + next(new HttpSuccess(200, req.resources.archive)); } async function getIcon(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.archive, 'object'); const [error, icon] = await safe(archives.getIcon(req.params.id, { original: req.query.original })); if (error) return next(BoxError.toHttpError(error)); @@ -62,9 +62,9 @@ async function getIcon(req, res, next) { async function del(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.archive, 'object'); - const [error] = await safe(archives.del(req.resource, AuditSource.fromRequest(req))); + const [error] = await safe(archives.del(req.resources.archive, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); @@ -72,7 +72,7 @@ async function del(req, res, next) { async function unarchive(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.archive, 'object'); assert.strictEqual(typeof req.body, 'object'); const data = req.body; @@ -89,7 +89,7 @@ async function unarchive(req, res, next) { 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')); } - const [error, result] = await safe(apps.unarchive(req.resource, req.body, AuditSource.fromRequest(req))); + const [error, result] = await safe(apps.unarchive(req.resources.archive, req.body, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, { id: result.id, taskId: result.taskId })); diff --git a/src/routes/groups.js b/src/routes/groups.js index 39f802b00..8e15b8748 100644 --- a/src/routes/groups.js +++ b/src/routes/groups.js @@ -27,7 +27,7 @@ async function load(req, res, next) { if (error) return next(BoxError.toHttpError(error)); if (!result) return next(new HttpError(404, 'Group not found')); - req.resource = result; + req.resources.group = result; next(); } @@ -46,41 +46,41 @@ async function add(req, res, next) { async function get(req, res, next) { assert.strictEqual(typeof req.params.groupId, 'string'); - next(new HttpSuccess(200, req.resource)); + next(new HttpSuccess(200, req.resources.group)); } async function setName(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.group, 'object'); assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be a string')); - const [error] = await safe(groups.setName(req.resource, req.body.name, AuditSource.fromRequest(req))); + const [error] = await safe(groups.setName(req.resources.group, req.body.name, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); } async function setMembers(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.group, 'object'); if (!req.body.userIds) return next(new HttpError(404, 'missing or invalid userIds fields')); if (!Array.isArray(req.body.userIds)) return next(new HttpError(404, 'userIds must be an array')); if (req.body.userIds.some((u) => typeof u !== 'string')) return next(new HttpError(400, 'userIds array must contain strings')); - const [error] = await safe(groups.setMembers(req.resource, req.body.userIds, { skipSourceCheck: false }, AuditSource.fromRequest(req))); + const [error] = await safe(groups.setMembers(req.resources.group, req.body.userIds, { skipSourceCheck: false }, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); } async function setAllowedApps(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.group, 'object'); if (!req.body.appIds) return next(new HttpError(404, 'missing or invalid userIds fields')); if (!Array.isArray(req.body.appIds)) return next(new HttpError(404, 'appIds must be an array')); if (req.body.appIds.some((a) => typeof a !== 'string')) return next(new HttpError(400, 'appIds array must contain strings')); - const [error] = await safe(groups.setAllowedApps(req.resource, req.body.appIds, AuditSource.fromRequest(req))); + const [error] = await safe(groups.setAllowedApps(req.resources.group, req.body.appIds, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -96,7 +96,7 @@ async function list(req, res, next) { async function del(req, res, next) { assert.strictEqual(typeof req.params.groupId, 'string'); - const [error] = await safe(groups.del(req.resource, AuditSource.fromRequest(req))); + const [error] = await safe(groups.del(req.resources.group, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); diff --git a/src/routes/notifications.js b/src/routes/notifications.js index 7f711f486..cbe4b485c 100644 --- a/src/routes/notifications.js +++ b/src/routes/notifications.js @@ -21,14 +21,15 @@ async function load(req, res, next) { if (error) return next(BoxError.toHttpError(error)); if (!result) return next(new HttpError(404, 'Notification not found')); - req.notification = result; + req.resources.notification = result; + next(); } function get(req, res, next) { - assert.strictEqual(typeof req.notification, 'object'); + assert.strictEqual(typeof req.resources.notification, 'object'); - next(new HttpSuccess(200, req.notification)); + next(new HttpSuccess(200, req.resources.notification)); } async function list(req, res, next) { @@ -40,7 +41,7 @@ async function list(req, res, next) { if (req.query.acknowledged && !(req.query.acknowledged === 'true' || req.query.acknowledged === 'false')) return next(new HttpError(400, 'acknowledged must be a true or false')); - let filters = {}; + const filters = {}; if (req.query.acknowledged) filters.acknowledged = req.query.acknowledged === 'true'; const [error, result] = await safe(notifications.list(filters, page, perPage)); @@ -49,12 +50,12 @@ async function list(req, res, next) { } async function update(req, res, next) { - assert.strictEqual(typeof req.notification, 'object'); + assert.strictEqual(typeof req.resources.notification, 'object'); assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.acknowledged !== 'boolean') return next(new HttpError(400, 'acknowledged must be a boolean')); - const [error] = await safe(notifications.update(req.notification, { acknowledged: req.body.acknowledged })); + const [error] = await safe(notifications.update(req.resources.notification, { acknowledged: req.body.acknowledged })); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204, {})); } diff --git a/src/routes/oidc.js b/src/routes/oidc.js index ef9c2d763..ab3c230fb 100644 --- a/src/routes/oidc.js +++ b/src/routes/oidc.js @@ -55,14 +55,15 @@ async function loadClient(req, res, next) { if (!client) return next(new HttpError(404, 'OIDC client not found')); if (client.appId) return next(new HttpError(422, 'OIDC client of an internal app')); - req.oidcClient = client; + req.resources.oidcClient = client; + next(); } async function getClient(req, res, next) { assert.strictEqual(typeof req.params.clientId, 'string'); - next(new HttpSuccess(200, req.oidcClient)); + next(new HttpSuccess(200, req.resources.oidcClient)); } async function updateClient(req, res, next) { diff --git a/src/routes/tasks.js b/src/routes/tasks.js index 90bcddc26..b34a10c54 100644 --- a/src/routes/tasks.js +++ b/src/routes/tasks.js @@ -25,14 +25,15 @@ async function load(req, res, next) { if (error) return next(BoxError.toHttpError(error)); if (!result) return next(new HttpError(404, 'Task not found')); - req.task = result; + req.resources.task = result; + next(); } async function get(req, res, next) { assert.strictEqual(typeof req.params.taskId, 'string'); - next(new HttpSuccess(200, tasks.removePrivateFields(req.task))); + next(new HttpSuccess(200, tasks.removePrivateFields(req.resources.task))); } async function list(req, res, next) { @@ -51,16 +52,16 @@ async function list(req, res, next) { } async function stopTask(req, res, next) { - assert.strictEqual(typeof req.task, 'object'); + assert.strictEqual(typeof req.resources.task, 'object'); - const [error] = await safe(tasks.stopTask(req.task.id)); + const [error] = await safe(tasks.stopTask(req.resources.task.id)); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204, {})); } async function getLogs(req, res, next) { - assert.strictEqual(typeof req.task, 'object'); + assert.strictEqual(typeof req.resources.task, 'object'); const lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10; // we ignore last-event-id if (isNaN(lines)) return next(new HttpError(400, 'lines must be a number')); @@ -71,12 +72,12 @@ async function getLogs(req, res, next) { format: req.query.format || 'json' }; - const [error, logStream] = await safe(tasks.getLogs(req.task, options)); + const [error, logStream] = await safe(tasks.getLogs(req.resources.task, options)); if (error) return next(BoxError.toHttpError(error)); res.writeHead(200, { 'Content-Type': 'application/x-logs', - 'Content-Disposition': `attachment; filename="task-${req.task.id}.log"`, + 'Content-Disposition': `attachment; filename="task-${req.resources.task.id}.log"`, 'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no' // disable nginx buffering }); @@ -86,7 +87,7 @@ async function getLogs(req, res, next) { // this route is for streaming logs async function getLogStream(req, res, next) { - assert.strictEqual(typeof req.task, 'object'); + assert.strictEqual(typeof req.resources.task, 'object'); const lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10; // we ignore last-event-id if (isNaN(lines)) return next(new HttpError(400, 'lines must be a valid number')); @@ -101,7 +102,7 @@ async function getLogStream(req, res, next) { format: 'json' }; - const [error, logStream] = await safe(tasks.getLogs(req.task, options)); + const [error, logStream] = await safe(tasks.getLogs(req.resources.task, options)); if (error) return next(BoxError.toHttpError(error)); res.writeHead(200, { diff --git a/src/routes/users.js b/src/routes/users.js index d89279789..589737cd4 100644 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -42,7 +42,8 @@ async function load(req, res, next) { const [error, result] = await safe(users.get(req.params.userId)); if (error) return next(BoxError.toHttpError(error)); if (!result) return next(new HttpError(404, 'User not found')); - req.resource = result; // this is not req.user because req.user is already the authenticated user + + req.resources.user = result; // this is not req.user because req.user is already the authenticated user next(); } @@ -73,40 +74,40 @@ async function add(req, res, next) { } async function setRole(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); assert.strictEqual(typeof req.user, 'object'); assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.role !== 'string') return next(new HttpError(400, 'role must be a string')); - if (req.user.id === req.resource.id) return next(new HttpError(409, 'Cannot set role flag on self')); + if (req.user.id === req.resources.user.id) return next(new HttpError(409, 'Cannot set role flag on self')); if (users.compareRoles(req.user.role, req.body.role) < 0) return next(new HttpError(403, `role '${req.body.role}' is required but you are only '${req.user.role}'`)); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but you are only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but you are only '${req.user.role}'`)); - const [error] = await safe(users.update(req.resource, { role: req.body.role }, AuditSource.fromRequest(req))); + const [error] = await safe(users.update(req.resources.user, { role: req.body.role }, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); } async function setActive(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); assert.strictEqual(typeof req.user, 'object'); assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.active !== 'boolean') return next(new HttpError(400, 'active must be a boolean')); - if (req.user.id === req.resource.id) return next(new HttpError(409, 'Cannot set active flag on self')); + if (req.user.id === req.resources.user.id) return next(new HttpError(409, 'Cannot set active flag on self')); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but you are only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but you are only '${req.user.role}'`)); - const [error] = await safe(users.update(req.resource, { active: req.body.active }, AuditSource.fromRequest(req))); + const [error] = await safe(users.update(req.resources.user, { active: req.body.active }, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); } async function updateProfile(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); assert.strictEqual(typeof req.user, 'object'); assert.strictEqual(typeof req.body, 'object'); @@ -115,10 +116,10 @@ async function updateProfile(req, res, next) { if ('fallbackEmail' in req.body && typeof req.body.fallbackEmail !== 'string') return next(new HttpError(400, 'fallbackEmail must be string')); if ('displayName' in req.body && typeof req.body.displayName !== 'string') return next(new HttpError(400, 'displayName must be string')); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but you are only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but you are only '${req.user.role}'`)); const data = _.pick(req.body, ['username', 'email', 'fallbackEmail', 'displayName']); - const [error] = await safe(users.updateProfile(req.resource, data, AuditSource.fromRequest(req))); + const [error] = await safe(users.updateProfile(req.resources.user, data, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); @@ -142,19 +143,19 @@ async function list(req, res, next) { } function get(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); assert.strictEqual(typeof req.user, 'object'); - next(new HttpSuccess(200, users.removePrivateFields(req.resource))); + next(new HttpSuccess(200, users.removePrivateFields(req.resources.user))); } async function del(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); - if (req.user.id === req.resource.id) return next(new HttpError(409, 'Not allowed to remove yourself.')); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but user has only '${req.user.role}'`)); + if (req.user.id === req.resources.user.id) return next(new HttpError(409, 'Not allowed to remove yourself.')); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but user has only '${req.user.role}'`)); - const [error] = await safe(users.del(req.resource, AuditSource.fromRequest(req))); + const [error] = await safe(users.del(req.resources.user, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); @@ -175,11 +176,11 @@ async function verifyPassword(req, res, next) { } async function disableTwoFactorAuthentication(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but user has only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but user has only '${req.user.role}'`)); - const [error] = await safe(users.disableTwoFactorAuthentication(req.resource, AuditSource.fromRequest(req))); + const [error] = await safe(users.disableTwoFactorAuthentication(req.resources.user, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -187,13 +188,13 @@ async function disableTwoFactorAuthentication(req, res, next) { async function setLocalGroups(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); if (!Array.isArray(req.body.groupIds)) return next(new HttpError(400, 'API call requires a groups array.')); if (req.body.groupIds.some((gid) => typeof gid !== 'string')) return next(new HttpError(400, 'groupIds array must contain strings')); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but user has only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but user has only '${req.user.role}'`)); - const [error] = await safe(groups.setLocalMembership(req.resource, req.body.groupIds, AuditSource.fromRequest(req))); + const [error] = await safe(groups.setLocalMembership(req.resources.user, req.body.groupIds, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); @@ -201,13 +202,13 @@ async function setLocalGroups(req, res, next) { async function setGhost(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); if (typeof req.body.password !== 'string' || !req.body.password) return next(new HttpError(400, 'password must be non-empty string')); if ('expiresAt' in req.body && typeof req.body.expiresAt !== 'number') return next(new HttpError(400, 'expiresAt must be a number')); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but user has only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but user has only '${req.user.role}'`)); - const [error] = await safe(users.setGhost(req.resource, req.body.password, req.body.expiresAt || 0)); + const [error] = await safe(users.setGhost(req.resources.user, req.body.password, req.body.expiresAt || 0)); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); @@ -215,12 +216,12 @@ async function setGhost(req, res, next) { async function setPassword(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); if (typeof req.body.password !== 'string') return next(new HttpError(400, 'password must be a string')); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but user has only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but user has only '${req.user.role}'`)); - const [error] = await safe(users.setPassword(req.resource, req.body.password, AuditSource.fromRequest(req))); + const [error] = await safe(users.setPassword(req.resources.user, req.body.password, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); @@ -228,46 +229,46 @@ async function setPassword(req, res, next) { // This will always return a reset link, if none is set or expired a new one will be created async function getPasswordResetLink(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but user has only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but user has only '${req.user.role}'`)); - const [error, passwordResetLink] = await safe(users.getPasswordResetLink(req.resource, AuditSource.fromRequest(req))); + const [error, passwordResetLink] = await safe(users.getPasswordResetLink(req.resources.user, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, { passwordResetLink })); } async function sendPasswordResetEmail(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); if (!req.body.email || typeof req.body.email !== 'string') return next(new HttpError(400, 'email must be a non-empty string')); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but user has only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but user has only '${req.user.role}'`)); - const [error] = await safe(users.sendPasswordResetEmail(req.resource, req.body.email, AuditSource.fromRequest(req))); + const [error] = await safe(users.sendPasswordResetEmail(req.resources.user, req.body.email, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, {})); } async function getInviteLink(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but user has only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but user has only '${req.user.role}'`)); - const [error, inviteLink] = await safe(users.getInviteLink(req.resource, AuditSource.fromRequest(req))); + const [error, inviteLink] = await safe(users.getInviteLink(req.resources.user, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, { inviteLink })); } async function sendInviteEmail(req, res, next) { - assert.strictEqual(typeof req.resource, 'object'); + assert.strictEqual(typeof req.resources.user, 'object'); if (!req.body.email || typeof req.body.email !== 'string') return next(new HttpError(400, 'email must be a non-empty string')); - if (users.compareRoles(req.user.role, req.resource.role) < 0) return next(new HttpError(403, `role '${req.resource.role}' is required but user has only '${req.user.role}'`)); + if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but user has only '${req.user.role}'`)); - const [error] = await safe(users.sendInviteEmail(req.resource, req.body.email, AuditSource.fromRequest(req))); + const [error] = await safe(users.sendInviteEmail(req.resources.user, req.body.email, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202, {})); diff --git a/src/routes/volumes.js b/src/routes/volumes.js index 89ac5074d..3f59bc00c 100644 --- a/src/routes/volumes.js +++ b/src/routes/volumes.js @@ -25,7 +25,9 @@ async function load(req, res, next) { const [error, result] = await safe(volumes.get(req.params.id)); if (error) return next(BoxError.toHttpError(error)); if (!result) return next(new HttpError(404, 'Volume not found')); - req.volume = result; + + req.resources.volume = result; + next(); } @@ -46,7 +48,7 @@ async function add(req, res, next) { async function get(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - next(new HttpSuccess(200, volumes.removePrivateFields(req.volume))); + next(new HttpSuccess(200, volumes.removePrivateFields(req.resources.volume))); } async function update(req, res, next) { @@ -54,7 +56,7 @@ async function update(req, res, next) { if (!req.body.mountOptions || typeof req.body.mountOptions !== 'object') return next(new HttpError(400, 'mountOptions must be a non-null object')); - const [error] = await safe(volumes.update(req.volume.id, req.body.mountOptions, AuditSource.fromRequest(req))); + const [error] = await safe(volumes.update(req.resources.volume.id, req.body.mountOptions, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); @@ -63,7 +65,7 @@ async function update(req, res, next) { async function del(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - const [error] = await safe(volumes.del(req.volume, AuditSource.fromRequest(req))); + const [error] = await safe(volumes.del(req.resources.volume, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204)); } @@ -80,7 +82,7 @@ async function list(req, res, next) { async function remount(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - const [error] = await safe(volumes.remount(req.volume, AuditSource.fromRequest(req))); + const [error] = await safe(volumes.remount(req.resources.volume, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(202)); } @@ -88,7 +90,7 @@ async function remount(req, res, next) { async function getStatus(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); - const [error, status] = await safe(volumes.getStatus(req.volume)); + const [error, status] = await safe(volumes.getStatus(req.resources.volume)); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, status)); } diff --git a/src/server.js b/src/server.js index 9b2892ae4..03809e0d4 100644 --- a/src/server.js +++ b/src/server.js @@ -54,6 +54,11 @@ async function initializeExpressSync() { // search for req.clearTimeout in route handlers to see places where this timeout is reset .use(middleware.timeout(REQUEST_TIMEOUT, { respond: true })) .use(middleware.cors({ origins: [ '*' ], allowCredentials: false })) + .use((req, res , next) => { + // we store our route resources, like app,volumes,... in req.resources. Those are added in the load() routes + req.resources = {}; + next(); + }) .use(router) .use(notFoundHandler) .use(middleware.lastMile());