diff --git a/CHANGES b/CHANGES index 49929586a..10152b053 100644 --- a/CHANGES +++ b/CHANGES @@ -1839,4 +1839,5 @@ [5.0.4] * Fix potential previlige escalation because of ghost file * linode: dns backend +* make branding routes owner only diff --git a/src/routes/branding.js b/src/routes/branding.js new file mode 100644 index 000000000..01ce10334 --- /dev/null +++ b/src/routes/branding.js @@ -0,0 +1,102 @@ +'use strict'; + +exports = module.exports = { + get, + set +}; + +var assert = require('assert'), + BoxError = require('../boxerror.js'), + HttpError = require('connect-lastmile').HttpError, + HttpSuccess = require('connect-lastmile').HttpSuccess, + safe = require('safetydance'), + settings = require('../settings.js'); + +function getFooter(req, res, next) { + settings.getFooter(function (error, footer) { + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, { footer })); + }); +} + +function setFooter(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + + if (typeof req.body.footer !== 'string') return next(new HttpError(400, 'footer is required')); + + settings.setFooter(req.body.footer, function (error) { + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, {})); + }); +} + +function setCloudronName(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + + if (typeof req.body.name !== 'string') return next(new HttpError(400, 'name is required')); + + settings.setCloudronName(req.body.name, function (error) { + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(202, {})); + }); +} + +function getCloudronName(req, res, next) { + settings.getCloudronName(function (error, name) { + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, { name: name })); + }); +} + +function setCloudronAvatar(req, res, next) { + assert.strictEqual(typeof req.files, 'object'); + + if (!req.files.avatar) return next(new HttpError(400, 'avatar must be provided')); + var avatar = safe.fs.readFileSync(req.files.avatar.path); + + settings.setCloudronAvatar(avatar, function (error) { + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(202, {})); + }); +} + +function getCloudronAvatar(req, res, next) { + settings.getCloudronAvatar(function (error, avatar) { + if (error) return next(BoxError.toHttpError(error)); + + // avoid caching the avatar on the client to see avatar changes immediately + res.set('Cache-Control', 'no-cache'); + + res.set('Content-Type', 'image/png'); + res.status(200).send(avatar); + }); +} + +function get(req, res, next) { + assert.strictEqual(typeof req.params.setting, 'string'); + + switch (req.params.setting) { + case settings.CLOUDRON_NAME_KEY: return getCloudronName(req, res, next); + case settings.FOOTER_KEY: return getFooter(req, res, next); + case settings.CLOUDRON_AVATAR_KEY: return getCloudronAvatar(req, res, next); + + default: return next(new HttpError(404, 'No such setting')); + } +} + +function set(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + + switch (req.params.setting) { + case settings.CLOUDRON_NAME_KEY: return setCloudronName(req, res, next); + case settings.FOOTER_KEY: return setFooter(req, res, next); + case settings.CLOUDRON_AVATAR_KEY: return setCloudronAvatar(req, res, next); + + default: return next(new HttpError(404, 'No such branding')); + } +} diff --git a/src/routes/index.js b/src/routes/index.js index e357eb0d9..5aed8a521 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -6,6 +6,7 @@ exports = module.exports = { apps: require('./apps.js'), appstore: require('./appstore.js'), backups: require('./backups.js'), + branding: require('./branding.js'), cloudron: require('./cloudron.js'), domains: require('./domains.js'), eventlog: require('./eventlog.js'), diff --git a/src/server.js b/src/server.js index 0cb7bec30..fdbf597fc 100644 --- a/src/server.js +++ b/src/server.js @@ -79,6 +79,7 @@ function initializeExpressSync() { // to keep routes code short const password = routes.accesscontrol.passwordAuth; const token = routes.accesscontrol.tokenAuth; + const authorizeOwner = routes.accesscontrol.authorize(users.ROLE_OWNER); const authorizeAdmin = routes.accesscontrol.authorize(users.ROLE_ADMIN); const authorizeUserManager = routes.accesscontrol.authorize(users.ROLE_USER_MANAGER); @@ -230,6 +231,11 @@ function initializeExpressSync() { router.get ('/api/v1/apps/:id/download', token, authorizeAdmin, routes.apps.downloadFile); router.post('/api/v1/apps/:id/upload', token, authorizeAdmin, multipart, routes.apps.uploadFile); + router.get ('/api/v1/branding/:setting', token, authorizeOwner, routes.branding.get); + router.post('/api/v1/branding/:setting', token, authorizeOwner, (req, res, next) => { + return req.params.setting === 'cloudron_avatar' ? multipart(req, res, next) : next(); + }, routes.settings.set); + // settings routes (these are for the settings tab - avatar & name have public routes for normal users. see above) router.get ('/api/v1/settings/:setting', token, authorizeAdmin, routes.settings.get); router.post('/api/v1/settings/:setting', token, authorizeAdmin, (req, res, next) => {