mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 20:03:12 +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 errors = require('@tryghost/errors');
|
||||||
const tpl = require('@tryghost/tpl');
|
const tpl = require('@tryghost/tpl');
|
||||||
const MembersSSR = require('@tryghost/members-ssr');
|
const MembersSSR = require('@tryghost/members-ssr');
|
||||||
@ -12,9 +13,9 @@ const labsService = require('../../../shared/labs');
|
|||||||
const settingsCache = require('../../../shared/settings-cache');
|
const settingsCache = require('../../../shared/settings-cache');
|
||||||
const config = require('../../../shared/config');
|
const config = require('../../../shared/config');
|
||||||
const models = require('../../models');
|
const models = require('../../models');
|
||||||
const _ = require('lodash');
|
|
||||||
const {GhostMailer} = require('../mail');
|
const {GhostMailer} = require('../mail');
|
||||||
const jobsService = require('../jobs');
|
const jobsService = require('../jobs');
|
||||||
|
const VerificationTrigger = require('@tryghost/verification-trigger');
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
noLiveKeysInDevelopment: 'Cannot use live stripe keys in development. Please restart in production mode.',
|
noLiveKeysInDevelopment: 'Cannot use live stripe keys in development. Please restart in production mode.',
|
||||||
@ -32,23 +33,15 @@ const membersConfig = new MembersConfigProvider({
|
|||||||
urlUtils
|
urlUtils
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const membersStats = new MembersStats({
|
||||||
|
db: db,
|
||||||
|
settingsCache: settingsCache,
|
||||||
|
isSQLite: config.get('database:client') === 'sqlite3'
|
||||||
|
});
|
||||||
|
|
||||||
let membersApi;
|
let membersApi;
|
||||||
let membersSettings;
|
let membersSettings;
|
||||||
|
let verificationTrigger;
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
};
|
|
||||||
|
|
||||||
const membersImporter = new MembersCSVImporter({
|
const membersImporter = new MembersCSVImporter({
|
||||||
storagePath: config.getContentPath('data'),
|
storagePath: config.getContentPath('data'),
|
||||||
@ -58,53 +51,20 @@ const membersImporter = new MembersCSVImporter({
|
|||||||
isSet: labsService.isSet.bind(labsService),
|
isSet: labsService.isSet.bind(labsService),
|
||||||
addJob: jobsService.addJob.bind(jobsService),
|
addJob: jobsService.addJob.bind(jobsService),
|
||||||
knex: db.knex,
|
knex: db.knex,
|
||||||
urlFor: urlUtils.urlFor.bind(urlUtils),
|
urlFor: urlUtils.urlFor.bind(urlUtils)
|
||||||
fetchThreshold: fetchImportThreshold
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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 processImport = async (options) => {
|
||||||
const result = await membersImporter.process(options);
|
const result = await membersImporter.process(options);
|
||||||
const freezeTriggered = result.meta.freeze;
|
|
||||||
const importSize = result.meta.originalImportSize;
|
const importSize = result.meta.originalImportSize;
|
||||||
delete result.meta.freeze;
|
|
||||||
delete result.meta.originalImportSize;
|
delete result.meta.originalImportSize;
|
||||||
|
|
||||||
if (freezeTriggered) {
|
const importThreshold = await verificationTrigger.getImportThreshold();
|
||||||
await startEmailVerification(importSize);
|
if (importThreshold > importSize) {
|
||||||
|
await verificationTrigger.startVerificationProcess({
|
||||||
|
amountImported: importSize,
|
||||||
|
throwOnTrigger: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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 () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const collection = await models.SingleUseToken.fetchAll();
|
const collection = await models.SingleUseToken.fetchAll();
|
||||||
@ -181,11 +167,7 @@ module.exports = {
|
|||||||
|
|
||||||
processImport: processImport,
|
processImport: processImport,
|
||||||
|
|
||||||
stats: new MembersStats({
|
stats: membersStats
|
||||||
db: db,
|
|
||||||
settingsCache: settingsCache,
|
|
||||||
isSQLite: config.get('database:client') === 'sqlite3'
|
|
||||||
})
|
|
||||||
|
|
||||||
};
|
};
|
||||||
module.exports.middleware = require('./middleware');
|
module.exports.middleware = require('./middleware');
|
||||||
|
@ -80,8 +80,8 @@
|
|||||||
"@tryghost/limit-service": "1.0.9",
|
"@tryghost/limit-service": "1.0.9",
|
||||||
"@tryghost/logging": "2.0.2",
|
"@tryghost/logging": "2.0.2",
|
||||||
"@tryghost/magic-link": "1.0.17",
|
"@tryghost/magic-link": "1.0.17",
|
||||||
"@tryghost/members-api": "4.8.1",
|
"@tryghost/members-api": "4.8.3",
|
||||||
"@tryghost/members-importer": "0.4.1",
|
"@tryghost/members-importer": "0.5.0",
|
||||||
"@tryghost/members-offers": "0.10.6",
|
"@tryghost/members-offers": "0.10.6",
|
||||||
"@tryghost/members-ssr": "1.0.19",
|
"@tryghost/members-ssr": "1.0.19",
|
||||||
"@tryghost/members-stripe-service": "0.6.5",
|
"@tryghost/members-stripe-service": "0.6.5",
|
||||||
@ -103,6 +103,7 @@
|
|||||||
"@tryghost/update-check-service": "0.3.0",
|
"@tryghost/update-check-service": "0.3.0",
|
||||||
"@tryghost/url-utils": "2.0.5",
|
"@tryghost/url-utils": "2.0.5",
|
||||||
"@tryghost/validator": "0.1.11",
|
"@tryghost/validator": "0.1.11",
|
||||||
|
"@tryghost/verification-trigger": "0.1.0",
|
||||||
"@tryghost/version": "0.1.9",
|
"@tryghost/version": "0.1.9",
|
||||||
"@tryghost/vhost-middleware": "1.0.20",
|
"@tryghost/vhost-middleware": "1.0.20",
|
||||||
"@tryghost/zip": "1.1.19",
|
"@tryghost/zip": "1.1.19",
|
||||||
|
72
yarn.lock
72
yarn.lock
@ -1507,34 +1507,34 @@
|
|||||||
jsonwebtoken "^8.5.1"
|
jsonwebtoken "^8.5.1"
|
||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
|
|
||||||
"@tryghost/member-analytics-service@^0.1.7":
|
"@tryghost/member-analytics-service@^0.1.8":
|
||||||
version "0.1.7"
|
version "0.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/member-analytics-service/-/member-analytics-service-0.1.7.tgz#eab4d17ec747007f007d0a2a8e2e157ac681cfbd"
|
resolved "https://registry.yarnpkg.com/@tryghost/member-analytics-service/-/member-analytics-service-0.1.8.tgz#8f43e1fd2ee661a2ab3b7469da7213e2a8886225"
|
||||||
integrity sha512-WMLxnvBVJiiV1Alef3nvBb6Jk8RiPJxoLEwUrbTT9mQydtQwIYVvh8wsmw+NgMZUKe+zqvwTLyBzqbMHkTWPjw==
|
integrity sha512-lDXIp0rrPC30eemita5h1C9wd4M5fzJft0v/346xyBCaIK2As+MUN5IuZG3RIqXG9wLevnFzZ3mNUXBLuGxEEA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@tryghost/domain-events" "^0.1.6"
|
"@tryghost/domain-events" "^0.1.6"
|
||||||
"@tryghost/errors" "^0.2.14"
|
"@tryghost/errors" "^0.2.14"
|
||||||
"@tryghost/member-events" "^0.3.3"
|
"@tryghost/member-events" "^0.3.4"
|
||||||
"@tryghost/tpl" "^0.1.4"
|
"@tryghost/tpl" "^0.1.4"
|
||||||
bson-objectid "^2.0.1"
|
bson-objectid "^2.0.1"
|
||||||
|
|
||||||
"@tryghost/member-events@^0.3.3":
|
"@tryghost/member-events@^0.3.4":
|
||||||
version "0.3.3"
|
version "0.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/member-events/-/member-events-0.3.3.tgz#96da2ffe762a30571e4bad4c75488f20681e8730"
|
resolved "https://registry.yarnpkg.com/@tryghost/member-events/-/member-events-0.3.4.tgz#8aadea01d67660e6bce49172215c25857ce5ef32"
|
||||||
integrity sha512-Mi94RMnGJFL0hzhPWpYVAfQBAG4eIBcaJR4Dghy2s6wfUGCaV3iZftyNcHvmCezN5tfrLAmQt6b/fI+NYrK/9Q==
|
integrity sha512-hPmk3RA/Vs6exfkz8jdA52Lx2KFN8XsZHR694MUEE7tU6FHTbjQp0VGn1PZXTU/UmVJV6xDyUbyXeVsaGvu09w==
|
||||||
|
|
||||||
"@tryghost/members-analytics-ingress@^0.1.8":
|
"@tryghost/members-analytics-ingress@^0.1.9":
|
||||||
version "0.1.8"
|
version "0.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/members-analytics-ingress/-/members-analytics-ingress-0.1.8.tgz#648b3fa6dee947d0c06892f19f6ddaa5e896a4fc"
|
resolved "https://registry.yarnpkg.com/@tryghost/members-analytics-ingress/-/members-analytics-ingress-0.1.9.tgz#769d6d2f7b4b4e55ae12d8ef170974f5a25ffafe"
|
||||||
integrity sha512-RTd+W5BcGFqYI1/ArzFnZwFDs62FwLnmtvJO4dOfP5i1dY0vtwqbqDZNciHXa2fW9IAP4PLiCiyN4MrppqI6xw==
|
integrity sha512-J0mzLkXycEuunugc+1KX9vPMvesCeJ7nLXLlT/bf7BRBov4hDjthBhAAs7XIciyGZXx0hleuh0EAVYZUnwlHhg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@tryghost/domain-events" "^0.1.6"
|
"@tryghost/domain-events" "^0.1.6"
|
||||||
"@tryghost/member-events" "^0.3.3"
|
"@tryghost/member-events" "^0.3.4"
|
||||||
|
|
||||||
"@tryghost/members-api@4.8.1":
|
"@tryghost/members-api@4.8.3":
|
||||||
version "4.8.1"
|
version "4.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-4.8.1.tgz#f3f6e4b5373407bc9c73275edf7aeb3007d4b446"
|
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-4.8.3.tgz#b85c095e2ac4477fe554c44607dec26681592aa9"
|
||||||
integrity sha512-tAmdwrtFwB8DbezSySIL1GhumVpfCi4b2FIfQo4eTtfyEGiUdoD5JRR+ToNbxor1/h+HN0gETnteflmQ97mOEw==
|
integrity sha512-7YOF65FWTICfWDuCwlRJmjVhzX4rRM4gjig9Hx9nxuRosOusv45B88RpbWqB4b7i/OfdMUvvVSemlGXjx8fPOA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nexes/nql" "^0.6.0"
|
"@nexes/nql" "^0.6.0"
|
||||||
"@tryghost/debug" "^0.1.2"
|
"@tryghost/debug" "^0.1.2"
|
||||||
@ -1542,11 +1542,11 @@
|
|||||||
"@tryghost/errors" "^1.1.1"
|
"@tryghost/errors" "^1.1.1"
|
||||||
"@tryghost/logging" "^2.0.0"
|
"@tryghost/logging" "^2.0.0"
|
||||||
"@tryghost/magic-link" "^1.0.17"
|
"@tryghost/magic-link" "^1.0.17"
|
||||||
"@tryghost/member-analytics-service" "^0.1.7"
|
"@tryghost/member-analytics-service" "^0.1.8"
|
||||||
"@tryghost/member-events" "^0.3.3"
|
"@tryghost/member-events" "^0.3.4"
|
||||||
"@tryghost/members-analytics-ingress" "^0.1.8"
|
"@tryghost/members-analytics-ingress" "^0.1.9"
|
||||||
"@tryghost/members-payments" "^0.1.8"
|
"@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"
|
"@tryghost/tpl" "^0.1.2"
|
||||||
"@types/jsonwebtoken" "^8.5.1"
|
"@types/jsonwebtoken" "^8.5.1"
|
||||||
bluebird "^3.5.4"
|
bluebird "^3.5.4"
|
||||||
@ -1570,10 +1570,10 @@
|
|||||||
papaparse "5.3.1"
|
papaparse "5.3.1"
|
||||||
pump "^3.0.0"
|
pump "^3.0.0"
|
||||||
|
|
||||||
"@tryghost/members-importer@0.4.1":
|
"@tryghost/members-importer@0.5.0":
|
||||||
version "0.4.1"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/members-importer/-/members-importer-0.4.1.tgz#e5c64ac94f03b922c9bb4067eeb21945373637c8"
|
resolved "https://registry.yarnpkg.com/@tryghost/members-importer/-/members-importer-0.5.0.tgz#262b542bd597853b091b819adcd8a13c7bd2dc0a"
|
||||||
integrity sha512-8vpei+dHRRosS1UpkyPDI/3100kFMdEidxH8hDFEZbyQjphGmQdeKpi04yJUQFGeANKBNljiifOuZ9vJdJcd9g==
|
integrity sha512-WNLexqKyqJ6yyBSoZWhtacJwWEoG6PlPxaCwjAk2pifPfWuT+T1uVk+tm7ETmQzBLxf0HKpcdpN4j4w9J0A0Ew==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@tryghost/errors" "^0.2.13"
|
"@tryghost/errors" "^0.2.13"
|
||||||
"@tryghost/members-csv" "^1.2.3"
|
"@tryghost/members-csv" "^1.2.3"
|
||||||
@ -1609,7 +1609,7 @@
|
|||||||
jsonwebtoken "^8.5.1"
|
jsonwebtoken "^8.5.1"
|
||||||
lodash "^4.17.11"
|
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"
|
version "0.6.5"
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/members-stripe-service/-/members-stripe-service-0.6.5.tgz#68e6bc4d3f5e4ee61468f5fc43eb77650543bf63"
|
resolved "https://registry.yarnpkg.com/@tryghost/members-stripe-service/-/members-stripe-service-0.6.5.tgz#68e6bc4d3f5e4ee61468f5fc43eb77650543bf63"
|
||||||
integrity sha512-cTFcacy/Rst+ao4oRIszCMUw8oI8hKo1u3xxbjXjchDQUfVAUxxfnDoHcULH1d4mXc+LdftD2iKYNav/BATC5Q==
|
integrity sha512-cTFcacy/Rst+ao4oRIszCMUw8oI8hKo1u3xxbjXjchDQUfVAUxxfnDoHcULH1d4mXc+LdftD2iKYNav/BATC5Q==
|
||||||
@ -1619,16 +1619,6 @@
|
|||||||
leaky-bucket "^2.2.0"
|
leaky-bucket "^2.2.0"
|
||||||
stripe "^8.174.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":
|
"@tryghost/metrics@1.0.3":
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/metrics/-/metrics-1.0.3.tgz#091ff6d0cc6c0da05bab6ba8231312cae3d395de"
|
resolved "https://registry.yarnpkg.com/@tryghost/metrics/-/metrics-1.0.3.tgz#091ff6d0cc6c0da05bab6ba8231312cae3d395de"
|
||||||
@ -1878,6 +1868,14 @@
|
|||||||
moment-timezone "0.5.23"
|
moment-timezone "0.5.23"
|
||||||
validator "7.2.0"
|
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":
|
"@tryghost/version@0.1.9", "@tryghost/version@^0.1.9":
|
||||||
version "0.1.9"
|
version "0.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@tryghost/version/-/version-0.1.9.tgz#47474fc04675028d5e178c73d135c138165802a1"
|
resolved "https://registry.yarnpkg.com/@tryghost/version/-/version-0.1.9.tgz#47474fc04675028d5e178c73d135c138165802a1"
|
||||||
|
Loading…
Reference in New Issue
Block a user