mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 19:33:02 +03:00
Trigger email verification workflow on API usage
refs: TryGhost/Toolbox#166 The new VerificationTrigger listens to events form the members repository, and will cause the verification workflow to be triggered if the number of events is greater than the configured threshold in a rolling 30 day window. The importer also no longer depends on the import threshold, so the threshold testing is now done in the processImport method in Ghost - seems sensible since we already had this wrapper and the logic is now tiny, since it's just relying on @tryghost/verification-trigger to handle the real stuff.
This commit is contained in:
parent
5713dfe7f7
commit
08829a6f0c
@ -1,3 +1,4 @@
|
||||
const _ = require('lodash');
|
||||
const errors = require('@tryghost/errors');
|
||||
const tpl = require('@tryghost/tpl');
|
||||
const MembersSSR = require('@tryghost/members-ssr');
|
||||
@ -12,9 +13,9 @@ const labsService = require('../../../shared/labs');
|
||||
const settingsCache = require('../../../shared/settings-cache');
|
||||
const config = require('../../../shared/config');
|
||||
const models = require('../../models');
|
||||
const _ = require('lodash');
|
||||
const {GhostMailer} = require('../mail');
|
||||
const jobsService = require('../jobs');
|
||||
const VerificationTrigger = require('@tryghost/verification-trigger');
|
||||
|
||||
const messages = {
|
||||
noLiveKeysInDevelopment: 'Cannot use live stripe keys in development. Please restart in production mode.',
|
||||
@ -32,23 +33,15 @@ const membersConfig = new MembersConfigProvider({
|
||||
urlUtils
|
||||
});
|
||||
|
||||
const membersStats = new MembersStats({
|
||||
db: db,
|
||||
settingsCache: settingsCache,
|
||||
isSQLite: config.get('database:client') === 'sqlite3'
|
||||
});
|
||||
|
||||
let membersApi;
|
||||
let membersSettings;
|
||||
|
||||
/**
|
||||
* @description Calculates threshold based on following formula
|
||||
* Threshold = max{[current number of members], [volume threshold]}
|
||||
*
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
const fetchImportThreshold = async () => {
|
||||
const membersTotal = await module.exports.stats.getTotalMembers();
|
||||
const configThreshold = _.get(config.get('hostSettings'), 'emailVerification.importThreshold');
|
||||
const volumeThreshold = (configThreshold === undefined) ? Infinity : configThreshold;
|
||||
const threshold = Math.max(membersTotal, volumeThreshold);
|
||||
|
||||
return threshold;
|
||||
};
|
||||
let verificationTrigger;
|
||||
|
||||
const membersImporter = new MembersCSVImporter({
|
||||
storagePath: config.getContentPath('data'),
|
||||
@ -58,53 +51,20 @@ const membersImporter = new MembersCSVImporter({
|
||||
isSet: labsService.isSet.bind(labsService),
|
||||
addJob: jobsService.addJob.bind(jobsService),
|
||||
knex: db.knex,
|
||||
urlFor: urlUtils.urlFor.bind(urlUtils),
|
||||
fetchThreshold: fetchImportThreshold
|
||||
urlFor: urlUtils.urlFor.bind(urlUtils)
|
||||
});
|
||||
|
||||
const startEmailVerification = async (importedNumber) => {
|
||||
const isVerifiedEmail = config.get('hostSettings:emailVerification:verified') === true;
|
||||
|
||||
if ((!isVerifiedEmail)) {
|
||||
// Only trigger flag change and escalation email the first time
|
||||
if (settingsCache.get('email_verification_required') !== true) {
|
||||
await models.Settings.edit([{
|
||||
key: 'email_verification_required',
|
||||
value: true
|
||||
}], {context: {internal: true}});
|
||||
|
||||
const escalationAddress = config.get('hostSettings:emailVerification:escalationAddress');
|
||||
const fromAddress = config.get('user_email');
|
||||
|
||||
if (escalationAddress) {
|
||||
ghostMailer.send({
|
||||
subject: 'Email needs verification',
|
||||
html: tpl(messages.emailVerificationEmailMessage, {
|
||||
importedNumber,
|
||||
siteUrl: urlUtils.getSiteUrl()
|
||||
}),
|
||||
forceTextContent: true,
|
||||
from: fromAddress,
|
||||
to: escalationAddress
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
throw new errors.ValidationError({
|
||||
message: tpl(messages.emailVerificationNeeded)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const processImport = async (options) => {
|
||||
const result = await membersImporter.process(options);
|
||||
const freezeTriggered = result.meta.freeze;
|
||||
const importSize = result.meta.originalImportSize;
|
||||
delete result.meta.freeze;
|
||||
delete result.meta.originalImportSize;
|
||||
|
||||
if (freezeTriggered) {
|
||||
await startEmailVerification(importSize);
|
||||
const importThreshold = await verificationTrigger.getImportThreshold();
|
||||
if (importThreshold > importSize) {
|
||||
await verificationTrigger.startVerificationProcess({
|
||||
amountImported: importSize,
|
||||
throwOnTrigger: true
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -139,6 +99,32 @@ module.exports = {
|
||||
});
|
||||
}
|
||||
|
||||
verificationTrigger = new VerificationTrigger({
|
||||
configThreshold: _.get(config.get('hostSettings'), 'emailVerification.importThreshold'),
|
||||
isVerified: () => config.get('hostSettings:emailVerification:verified') === true,
|
||||
isVerificationRequired: () => settingsCache.get('email_verification_required') === true,
|
||||
sendVerificationEmail: ({subject, message, amountImported}) => {
|
||||
const escalationAddress = config.get('hostSettings:emailVerification:escalationAddress');
|
||||
const fromAddress = config.get('user_email');
|
||||
|
||||
if (escalationAddress) {
|
||||
this._ghostMailer.send({
|
||||
subject,
|
||||
html: tpl(message, {
|
||||
amountImported,
|
||||
siteUrl: this._urlUtils.getSiteUrl()
|
||||
}),
|
||||
forceTextContent: true,
|
||||
from: fromAddress,
|
||||
to: escalationAddress
|
||||
});
|
||||
}
|
||||
},
|
||||
membersStats,
|
||||
Settings: models.Settings,
|
||||
eventRepository: membersApi.events
|
||||
});
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const collection = await models.SingleUseToken.fetchAll();
|
||||
@ -181,11 +167,7 @@ module.exports = {
|
||||
|
||||
processImport: processImport,
|
||||
|
||||
stats: new MembersStats({
|
||||
db: db,
|
||||
settingsCache: settingsCache,
|
||||
isSQLite: config.get('database:client') === 'sqlite3'
|
||||
})
|
||||
stats: membersStats
|
||||
|
||||
};
|
||||
module.exports.middleware = require('./middleware');
|
||||
|
@ -80,8 +80,8 @@
|
||||
"@tryghost/limit-service": "1.0.9",
|
||||
"@tryghost/logging": "2.0.2",
|
||||
"@tryghost/magic-link": "1.0.17",
|
||||
"@tryghost/members-api": "4.8.1",
|
||||
"@tryghost/members-importer": "0.4.1",
|
||||
"@tryghost/members-api": "4.8.3",
|
||||
"@tryghost/members-importer": "0.5.0",
|
||||
"@tryghost/members-offers": "0.10.6",
|
||||
"@tryghost/members-ssr": "1.0.19",
|
||||
"@tryghost/members-stripe-service": "0.6.5",
|
||||
@ -103,6 +103,7 @@
|
||||
"@tryghost/update-check-service": "0.3.0",
|
||||
"@tryghost/url-utils": "2.0.5",
|
||||
"@tryghost/validator": "0.1.11",
|
||||
"@tryghost/verification-trigger": "0.1.0",
|
||||
"@tryghost/version": "0.1.9",
|
||||
"@tryghost/vhost-middleware": "1.0.20",
|
||||
"@tryghost/zip": "1.1.19",
|
||||
|
72
yarn.lock
72
yarn.lock
@ -1507,34 +1507,34 @@
|
||||
jsonwebtoken "^8.5.1"
|
||||
lodash "^4.17.15"
|
||||
|
||||
"@tryghost/member-analytics-service@^0.1.7":
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/member-analytics-service/-/member-analytics-service-0.1.7.tgz#eab4d17ec747007f007d0a2a8e2e157ac681cfbd"
|
||||
integrity sha512-WMLxnvBVJiiV1Alef3nvBb6Jk8RiPJxoLEwUrbTT9mQydtQwIYVvh8wsmw+NgMZUKe+zqvwTLyBzqbMHkTWPjw==
|
||||
"@tryghost/member-analytics-service@^0.1.8":
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/member-analytics-service/-/member-analytics-service-0.1.8.tgz#8f43e1fd2ee661a2ab3b7469da7213e2a8886225"
|
||||
integrity sha512-lDXIp0rrPC30eemita5h1C9wd4M5fzJft0v/346xyBCaIK2As+MUN5IuZG3RIqXG9wLevnFzZ3mNUXBLuGxEEA==
|
||||
dependencies:
|
||||
"@tryghost/domain-events" "^0.1.6"
|
||||
"@tryghost/errors" "^0.2.14"
|
||||
"@tryghost/member-events" "^0.3.3"
|
||||
"@tryghost/member-events" "^0.3.4"
|
||||
"@tryghost/tpl" "^0.1.4"
|
||||
bson-objectid "^2.0.1"
|
||||
|
||||
"@tryghost/member-events@^0.3.3":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/member-events/-/member-events-0.3.3.tgz#96da2ffe762a30571e4bad4c75488f20681e8730"
|
||||
integrity sha512-Mi94RMnGJFL0hzhPWpYVAfQBAG4eIBcaJR4Dghy2s6wfUGCaV3iZftyNcHvmCezN5tfrLAmQt6b/fI+NYrK/9Q==
|
||||
"@tryghost/member-events@^0.3.4":
|
||||
version "0.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/member-events/-/member-events-0.3.4.tgz#8aadea01d67660e6bce49172215c25857ce5ef32"
|
||||
integrity sha512-hPmk3RA/Vs6exfkz8jdA52Lx2KFN8XsZHR694MUEE7tU6FHTbjQp0VGn1PZXTU/UmVJV6xDyUbyXeVsaGvu09w==
|
||||
|
||||
"@tryghost/members-analytics-ingress@^0.1.8":
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-analytics-ingress/-/members-analytics-ingress-0.1.8.tgz#648b3fa6dee947d0c06892f19f6ddaa5e896a4fc"
|
||||
integrity sha512-RTd+W5BcGFqYI1/ArzFnZwFDs62FwLnmtvJO4dOfP5i1dY0vtwqbqDZNciHXa2fW9IAP4PLiCiyN4MrppqI6xw==
|
||||
"@tryghost/members-analytics-ingress@^0.1.9":
|
||||
version "0.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-analytics-ingress/-/members-analytics-ingress-0.1.9.tgz#769d6d2f7b4b4e55ae12d8ef170974f5a25ffafe"
|
||||
integrity sha512-J0mzLkXycEuunugc+1KX9vPMvesCeJ7nLXLlT/bf7BRBov4hDjthBhAAs7XIciyGZXx0hleuh0EAVYZUnwlHhg==
|
||||
dependencies:
|
||||
"@tryghost/domain-events" "^0.1.6"
|
||||
"@tryghost/member-events" "^0.3.3"
|
||||
"@tryghost/member-events" "^0.3.4"
|
||||
|
||||
"@tryghost/members-api@4.8.1":
|
||||
version "4.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-4.8.1.tgz#f3f6e4b5373407bc9c73275edf7aeb3007d4b446"
|
||||
integrity sha512-tAmdwrtFwB8DbezSySIL1GhumVpfCi4b2FIfQo4eTtfyEGiUdoD5JRR+ToNbxor1/h+HN0gETnteflmQ97mOEw==
|
||||
"@tryghost/members-api@4.8.3":
|
||||
version "4.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-4.8.3.tgz#b85c095e2ac4477fe554c44607dec26681592aa9"
|
||||
integrity sha512-7YOF65FWTICfWDuCwlRJmjVhzX4rRM4gjig9Hx9nxuRosOusv45B88RpbWqB4b7i/OfdMUvvVSemlGXjx8fPOA==
|
||||
dependencies:
|
||||
"@nexes/nql" "^0.6.0"
|
||||
"@tryghost/debug" "^0.1.2"
|
||||
@ -1542,11 +1542,11 @@
|
||||
"@tryghost/errors" "^1.1.1"
|
||||
"@tryghost/logging" "^2.0.0"
|
||||
"@tryghost/magic-link" "^1.0.17"
|
||||
"@tryghost/member-analytics-service" "^0.1.7"
|
||||
"@tryghost/member-events" "^0.3.3"
|
||||
"@tryghost/members-analytics-ingress" "^0.1.8"
|
||||
"@tryghost/member-analytics-service" "^0.1.8"
|
||||
"@tryghost/member-events" "^0.3.4"
|
||||
"@tryghost/members-analytics-ingress" "^0.1.9"
|
||||
"@tryghost/members-payments" "^0.1.8"
|
||||
"@tryghost/members-stripe-service" "^0.6.4"
|
||||
"@tryghost/members-stripe-service" "^0.6.5"
|
||||
"@tryghost/tpl" "^0.1.2"
|
||||
"@types/jsonwebtoken" "^8.5.1"
|
||||
bluebird "^3.5.4"
|
||||
@ -1570,10 +1570,10 @@
|
||||
papaparse "5.3.1"
|
||||
pump "^3.0.0"
|
||||
|
||||
"@tryghost/members-importer@0.4.1":
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-importer/-/members-importer-0.4.1.tgz#e5c64ac94f03b922c9bb4067eeb21945373637c8"
|
||||
integrity sha512-8vpei+dHRRosS1UpkyPDI/3100kFMdEidxH8hDFEZbyQjphGmQdeKpi04yJUQFGeANKBNljiifOuZ9vJdJcd9g==
|
||||
"@tryghost/members-importer@0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-importer/-/members-importer-0.5.0.tgz#262b542bd597853b091b819adcd8a13c7bd2dc0a"
|
||||
integrity sha512-WNLexqKyqJ6yyBSoZWhtacJwWEoG6PlPxaCwjAk2pifPfWuT+T1uVk+tm7ETmQzBLxf0HKpcdpN4j4w9J0A0Ew==
|
||||
dependencies:
|
||||
"@tryghost/errors" "^0.2.13"
|
||||
"@tryghost/members-csv" "^1.2.3"
|
||||
@ -1609,7 +1609,7 @@
|
||||
jsonwebtoken "^8.5.1"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@tryghost/members-stripe-service@0.6.5":
|
||||
"@tryghost/members-stripe-service@0.6.5", "@tryghost/members-stripe-service@^0.6.5":
|
||||
version "0.6.5"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-stripe-service/-/members-stripe-service-0.6.5.tgz#68e6bc4d3f5e4ee61468f5fc43eb77650543bf63"
|
||||
integrity sha512-cTFcacy/Rst+ao4oRIszCMUw8oI8hKo1u3xxbjXjchDQUfVAUxxfnDoHcULH1d4mXc+LdftD2iKYNav/BATC5Q==
|
||||
@ -1619,16 +1619,6 @@
|
||||
leaky-bucket "^2.2.0"
|
||||
stripe "^8.174.0"
|
||||
|
||||
"@tryghost/members-stripe-service@^0.6.4":
|
||||
version "0.6.4"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-stripe-service/-/members-stripe-service-0.6.4.tgz#8946e1eb8becaccb0c1dc3a7d26a6931802d2023"
|
||||
integrity sha512-twxt5r/yqSGFO48cV6RtMyTngd+HvBBr97/Psbjy5TnyYZo6D1AXetocHscOMqbWkpu3CSTBZgJx0VqywKLYKQ==
|
||||
dependencies:
|
||||
"@tryghost/debug" "^0.1.4"
|
||||
"@tryghost/errors" "1.2.0"
|
||||
leaky-bucket "^2.2.0"
|
||||
stripe "^8.174.0"
|
||||
|
||||
"@tryghost/metrics@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/metrics/-/metrics-1.0.3.tgz#091ff6d0cc6c0da05bab6ba8231312cae3d395de"
|
||||
@ -1878,6 +1868,14 @@
|
||||
moment-timezone "0.5.23"
|
||||
validator "7.2.0"
|
||||
|
||||
"@tryghost/verification-trigger@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/verification-trigger/-/verification-trigger-0.1.0.tgz#7d1baf19c26e09c51f8921ad1e7fce20d562b286"
|
||||
integrity sha512-k+nFYrQqn9gQbI9N7aaIKo5+/ot7l6IzsgJ64BqN+X4/25g01x2zAHbKk4fwTcMQ9sOFstUqBPAr4thZgHIHUg==
|
||||
dependencies:
|
||||
"@tryghost/domain-events" "^0.1.6"
|
||||
"@tryghost/member-events" "^0.3.4"
|
||||
|
||||
"@tryghost/version@0.1.9", "@tryghost/version@^0.1.9":
|
||||
version "0.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/version/-/version-0.1.9.tgz#47474fc04675028d5e178c73d135c138165802a1"
|
||||
|
Loading…
Reference in New Issue
Block a user