diff --git a/dashboard/src/js/client.js b/dashboard/src/js/client.js index fefc036d1..a8aea2173 100644 --- a/dashboard/src/js/client.js +++ b/dashboard/src/js/client.js @@ -1031,7 +1031,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }; Client.prototype.getSupportConfig = function (callback) { - get('/api/v1/settings/support_config', null, function (error, data, status) { + get('/api/v1/support/config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); diff --git a/src/mailer.js b/src/mailer.js index 48722d18c..5b2b485d7 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -25,6 +25,7 @@ const assert = require('assert'), path = require('path'), safe = require('safetydance'), settings = require('./settings.js'), + support = require('./support.js'), translation = require('./translation.js'), util = require('util'); @@ -33,7 +34,7 @@ const MAIL_TEMPLATES_DIR = path.join(__dirname, 'mail_templates'); // This will collect the most common details required for notification emails async function getMailConfig() { const cloudronName = await branding.getCloudronName(); - const supportConfig = await settings.getSupportConfig(); + const supportConfig = await support.getConfig(); return { cloudronName, diff --git a/src/routes/settings.js b/src/routes/settings.js index db23e5014..f294c64f3 100644 --- a/src/routes/settings.js +++ b/src/routes/settings.js @@ -54,13 +54,6 @@ async function setTimeZone(req, res, next) { next(new HttpSuccess(200, {})); } -async function getSupportConfig(req, res, next) { - const [error, supportConfig] = await safe(settings.getSupportConfig()); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(200, supportConfig)); -} - async function getBackupConfig(req, res, next) { const [error, backupConfig] = await safe(settings.getBackupConfig()); if (error) return next(BoxError.toHttpError(error)); @@ -314,7 +307,6 @@ function get(req, res, next) { case settings.TIME_ZONE_KEY: return getTimeZone(req, res, next); case settings.PROFILE_CONFIG_KEY: return getProfileConfig(req, res, next); - case settings.SUPPORT_CONFIG_KEY: return getSupportConfig(req, res, next); default: return next(new HttpError(404, 'No such setting')); } diff --git a/src/routes/support.js b/src/routes/support.js index 36b0ebb22..c85dd4dc0 100644 --- a/src/routes/support.js +++ b/src/routes/support.js @@ -6,6 +6,8 @@ exports = module.exports = { getRemoteSupport, enableRemoteSupport, + getConfig, + canCreateTicket, canEnableRemoteSupport }; @@ -13,15 +15,22 @@ exports = module.exports = { const appstore = require('../appstore.js'), assert = require('assert'), AuditSource = require('../auditsource.js'), + BoxError = require('../boxerror.js'), constants = require('../constants.js'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, safe = require('safetydance'), - settings = require('../settings.js'), support = require('../support.js'); +async function getConfig(req, res, next) { + const [error, supportConfig] = await safe(support.getConfig()); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, supportConfig)); +} + async function canCreateTicket(req, res, next) { - const [error, supportConfig] = await safe(settings.getSupportConfig()); + const [error, supportConfig] = await safe(support.getConfig()); if (error) return next(new HttpError(503, error.message)); if (!supportConfig.submitTickets) return next(new HttpError(405, 'feature disabled by admin')); @@ -42,7 +51,7 @@ async function createTicket(req, res, next) { if (req.body.altEmail && typeof req.body.altEmail !== 'string') return next(new HttpError(400, 'altEmail must be string')); if (req.body.enableSshSupport && typeof req.body.enableSshSupport !== 'boolean') return next(new HttpError(400, 'enableSshSupport must be a boolean')); - const [error, supportConfig] = await safe(settings.getSupportConfig()); + const [error, supportConfig] = await safe(support.getConfig()); if (error) return next(new HttpError(503, `Error getting support config: ${error.message}`)); if (supportConfig.email !== constants.SUPPORT_EMAIL) return next(new HttpError(503, 'Sending to non-cloudron email not implemented yet')); @@ -53,7 +62,7 @@ async function createTicket(req, res, next) { } async function canEnableRemoteSupport(req, res, next) { - const [error, supportConfig] = await safe(settings.getSupportConfig()); + const [error, supportConfig] = await safe(support.getConfig()); if (error) return next(new HttpError(503, error.message)); if (!supportConfig.remoteSupport) return next(new HttpError(405, 'feature disabled by admin')); diff --git a/src/routes/test/support-test.js b/src/routes/test/support-test.js index 6528d04fb..01c588e07 100644 --- a/src/routes/test/support-test.js +++ b/src/routes/test/support-test.js @@ -83,6 +83,34 @@ describe('Support API', function () { }); }); + describe('config', function () { + it('normal user cannot get config', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/support/config`) + .query({ access_token: user.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(403); + }); + + it('admin also cannot get config', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/support/config`) + .query({ access_token: admin.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(403); + }); + + it('owner can get config', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/support/config`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body.email).to.be('support@cloudron.io'); + expect(response.body.remoteSupport).to.be(true); + expect(response.body.submitTickets).to.be(true); + }); + }); + describe('ticket', function () { it('fails without token', async function () { const response = await superagent.post(`${serverUrl}/api/v1/support/ticket`) diff --git a/src/server.js b/src/server.js index d3cecf104..ae43661e3 100644 --- a/src/server.js +++ b/src/server.js @@ -342,6 +342,7 @@ async function initializeExpressSync() { // support routes router.post('/api/v1/support/ticket', json, token, authorizeOwner, routes.support.canCreateTicket, routes.support.createTicket); + router.get ('/api/v1/support/config', token, authorizeOwner, routes.support.getConfig); router.get ('/api/v1/support/remote_support', token, authorizeOwner, routes.support.getRemoteSupport); router.post('/api/v1/support/remote_support', json, token, authorizeOwner, routes.support.canEnableRemoteSupport, routes.support.enableRemoteSupport); diff --git a/src/settings.js b/src/settings.js index a58356b3f..92bdefb8e 100644 --- a/src/settings.js +++ b/src/settings.js @@ -43,7 +43,6 @@ exports = module.exports = { getGhosts, setGhosts, - getSupportConfig, provider, list, initCache, @@ -197,17 +196,6 @@ const gDefaults = (function () { result[exports.GHOSTS_CONFIG_KEY] = {}; - result[exports.SUPPORT_CONFIG_KEY] = { - email: 'support@cloudron.io', - remoteSupport: true, - ticketFormBody: - 'Use this form to open support tickets. You can also write directly to [support@cloudron.io](mailto:support@cloudron.io).\n\n' - + '* [Knowledge Base & App Docs](https://docs.cloudron.io/apps/?support_view)\n' - + '* [Custom App Packaging & API](https://docs.cloudron.io/custom-apps/tutorial/?support_view)\n' - + '* [Forum](https://forum.cloudron.io/)\n\n', - submitTickets: true - }; - return result; })(); @@ -535,13 +523,6 @@ async function setGhosts(ghosts) { notifyChange(exports.GHOSTS_CONFIG_KEY, ghosts); } -async function getSupportConfig() { - const value = await get(exports.SUPPORT_CONFIG_KEY); - if (value === null) return gDefaults[exports.SUPPORT_CONFIG_KEY]; - - return JSON.parse(value); -} - async function getLanguage() { const value = await get(exports.LANGUAGE_KEY); if (value === null) return gDefaults[exports.LANGUAGE_KEY]; diff --git a/src/support.js b/src/support.js index be3fb0bc7..bba0b432d 100644 --- a/src/support.js +++ b/src/support.js @@ -4,6 +4,8 @@ exports = module.exports = { getRemoteSupport, enableRemoteSupport, + getConfig, + _sshInfo: sshInfo }; @@ -14,6 +16,7 @@ const assert = require('assert'), path = require('path'), paths = require('./paths.js'), safe = require('safetydance'), + settings = require('./settings.js'), shell = require('./shell.js'); // the logic here is also used in the cloudron-support tool @@ -50,3 +53,17 @@ async function enableRemoteSupport(enable, auditSource) { await eventlog.add(eventlog.ACTION_SUPPORT_SSH, auditSource, { enable }); } + +async function getConfig() { + const value = await settings.get(settings.SUPPORT_CONFIG_KEY); + return value ? JSON.parse(value) : { + email: 'support@cloudron.io', + remoteSupport: true, + ticketFormBody: + 'Use this form to open support tickets. You can also write directly to [support@cloudron.io](mailto:support@cloudron.io).\n\n' + + '* [Knowledge Base & App Docs](https://docs.cloudron.io/apps/?support_view)\n' + + '* [Custom App Packaging & API](https://docs.cloudron.io/custom-apps/tutorial/?support_view)\n' + + '* [Forum](https://forum.cloudron.io/)\n\n', + submitTickets: true + }; +}