Ghost/core/server/services/settings/settings-bread-service.js

211 lines
7.4 KiB
JavaScript
Raw Normal View History

const _ = require('lodash');
const tpl = require('@tryghost/tpl');
const {NotFoundError, NoPermissionError, BadRequestError} = require('@tryghost/errors');
const {obfuscatedSetting, isSecretSetting, hideValueIfSecret} = require('./settings-utils');
const messages = {
problemFindingSetting: 'Problem finding setting: {key}',
accessCoreSettingFromExtReq: 'Attempted to access core setting from external request'
};
class SettingsBREADService {
/**
*
* @param {Object} options
* @param {Object} options.SettingsModel
* @param {Object} options.settingsCache - SettingsCache instance
* @param {Object} options.labsService - labs service instance
*/
constructor({SettingsModel, settingsCache, labsService}) {
this.SettingsModel = SettingsModel;
this.settingsCache = settingsCache;
this.labs = labsService;
}
/**
*
* @param {Object} context ghost API context instance
* @returns
*/
browse(context) {
let settings = this.settingsCache.getAll();
return this._formatBrowse(settings, context);
}
/**
*
* @param {String} key setting key
* @param {Object} [context] API context instance
* @returns {Object} an object with a filled out key that comes in a parameter
*/
read(key, context) {
let setting;
if (key === 'slack') {
const slackURL = this.settingsCache.get('slack_url', {resolve: false});
const slackUsername = this.settingsCache.get('slack_username', {resolve: false});
setting = slackURL || slackUsername;
setting.key = 'slack';
setting.value = [{
url: slackURL && slackURL.value,
username: slackUsername && slackUsername.value
}];
} else {
setting = this.settingsCache.get(key, {resolve: false});
}
if (!setting) {
return Promise.reject(new NotFoundError({
message: tpl(messages.problemFindingSetting, {
key: key
})
}));
}
// @TODO: handle in settings model permissible fn
if (setting.group === 'core' && !(context && context.internal)) {
return Promise.reject(new NoPermissionError({
message: tpl(messages.accessCoreSettingFromExtReq)
}));
}
// NOTE: Labs flags can exist outside of the DB when they are forced on/off
// so we grab them from the labs service instead as that's source-of-truth
if (setting.key === 'labs') {
setting.value = JSON.stringify(this.labs.getAll());
}
setting = hideValueIfSecret(setting);
return {
[key]: setting
};
}
/**
*
* @param {Object[]} settings
* @param {Object} options
* @param {Object} [options.context]
* @param {Object} [stripeConnectData]
* @returns
*/
async edit(settings, options, stripeConnectData) {
const filteredSettings = settings.filter((setting) => {
// The `stripe_connect_integration_token` "setting" is only used to set the `stripe_connect_*` settings.
return ![
'stripe_connect_integration_token',
'stripe_connect_publishable_key',
'stripe_connect_secret_key',
'stripe_connect_livemode',
'stripe_connect_account_id',
'stripe_connect_display_name'
].includes(setting.key)
// Remove obfuscated settings
&& !(setting.value === obfuscatedSetting && isSecretSetting(setting));
});
const getSetting = setting => this.settingsCache.get(setting.key, {resolve: false});
const firstUnknownSetting = filteredSettings.find(setting => !getSetting(setting));
if (firstUnknownSetting) {
throw new NotFoundError({
message: tpl(messages.problemFindingSetting, {
key: firstUnknownSetting.key
})
});
}
if (!(options.context && options.context.internal)) {
const firstCoreSetting = filteredSettings.find(setting => getSetting(setting).group === 'core');
if (firstCoreSetting) {
throw new NoPermissionError({
message: tpl(messages.accessCoreSettingFromExtReq)
});
}
}
if (stripeConnectData) {
filteredSettings.push({
key: 'stripe_connect_publishable_key',
value: stripeConnectData.public_key
});
filteredSettings.push({
key: 'stripe_connect_secret_key',
value: stripeConnectData.secret_key
});
filteredSettings.push({
key: 'stripe_connect_livemode',
value: stripeConnectData.livemode
});
filteredSettings.push({
key: 'stripe_connect_display_name',
value: stripeConnectData.display_name
});
filteredSettings.push({
key: 'stripe_connect_account_id',
value: stripeConnectData.account_id
});
}
return this.SettingsModel.edit(filteredSettings, options).then((result) => {
return this._formatBrowse(_.keyBy(_.invokeMap(result, 'toJSON'), 'key'), options.context);
});
}
/**
*
* @param {Object} stripeConnectIntegrationToken
* @param {Function} getSessionProp sync function fetching property from session store
* @param {Function} getStripeConnectTokenData async function retreiving Stripe Connect data for settings
* @returns {Promise<Object>} resolves with an object with following keys: public_key, secret_key, livemode, display_name, account_id
*/
async getStripeConnectData(stripeConnectIntegrationToken, getSessionProp, getStripeConnectTokenData) {
if (stripeConnectIntegrationToken && stripeConnectIntegrationToken.value) {
try {
return await getStripeConnectTokenData(stripeConnectIntegrationToken.value, getSessionProp);
} catch (err) {
throw new BadRequestError({
err,
message: 'The Stripe Connect token could not be parsed.'
});
}
}
}
_formatBrowse(inputSettings, context) {
let settings = _.values(inputSettings);
// CASE: no context passed (functional call)
if (!context) {
return Promise.resolve(settings.filter((setting) => {
return setting.group === 'site';
}));
}
if (!context.internal) {
// CASE: omit core settings unless internal request
settings = _.filter(settings, (setting) => {
const isCore = setting.group === 'core';
return !isCore;
});
// CASE: omit secret settings unless internal request
settings = settings.map(hideValueIfSecret);
}
// NOTE: Labs flags can exist outside of the DB when they are forced on/off
// so we grab them from the labs service instead as that's source-of-truth
const labsSetting = settings.find(setting => setting.key === 'labs');
if (labsSetting) {
labsSetting.value = JSON.stringify(this.labs.getAll());
}
return settings;
}
}
module.exports = SettingsBREADService;