0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Added email verification trigger for admin requests

refs https://github.com/TryGhost/Toolbox/issues/387

- When members are added through the Admin client they have to be a part of instance validation process to prevent service misuse.
This commit is contained in:
Naz 2022-08-25 14:26:26 +08:00
parent 7cae68baaa
commit 7e3b8ff404
3 changed files with 123 additions and 6 deletions

View file

@ -106,6 +106,7 @@ module.exports = {
verificationTrigger = new VerificationTrigger({
apiTriggerThreshold: _.get(config.get('hostSettings'), 'emailVerification.importThreshold'),
adminTriggerThreshold: _.get(config.get('hostSettings'), 'emailVerification.adminThreshold'),
isVerified: () => config.get('hostSettings:emailVerification:verified') === true,
isVerificationRequired: () => settingsCache.get('email_verification_required') === true,
sendVerificationEmail: ({subject, message, amountTriggered}) => {
@ -189,4 +190,5 @@ module.exports = {
stats: membersStats,
export: require('./exporter/query')
};
module.exports.middleware = require('./middleware');

View file

@ -6,6 +6,7 @@ const messages = {
emailVerificationNeeded: `We're hard at work processing your import. To make sure you get great deliverability on a list of that size, we'll need to enable some extra features for your account. A member of our team will be in touch with you by email to review your account make sure everything is configured correctly so you're ready to go.`,
emailVerificationEmailSubject: `Email needs verification`,
emailVerificationEmailMessageImport: `Email verification needed for site: {siteUrl}, has imported: {amountTriggered} members in the last 30 days.`,
emailVerificationEmailMessageAdmin: `Email verification needed for site: {siteUrl} has added: {amountTriggered} members through the Admin client in the last 30 days.`,
emailVerificationEmailMessageAPI: `Email verification needed for site: {siteUrl} has added: {amountTriggered} members through the API in the last 30 days.`
};
@ -13,7 +14,8 @@ class VerificationTrigger {
/**
*
* @param {object} deps
* @param {number} deps.apiTriggerThreshold Threshold for triggering verification as defined in config
* @param {number} deps.apiTriggerThreshold Threshold for triggering API&Import sourced verifications
* @param {number} deps.adminTriggerThreshold Threshold for triggering Admin sourced verifications
* @param {() => boolean} deps.isVerified Check Ghost config to see if we are already verified
* @param {() => boolean} deps.isVerificationRequired Check Ghost settings to see whether verification has been requested
* @param {(content: {subject: string, message: string, amountTriggered: number}) => void} deps.sendVerificationEmail Sends an email to the escalation address to confirm that customer needs to be verified
@ -23,6 +25,7 @@ class VerificationTrigger {
*/
constructor({
apiTriggerThreshold,
adminTriggerThreshold,
isVerified,
isVerificationRequired,
sendVerificationEmail,
@ -31,6 +34,7 @@ class VerificationTrigger {
eventRepository
}) {
this._apiTriggerThreshold = apiTriggerThreshold;
this._adminTriggerThreshold = adminTriggerThreshold;
this._isVerified = isVerified;
this._isVerificationRequired = isVerificationRequired;
this._sendVerificationEmail = sendVerificationEmail;
@ -42,11 +46,21 @@ class VerificationTrigger {
DomainEvents.subscribe(MemberSubscribeEvent, this._handleMemberSubscribeEvent);
}
/**
*
* @param {MemberSubscribeEvent} event
*/
async _handleMemberSubscribeEvent(event) {
const source = event.data?.source;
const sourceThreshold = this._apiTriggerThreshold;
let sourceThreshold;
if (source === 'api' && isFinite(sourceThreshold)) {
if (source === 'api') {
sourceThreshold = this._apiTriggerThreshold;
} else if (source === 'admin') {
sourceThreshold = this._adminTriggerThreshold;
}
if (['api', 'admin'].includes(source) && isFinite(sourceThreshold)) {
const createdAt = new Date();
createdAt.setDate(createdAt.getDate() - 30);
const events = await this._eventRepository.getNewsletterSubscriptionEvents({}, {
@ -127,10 +141,17 @@ class VerificationTrigger {
value: true
}], {context: {internal: true}});
// Setting import as a default message
let verificationMessage = messages.emailVerificationEmailMessageImport;
if (source === 'api') {
verificationMessage = messages.emailVerificationEmailMessageAPI;
} else if (source === 'admin') {
verificationMessage = messages.emailVerificationEmailMessageAdmin;
}
this._sendVerificationEmail({
message: source === 'api'
? messages.emailVerificationEmailMessageAPI
: messages.emailVerificationEmailMessageImport,
message: verificationMessage,
subject: messages.emailVerificationEmailSubject,
amountTriggered: amount
});

View file

@ -233,6 +233,100 @@ describe('Email verification flow', function () {
amountTriggered: 10
});
});
it('Triggers when a number of members are added from Admin', async function () {
const emailStub = sinon.stub().resolves(null);
const settingsStub = sinon.stub().resolves(null);
const eventStub = sinon.stub().resolves({
meta: {
pagination: {
total: 10
}
}
});
const trigger = new VerificationTrigger({
adminTriggerThreshold: 2,
Settings: {
edit: settingsStub
},
membersStats: {
getTotalMembers: () => 15
},
isVerified: () => false,
isVerificationRequired: () => false,
sendVerificationEmail: emailStub,
eventRepository: {
getNewsletterSubscriptionEvents: eventStub
}
});
await trigger._handleMemberSubscribeEvent({
data: {
source: 'admin'
}
});
eventStub.callCount.should.eql(1);
eventStub.lastCall.lastArg.should.have.property('data.source');
eventStub.lastCall.lastArg.should.have.property('data.created_at');
eventStub.lastCall.lastArg['data.source'].should.eql(`data.source:'admin'`);
eventStub.lastCall.lastArg['data.created_at'].should.startWith(`data.created_at:>'`);
emailStub.callCount.should.eql(1);
emailStub.lastCall.firstArg.should.eql({
subject: 'Email needs verification',
message: 'Email verification needed for site: {siteUrl} has added: {importedNumber} members through the Admin client in the last 30 days.',
amountTriggered: 10
});
});
it('Triggers when a number of members are added from API', async function () {
const emailStub = sinon.stub().resolves(null);
const settingsStub = sinon.stub().resolves(null);
const eventStub = sinon.stub().resolves({
meta: {
pagination: {
total: 10
}
}
});
const trigger = new VerificationTrigger({
adminTriggerThreshold: 2,
apiTriggerThreshold: 2,
Settings: {
edit: settingsStub
},
membersStats: {
getTotalMembers: () => 15
},
isVerified: () => false,
isVerificationRequired: () => false,
sendVerificationEmail: emailStub,
eventRepository: {
getNewsletterSubscriptionEvents: eventStub
}
});
await trigger._handleMemberSubscribeEvent({
data: {
source: 'api'
}
});
eventStub.callCount.should.eql(1);
eventStub.lastCall.lastArg.should.have.property('data.source');
eventStub.lastCall.lastArg.should.have.property('data.created_at');
eventStub.lastCall.lastArg['data.source'].should.eql(`data.source:'admin'`);
eventStub.lastCall.lastArg['data.created_at'].should.startWith(`data.created_at:>'`);
emailStub.callCount.should.eql(1);
emailStub.lastCall.firstArg.should.eql({
subject: 'Email needs verification',
message: 'Email verification needed for site: {siteUrl} has added: {importedNumber} members through the API in the last 30 days.',
amountTriggered: 10
});
});
it('Does not fetch events and trigger when threshold is Infinity', async function () {