mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-19 00:11:49 +03:00
f71c074d31
fixes https://github.com/TryGhost/Product/issues/3820 - This adds a new public site endpoint in the members API to check if a site can offer the one-click-subscribe feature - This is implemented on the members API as a copy of the `site` endpoint because the admin API site endpoint is protected by CORS and mainly because it can be served on a different domain than the recommended site and this is hard to detect reliably from the frontend - Added a new calculated setting `allow_self_signup`, which can replace the setting that is currently used in Portal (best to do this after a release otherwise we risk creating issues if a patch release happens)
500 lines
18 KiB
JavaScript
500 lines
18 KiB
JavaScript
const assert = require('assert/strict');
|
|
const sinon = require('sinon');
|
|
const logging = require('@tryghost/logging');
|
|
const SingleUseTokenProvider = require('../../../core/server/services/members/SingleUseTokenProvider');
|
|
const settingsCache = require('../../../core/shared/settings-cache');
|
|
const {agentProvider, fixtureManager, mockManager, matchers} = require('../../utils/e2e-framework');
|
|
const {stringMatching, anyEtag, anyUuid, anyContentLength, anyContentVersion} = matchers;
|
|
const models = require('../../../core/server/models');
|
|
const {anyErrorId} = matchers;
|
|
|
|
const CURRENT_SETTINGS_COUNT = 84;
|
|
|
|
const settingsMatcher = {};
|
|
|
|
const publicHashSettingMatcher = {
|
|
value: stringMatching(/[a-z0-9]{30}/)
|
|
};
|
|
|
|
const labsSettingMatcher = {
|
|
value: stringMatching(/\{[^\s]+\}/)
|
|
};
|
|
|
|
const matchSettingsArray = (length) => {
|
|
const settingsArray = new Array(length).fill(settingsMatcher);
|
|
|
|
if (length > 26) {
|
|
// Added a setting that is alphabetically before 'public_hash'? then you need to increment this counter.
|
|
// Item at index x is the public hash, which is always different
|
|
settingsArray[26] = publicHashSettingMatcher;
|
|
}
|
|
|
|
if (length > 60) {
|
|
// Added a setting that is alphabetically before 'labs'? then you need to increment this counter.
|
|
// Item at index x is the lab settings, which changes as we add and remove features
|
|
settingsArray[60] = labsSettingMatcher;
|
|
}
|
|
|
|
return settingsArray;
|
|
};
|
|
|
|
describe('Settings API', function () {
|
|
let agent;
|
|
|
|
before(async function () {
|
|
agent = await agentProvider.getAdminAPIAgent();
|
|
await fixtureManager.init();
|
|
await agent.loginAsOwner();
|
|
});
|
|
|
|
beforeEach(function () {
|
|
mockManager.mockMail();
|
|
});
|
|
|
|
afterEach(function () {
|
|
mockManager.restore();
|
|
sinon.restore();
|
|
});
|
|
|
|
describe('Browse', function () {
|
|
it('Can request all settings', async function () {
|
|
await agent
|
|
.get('settings/')
|
|
.expectStatus(200)
|
|
.matchBodySnapshot({
|
|
settings: matchSettingsArray(CURRENT_SETTINGS_COUNT)
|
|
})
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag,
|
|
// Special rule for this test, as the labs setting changes a lot
|
|
'content-length': anyContentLength,
|
|
'content-version': anyContentVersion
|
|
});
|
|
});
|
|
|
|
it('Can request settings by group', async function () {
|
|
await agent
|
|
.get('settings/?group=theme')
|
|
.expectStatus(200)
|
|
.matchBodySnapshot({
|
|
settings: matchSettingsArray(1)
|
|
})
|
|
.matchHeaderSnapshot({
|
|
'content-version': anyContentVersion,
|
|
etag: anyEtag
|
|
});
|
|
});
|
|
|
|
it('Requesting core settings by group ignores the parameter and returns no settings', async function () {
|
|
await agent
|
|
.get('settings/?group=core')
|
|
.expectStatus(200)
|
|
.matchBodySnapshot()
|
|
.matchHeaderSnapshot({
|
|
'content-version': anyContentVersion,
|
|
etag: anyEtag
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Edit', function () {
|
|
it('Can edit a setting', async function () {
|
|
const settingsToChange = [
|
|
{
|
|
key: 'title',
|
|
value: []
|
|
},
|
|
{
|
|
key: 'codeinjection_head',
|
|
value: null
|
|
},
|
|
{
|
|
key: 'announcement_content',
|
|
value: '<p>Great news coming soon!</p>'
|
|
},
|
|
{
|
|
key: 'announcement_visibility',
|
|
value: JSON.stringify(['visitors', 'free_members'])
|
|
},
|
|
{
|
|
key: 'navigation',
|
|
value: JSON.stringify([{
|
|
label: 'label1'
|
|
}])
|
|
},
|
|
{
|
|
key: 'slack_username',
|
|
value: 'New Slack Username'
|
|
},
|
|
{
|
|
key: 'is_private',
|
|
value: false
|
|
},
|
|
{
|
|
key: 'meta_title',
|
|
value: 'SEO title'
|
|
},
|
|
{
|
|
key: 'meta_description',
|
|
value: 'SEO description'
|
|
},
|
|
{
|
|
key: 'og_image',
|
|
value: '/content/images/2019/07/facebook.png'
|
|
},
|
|
{
|
|
key: 'og_title',
|
|
value: 'facebook title'
|
|
},
|
|
{
|
|
key: 'og_description',
|
|
value: 'facebook description'
|
|
},
|
|
{
|
|
key: 'twitter_image',
|
|
value: '/content/images/2019/07/twitter.png'
|
|
},
|
|
{
|
|
key: 'twitter_title',
|
|
value: 'twitter title'
|
|
},
|
|
{
|
|
key: 'twitter_description',
|
|
value: 'twitter description'
|
|
},
|
|
{
|
|
key: 'locale',
|
|
value: 'ua'
|
|
},
|
|
{
|
|
key: 'labs',
|
|
value: JSON.stringify({})
|
|
},
|
|
{
|
|
key: 'timezone',
|
|
value: 'Pacific/Auckland'
|
|
},
|
|
{
|
|
key: 'unsplash',
|
|
value: false
|
|
}
|
|
];
|
|
|
|
await agent.put('settings/')
|
|
.body({
|
|
settings: settingsToChange
|
|
})
|
|
.expectStatus(200)
|
|
.matchBodySnapshot({
|
|
settings: matchSettingsArray(CURRENT_SETTINGS_COUNT)
|
|
})
|
|
.matchHeaderSnapshot({
|
|
'content-version': anyContentVersion,
|
|
etag: anyEtag
|
|
});
|
|
|
|
mockManager.assert.sentEmailCount(0);
|
|
});
|
|
|
|
it('removes image size prefixes when setting the icon', async function () {
|
|
const settingsToChange = [
|
|
{
|
|
key: 'icon',
|
|
value: '/content/images/size/w256h256/2019/07/icon.png'
|
|
}
|
|
];
|
|
|
|
const {body} = await agent.put('settings/')
|
|
.body({
|
|
settings: settingsToChange
|
|
})
|
|
.expectStatus(200)
|
|
.matchBodySnapshot({
|
|
settings: matchSettingsArray(CURRENT_SETTINGS_COUNT)
|
|
})
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag,
|
|
// Special rule for this test, as the labs setting changes a lot
|
|
'content-length': anyContentLength,
|
|
'content-version': anyContentVersion
|
|
});
|
|
|
|
// Check returned WITH prefix
|
|
const val = body.settings.find(setting => setting.key === 'icon');
|
|
assert.ok(val);
|
|
assert.equal(val.value, 'http://127.0.0.1:2369/content/images/size/w256h256/2019/07/icon.png');
|
|
|
|
// Check if not changed (also check internal ones)
|
|
const afterValue = settingsCache.get('icon');
|
|
assert.equal(afterValue, 'http://127.0.0.1:2369/content/images/2019/07/icon.png');
|
|
|
|
mockManager.assert.sentEmailCount(0);
|
|
});
|
|
|
|
it('cannot edit uneditable settings', async function () {
|
|
await agent.put('settings/')
|
|
.body({
|
|
settings: [{key: 'email_verification_required', value: true}]
|
|
})
|
|
.expectStatus(200)
|
|
.matchBodySnapshot({
|
|
settings: matchSettingsArray(CURRENT_SETTINGS_COUNT)
|
|
})
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag,
|
|
// Special rule for this test, as the labs setting changes a lot
|
|
'content-length': anyContentLength,
|
|
'content-version': anyContentVersion
|
|
})
|
|
.expect(({body}) => {
|
|
const emailVerificationRequired = body.settings.find(setting => setting.key === 'email_verification_required');
|
|
assert.equal(emailVerificationRequired.value, false);
|
|
});
|
|
mockManager.assert.sentEmailCount(0);
|
|
});
|
|
|
|
it('editing members_support_address triggers email verification flow', async function () {
|
|
await agent.put('settings/')
|
|
.body({
|
|
settings: [{key: 'members_support_address', value: 'support@example.com'}]
|
|
})
|
|
.expectStatus(200)
|
|
.matchBodySnapshot({
|
|
settings: matchSettingsArray(CURRENT_SETTINGS_COUNT)
|
|
})
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag,
|
|
// Special rule for this test, as the labs setting changes a lot
|
|
'content-length': anyContentLength,
|
|
'content-version': anyContentVersion
|
|
})
|
|
.expect(({body}) => {
|
|
const membersSupportAddress = body.settings.find(setting => setting.key === 'members_support_address');
|
|
assert.equal(membersSupportAddress.value, 'noreply');
|
|
|
|
assert.deepEqual(body.meta, {
|
|
sent_email_verification: ['members_support_address']
|
|
});
|
|
});
|
|
|
|
mockManager.assert.sentEmailCount(1);
|
|
mockManager.assert.sentEmail({
|
|
subject: 'Verify email address',
|
|
to: 'support@example.com'
|
|
});
|
|
});
|
|
|
|
it('does not trigger email verification flow if members_support_address remains the same', async function () {
|
|
await models.Settings.edit({
|
|
key: 'members_support_address',
|
|
value: 'support@example.com'
|
|
});
|
|
|
|
await agent.put('settings/')
|
|
.body({
|
|
settings: [{key: 'members_support_address', value: 'support@example.com'}]
|
|
})
|
|
.expectStatus(200)
|
|
.matchBodySnapshot({
|
|
settings: matchSettingsArray(CURRENT_SETTINGS_COUNT)
|
|
})
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag,
|
|
// Special rule for this test, as the labs setting changes a lot
|
|
'content-length': anyContentLength,
|
|
'content-version': anyContentVersion
|
|
})
|
|
.expect(({body}) => {
|
|
const membersSupportAddress = body.settings.find(setting => setting.key === 'members_support_address');
|
|
assert.equal(membersSupportAddress.value, 'support@example.com');
|
|
|
|
assert.deepEqual(body.meta, {});
|
|
});
|
|
|
|
mockManager.assert.sentEmailCount(0);
|
|
});
|
|
|
|
it('fails to edit setting with unsupported announcement_visibility value', async function () {
|
|
const loggingStub = sinon.stub(logging, 'error');
|
|
const settingsToChange = [
|
|
{
|
|
key: 'announcement_visibility',
|
|
value: JSON.stringify(['invalid value'])
|
|
}
|
|
];
|
|
|
|
await agent.put('settings/')
|
|
.body({
|
|
settings: settingsToChange
|
|
})
|
|
.expectStatus(422)
|
|
.matchBodySnapshot({
|
|
errors: [
|
|
{
|
|
id: anyErrorId
|
|
}
|
|
]
|
|
})
|
|
.matchHeaderSnapshot({
|
|
'content-version': anyContentVersion,
|
|
etag: anyEtag
|
|
});
|
|
|
|
sinon.assert.calledOnce(loggingStub);
|
|
});
|
|
|
|
it('fails to edit setting with unsupported announcement_background value', async function () {
|
|
const loggingStub = sinon.stub(logging, 'error');
|
|
const settingsToChange = [
|
|
{
|
|
key: 'announcement_background',
|
|
value: 'not a background value'
|
|
}
|
|
];
|
|
|
|
await agent.put('settings/')
|
|
.body({
|
|
settings: settingsToChange
|
|
})
|
|
.expectStatus(422)
|
|
.matchBodySnapshot({
|
|
errors: [
|
|
{
|
|
id: anyErrorId
|
|
}
|
|
]
|
|
})
|
|
.matchHeaderSnapshot({
|
|
'content-version': anyContentVersion,
|
|
etag: anyEtag
|
|
});
|
|
|
|
sinon.assert.calledOnce(loggingStub);
|
|
});
|
|
});
|
|
|
|
describe('verify key update', function () {
|
|
it('can update members_support_address via token', async function () {
|
|
const token = await (new SingleUseTokenProvider({
|
|
SingleUseTokenModel: models.SingleUseToken,
|
|
validityPeriod: 24 * 60 * 60 * 1000,
|
|
validityPeriodAfterUsage: 10 * 60 * 1000,
|
|
maxUsageCount: 1
|
|
})).create({key: 'members_support_address', value: 'support@example.com'});
|
|
await agent.put('settings/verifications/')
|
|
.body({
|
|
token
|
|
})
|
|
.expectStatus(200)
|
|
.matchBodySnapshot({
|
|
settings: matchSettingsArray(CURRENT_SETTINGS_COUNT)
|
|
})
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag,
|
|
// Special rule for this test, as the labs setting changes a lot
|
|
'content-length': anyContentLength,
|
|
'content-version': anyContentVersion
|
|
})
|
|
.expect(({body}) => {
|
|
const membersSupportAddress = body.settings.find(setting => setting.key === 'members_support_address');
|
|
assert.equal(membersSupportAddress.value, 'support@example.com');
|
|
});
|
|
|
|
mockManager.assert.sentEmailCount(0);
|
|
});
|
|
|
|
it('cannot update invalid keys via token', async function () {
|
|
const loggingStub = sinon.stub(logging, 'error');
|
|
const token = await (new SingleUseTokenProvider({
|
|
SingleUseTokenModel: models.SingleUseToken,
|
|
validityPeriod: 24 * 60 * 60 * 1000,
|
|
validityPeriodAfterUsage: 10 * 60 * 1000,
|
|
maxUsageCount: 1
|
|
})).create({key: 'members_support_address_invalid', value: 'support@example.com'});
|
|
await agent.put('settings/verifications/')
|
|
.body({
|
|
token
|
|
})
|
|
.expectStatus(400)
|
|
.matchBodySnapshot({
|
|
errors: [
|
|
{
|
|
id: anyErrorId
|
|
}
|
|
]
|
|
})
|
|
.matchHeaderSnapshot({
|
|
'content-version': anyContentVersion,
|
|
etag: anyEtag
|
|
});
|
|
|
|
sinon.assert.calledOnce(loggingStub);
|
|
});
|
|
});
|
|
|
|
describe('stripe connect', function () {
|
|
it('can do disconnectStripeConnectIntegration', async function () {
|
|
await agent
|
|
.delete('/settings/stripe/connect/')
|
|
.expectStatus(204)
|
|
.expectEmptyBody()
|
|
.matchHeaderSnapshot({
|
|
'content-version': anyContentVersion,
|
|
etag: anyEtag
|
|
});
|
|
|
|
const stripeSettings = [
|
|
'stripe_connect_publishable_key',
|
|
'stripe_connect_secret_key',
|
|
'stripe_connect_livemode',
|
|
'stripe_connect_display_name',
|
|
'stripe_connect_account_id',
|
|
'members_stripe_webhook_id',
|
|
'members_stripe_webhook_secret'
|
|
];
|
|
|
|
// Assert that the settings are changed as a side effect
|
|
await agent.get('settings/')
|
|
.expect(({body}) => {
|
|
body.settings.forEach((setting) => {
|
|
if (stripeSettings.includes(setting.key)) {
|
|
assert.equal(setting.value, null);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Can attempt to connect to stripe', async function () {
|
|
const settingsToChange = [
|
|
{
|
|
key: 'stripe_connect_integration_token',
|
|
value: JSON.stringify({
|
|
s: 'session_state',
|
|
p: 'public_key',
|
|
a: 'secret_key',
|
|
l: true,
|
|
n: 'Display Name',
|
|
i: 'account_id'
|
|
|
|
})
|
|
}
|
|
];
|
|
|
|
await agent.put('settings/')
|
|
.body({
|
|
settings: settingsToChange
|
|
})
|
|
.expectStatus(400)
|
|
.matchBodySnapshot({
|
|
errors: [{
|
|
id: anyUuid
|
|
}]
|
|
})
|
|
.matchHeaderSnapshot({
|
|
'content-version': anyContentVersion,
|
|
etag: anyEtag
|
|
});
|
|
});
|
|
});
|
|
});
|