mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-02 08:13:34 +03:00
01d0b2b304
ref https://linear.app/tryghost/issue/KTLO-1/members-spam-signups - Some customers are seeing many spammy signups ("hundreds a day") — our hypothesis is that bots and/or email link checkers are able to signup by simply following the link in the email without even loading the page in a browser. - Currently new members signup by clicking a magic link in an email, which is a simple GET request. When the user (or a bot) clicks that link, Ghost creates the member and signs them in for the first time. - This change, behind an alpha flag, requires a new member to click the link in the email, which takes them to a new frontend route `/confirm_signup/`, then submit a form on the page which sends a POST request to the server. If JavaScript is enabled, the form will be submitted automatically so the only change to the user is an extra flash/redirect before being signed in and redirected to the homepage. - This change is behind the alpha flag `membersSpamPrevention` so we can test it out on a few customer's sites and see if it helps reduce the spam signups. With the flag off, the signup flow remains the same as before.
169 lines
5.4 KiB
JavaScript
169 lines
5.4 KiB
JavaScript
import $ from 'jquery';
|
|
import Ember from 'ember';
|
|
import EmberError from '@ember/error';
|
|
import Service, {inject as service} from '@ember/service';
|
|
import classic from 'ember-classic-decorator';
|
|
import {computed, set} from '@ember/object';
|
|
import {inject} from 'ghost-admin/decorators/inject';
|
|
|
|
export function feature(name, options = {}) {
|
|
let {user, onChange} = options;
|
|
let watchedProps = user ? [`accessibility.${name}`] : [`config.${name}`, `labs.${name}`];
|
|
|
|
return computed.apply(Ember, watchedProps.concat({
|
|
get() {
|
|
let enabled = false;
|
|
|
|
if (user) {
|
|
enabled = this.get(`accessibility.${name}`);
|
|
} else if (typeof this.get(`config.${name}`) === 'boolean') {
|
|
enabled = this.get(`config.${name}`);
|
|
} else {
|
|
enabled = this.get(`labs.${name}`) || false;
|
|
}
|
|
|
|
return enabled;
|
|
},
|
|
set(key, value) {
|
|
this.update(key, value, options);
|
|
|
|
if (onChange) {
|
|
// value must be passed here because the value isn't set until
|
|
// the setter function returns
|
|
this.get(onChange).bind(this)(value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}));
|
|
}
|
|
|
|
@classic
|
|
export default class FeatureService extends Service {
|
|
@service lazyLoader;
|
|
@service notifications;
|
|
@service session;
|
|
@service settings;
|
|
@service store;
|
|
|
|
@inject config;
|
|
|
|
// features
|
|
@feature('emailAnalytics') emailAnalytics;
|
|
|
|
// user-specific flags
|
|
@feature('nightShift', {user: true, onChange: '_setAdminTheme'})
|
|
nightShift;
|
|
|
|
// user-specific referral invitation
|
|
@feature('referralInviteDismissed', {user: true}) referralInviteDismissed;
|
|
|
|
// labs flags
|
|
@feature('urlCache') urlCache;
|
|
@feature('lexicalMultiplayer') lexicalMultiplayer;
|
|
@feature('audienceFeedback') audienceFeedback;
|
|
@feature('webmentions') webmentions;
|
|
@feature('websockets') websockets;
|
|
@feature('stripeAutomaticTax') stripeAutomaticTax;
|
|
@feature('emailCustomization') emailCustomization;
|
|
@feature('i18n') i18n;
|
|
@feature('announcementBar') announcementBar;
|
|
@feature('signupCard') signupCard;
|
|
@feature('signupForm') signupForm;
|
|
@feature('collections') collections;
|
|
@feature('mailEvents') mailEvents;
|
|
@feature('collectionsCard') collectionsCard;
|
|
@feature('importMemberTier') importMemberTier;
|
|
@feature('tipsAndDonations') tipsAndDonations;
|
|
@feature('recommendations') recommendations;
|
|
@feature('lexicalIndicators') lexicalIndicators;
|
|
@feature('filterEmailDisabled') filterEmailDisabled;
|
|
@feature('adminXDemo') adminXDemo;
|
|
@feature('portalImprovements') portalImprovements;
|
|
@feature('onboardingChecklist') onboardingChecklist;
|
|
@feature('membersSpamPrevention') membersSpamPrevention;
|
|
|
|
_user = null;
|
|
|
|
@computed('settings.labs')
|
|
get labs() {
|
|
let labs = this.settings.labs;
|
|
|
|
try {
|
|
return JSON.parse(labs) || {};
|
|
} catch (e) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
@computed('_user.accessibility')
|
|
get accessibility() {
|
|
let accessibility = this.get('_user.accessibility');
|
|
|
|
try {
|
|
return JSON.parse(accessibility) || {};
|
|
} catch (e) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
fetch() {
|
|
return this.settings.fetch().then(() => {
|
|
this.set('_user', this.session.user);
|
|
return this._setAdminTheme().then(() => true);
|
|
});
|
|
}
|
|
|
|
update(key, value, options = {}) {
|
|
let serviceProperty = options.user ? 'accessibility' : 'labs';
|
|
let model = this.get(options.user ? '_user' : 'settings');
|
|
let featureObject = this.get(serviceProperty);
|
|
|
|
// set the new key value for either the labs property or the accessibility property
|
|
set(featureObject, key, value);
|
|
|
|
if (options.requires && value === true) {
|
|
options.requires.forEach((flag) => {
|
|
set(featureObject, flag, true);
|
|
});
|
|
}
|
|
|
|
// update the 'labs' or 'accessibility' key of the model
|
|
model.set(serviceProperty, JSON.stringify(featureObject));
|
|
|
|
return model.save().then(() => {
|
|
// return the labs key value that we get from the server
|
|
this.notifyPropertyChange(serviceProperty);
|
|
return this.get(`${serviceProperty}.${key}`);
|
|
}).catch((error) => {
|
|
model.rollbackAttributes();
|
|
this.notifyPropertyChange(serviceProperty);
|
|
|
|
// we'll always have an errors object unless we hit a
|
|
// validation error
|
|
if (!error) {
|
|
throw new EmberError(`Validation of the feature service ${options.user ? 'user' : 'settings'} model failed when updating ${serviceProperty}.`);
|
|
}
|
|
|
|
this.notifications.showAPIError(error);
|
|
|
|
return this.get(`${serviceProperty}.${key}`);
|
|
});
|
|
}
|
|
|
|
_setAdminTheme(enabled) {
|
|
let nightShift = enabled;
|
|
|
|
if (typeof nightShift === 'undefined') {
|
|
nightShift = enabled || this.nightShift;
|
|
}
|
|
|
|
return this.lazyLoader.loadStyle('dark', 'assets/ghost-dark.css', true).then(() => {
|
|
$('link[title=dark]').prop('disabled', !nightShift);
|
|
}).catch(() => {
|
|
//TODO: Also disable toggle from settings and Labs hover
|
|
$('link[title=dark]').prop('disabled', true);
|
|
});
|
|
}
|
|
}
|