Migrated 'invite' to 'all' for sites that were not truly invite-only (#12907)

refs https://github.com/TryGhost/Team/issues/579

The previous `members_allow_free_signup` -> `members_signup_access` migration made a direct correlation between the toggle `true/false` to `all/invite` under the assumption that behaviour between the two settings would be identical. The assumption was incorrect and the behaviour is changing so `invite` forces invite-only mode, stopping all front-end signup to free or paid plans with the free plan now being disabled via the portal plans setting.

- check existing `members_signup_access` setting and if it's `'invite'` migrate it to `'all'` where signup should still be possible. The "invite-only" mode should only be active if certain conditions are met:
  - Stripe is not configured ("allow free member signup" off and no Stripe showed "invite-only" in portal)
  - Stripe is configured but no plans are selected in portal (no plans showed "invite-only" in portal)
- when migrating `'invite'` to `'all'`, also remove `'free'` plan from the `portal_plans` setting to avoid previously paid-only sites unexpectedly showing a free plan on signup
This commit is contained in:
Kevin Ansfield 2021-05-04 09:50:29 +01:00 committed by GitHub
parent 0f41d1aa49
commit 65862b437e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -0,0 +1,126 @@
const logging = require('../../../../../shared/logging');
const {createTransactionalMigration} = require('../../utils');
const MembersConfigProvider = require('../../../../services/members/config');
const settingsCache = require('../../../../services/settings/cache');
const config = require('../../../../../shared/config');
// members_allow_free_signup was migrated to members_signup_access in 4.3/08-migrate-members-signup-setting
// using a direct binary migration of true=all and false=invite based on new setting behaviour matching the
// old setting. That was innacurate and the behaviour is changing to a proper invite-only toggle which
// requires a more nuanced migration.
//
// 'all' - no need to change, matches expected behaviour
// 'invite' - change to 'all' based on members usage
// 'none' - no need to change, it's a completely new option that wasn't previously migrated to
//
// For the majority of cases 'invite' should actually be 'all'. The true invite-only case is:
// - stripe not configured
// - portal configured with no plans
//
// When switching from signup access from 'invite' to 'all' we also need to remove the 'free' plan from
// portal settings to avoid paid-only sites suddenly offering free plan signups
module.exports = createTransactionalMigration(
async function up(connection) {
const currentSetting = await connection('settings')
.where('key', 'members_signup_access')
.select('value')
.first();
if (!currentSetting) {
logging.info('Skipping update of members_signup_access setting. Does not exist');
return;
}
if (currentSetting.value !== 'invite') {
logging.info(`Skipping update of members_signup_access setting. Not set to 'invite', keeping current value`);
return;
}
const membersConfig = new MembersConfigProvider({
settingsCache,
config
});
const hasStripe = membersConfig.isStripeConnected();
if (!hasStripe) {
logging.info('Skipping update of members_signup_access setting. Stripe is not configured, staying as invite-only');
return;
}
const portalPlansSetting = await connection('settings')
.where('key', 'portal_plans')
.select('value')
.first();
if (!portalPlansSetting) {
// shouldn't be reachable because members_signup_access wouldn't exist either for a clean install but better to be safe
logging.info('Skipping update of members_signup_access setting. Portal plans setting does not exist');
return;
}
let currentPlans = JSON.parse(portalPlansSetting.value);
if (currentPlans.length === 0) {
logging.info('Skipping update of members_signup_access setting. Portal configured as invite-only, staying as invite-only');
return;
}
logging.info(`Updating members_signup_access setting to 'all'`);
await connection('settings')
.where('key', 'members_signup_access')
.update({value: 'all'});
if (currentPlans.includes('free')) {
currentPlans.splice(currentPlans.indexOf('free'), 1);
logging.info(`Removing free plan from portal plans setting to match "allow free members signup = false" behaviour`);
await connection('settings')
.where('key', 'portal_plans')
.update({value: JSON.stringify(currentPlans)});
}
},
async function down(connection) {
const accessSetting = await connection('settings')
.where('key', 'members_signup_access')
.select('value')
.first();
if (!accessSetting) {
logging.info('Skipping rollback of members_signup_access setting. Does not exist.');
return;
}
if (accessSetting.value !== 'all') {
logging.info(`Skipping rollback of members_signup_access setting. Not set 'all', nothing to roll back`);
return;
}
const portalPlansSetting = await connection('settings')
.where('key', 'portal_plans')
.select('value')
.first();
const membersConfig = new MembersConfigProvider({
settingsCache,
config
});
const hasStripe = membersConfig.isStripeConnected();
if (hasStripe && JSON.parse(portalPlansSetting.value).length === 0) {
logging.info(`Reverting members_signup_access setting to 'invite'`);
await connection('settings')
.where('key', 'members_signup_access')
.update({value: 'invite'});
// free plan removal is not rolled back because we can't be 100% sure of it's previous state
// this won't have any detrimental effect because the free plan was already hidden by the
// "allow free members signup = false" or 4.3.x "members_signup_access = 'invite'" settings
}
logging.info(`Skipping rollback of members_signup_access setting. Value did not match \`up\` requirements`);
}
);