2022-01-27 13:57:51 +03:00
const errors = require ( '@tryghost/errors' ) ;
2022-02-03 18:02:33 +03:00
const DomainEvents = require ( '@tryghost/domain-events' ) ;
const { MemberSubscribeEvent } = require ( '@tryghost/member-events' ) ;
2022-01-27 13:57:51 +03:00
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 ` ,
2022-08-25 05:43:46 +03:00
emailVerificationEmailMessageImport : ` Email verification needed for site: {siteUrl}, has imported: {amountTriggered} members in the last 30 days. ` ,
2022-08-25 09:26:26 +03:00
emailVerificationEmailMessageAdmin : ` Email verification needed for site: {siteUrl} has added: {amountTriggered} members through the Admin client in the last 30 days. ` ,
2022-08-25 05:43:46 +03:00
emailVerificationEmailMessageAPI : ` Email verification needed for site: {siteUrl} has added: {amountTriggered} members through the API in the last 30 days. `
2022-01-27 13:57:51 +03:00
} ;
class VerificationTrigger {
/ * *
2022-02-04 14:54:48 +03:00
*
2022-01-27 13:57:51 +03:00
* @ param { object } deps
2022-08-25 09:26:26 +03:00
* @ param { number } deps . apiTriggerThreshold Threshold for triggering API & Import sourced verifications
* @ param { number } deps . adminTriggerThreshold Threshold for triggering Admin sourced verifications
2022-08-25 12:07:03 +03:00
* @ param { number } deps . importTriggerThreshold Threshold for triggering Import sourced verifications
2022-01-27 13:57:51 +03:00
* @ 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
2022-08-25 05:43:46 +03:00
* @ 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
2022-01-27 13:57:51 +03:00
* @ param { any } deps . membersStats MemberStats service
* @ param { any } deps . Settings Ghost Settings model
* @ param { any } deps . eventRepository For querying events
* /
constructor ( {
2022-08-25 04:41:19 +03:00
apiTriggerThreshold ,
2022-08-25 09:26:26 +03:00
adminTriggerThreshold ,
2022-08-25 12:07:03 +03:00
importTriggerThreshold ,
2022-01-27 13:57:51 +03:00
isVerified ,
isVerificationRequired ,
sendVerificationEmail ,
membersStats ,
Settings ,
eventRepository
} ) {
2022-08-25 04:41:19 +03:00
this . _apiTriggerThreshold = apiTriggerThreshold ;
2022-08-25 09:26:26 +03:00
this . _adminTriggerThreshold = adminTriggerThreshold ;
2022-08-25 12:07:03 +03:00
this . _importTriggerThreshold = importTriggerThreshold ;
2022-01-27 13:57:51 +03:00
this . _isVerified = isVerified ;
this . _isVerificationRequired = isVerificationRequired ;
this . _sendVerificationEmail = sendVerificationEmail ;
this . _membersStats = membersStats ;
this . _Settings = Settings ;
this . _eventRepository = eventRepository ;
2022-02-03 18:02:33 +03:00
2022-08-24 09:06:05 +03:00
this . _handleMemberSubscribeEvent = this . _handleMemberSubscribeEvent . bind ( this ) ;
DomainEvents . subscribe ( MemberSubscribeEvent , this . _handleMemberSubscribeEvent ) ;
}
2022-02-03 18:02:33 +03:00
2022-08-25 09:26:26 +03:00
/ * *
*
* @ param { MemberSubscribeEvent } event
* /
2022-08-24 09:06:05 +03:00
async _handleMemberSubscribeEvent ( event ) {
2022-08-24 09:23:32 +03:00
const source = event . data ? . source ;
2022-08-25 09:26:26 +03:00
let sourceThreshold ;
if ( source === 'api' ) {
sourceThreshold = this . _apiTriggerThreshold ;
} else if ( source === 'admin' ) {
sourceThreshold = this . _adminTriggerThreshold ;
}
2022-08-24 09:23:32 +03:00
2022-08-25 09:26:26 +03:00
if ( [ 'api' , 'admin' ] . includes ( source ) && isFinite ( sourceThreshold ) ) {
2022-08-24 09:06:05 +03:00
const createdAt = new Date ( ) ;
createdAt . setDate ( createdAt . getDate ( ) - 30 ) ;
const events = await this . _eventRepository . getNewsletterSubscriptionEvents ( { } , {
2022-08-24 09:23:32 +03:00
'data.source' : ` data.source:' ${ source } ' ` ,
2022-08-24 09:06:05 +03:00
'data.created_at' : ` data.created_at:>' ${ createdAt . toISOString ( ) . replace ( 'T' , ' ' ) . substring ( 0 , 19 ) } ' `
} ) ;
2022-08-24 09:23:32 +03:00
if ( events . meta . pagination . total > sourceThreshold ) {
2022-08-25 04:47:59 +03:00
await this . _startVerificationProcess ( {
amount : events . meta . pagination . total ,
2022-08-24 09:06:05 +03:00
throwOnTrigger : false ,
2022-08-24 09:23:32 +03:00
source : source
2022-08-24 09:06:05 +03:00
} ) ;
2022-02-03 18:02:33 +03:00
}
2022-08-24 09:06:05 +03:00
}
2022-01-27 13:57:51 +03:00
}
async getImportThreshold ( ) {
2022-08-25 12:07:03 +03:00
const volumeThreshold = this . _importTriggerThreshold ;
2022-01-27 13:57:51 +03:00
if ( isFinite ( volumeThreshold ) ) {
const membersTotal = await this . _membersStats . getTotalMembers ( ) ;
return Math . max ( membersTotal , volumeThreshold ) ;
} else {
return volumeThreshold ;
}
}
2022-04-13 19:35:24 +03:00
async testImportThreshold ( ) {
2022-08-25 12:07:03 +03:00
if ( ! isFinite ( this . _importTriggerThreshold ) ) {
2022-07-26 12:35:48 +03:00
// Infinite threshold, quick path
return ;
}
2022-04-13 19:35:24 +03:00
const createdAt = new Date ( ) ;
createdAt . setDate ( createdAt . getDate ( ) - 30 ) ;
const events = await this . _eventRepository . getNewsletterSubscriptionEvents ( { } , {
'data.source' : ` data.source:'import' ` ,
'data.created_at' : ` data.created_at:>' ${ createdAt . toISOString ( ) . replace ( 'T' , ' ' ) . substring ( 0 , 19 ) } ' `
} ) ;
const membersTotal = await this . _membersStats . getTotalMembers ( ) ;
// Import threshold is either the total number of members (discounting any created by imports in
// the last 30 days) or the threshold defined in config, whichever is greater.
2022-08-25 12:07:03 +03:00
const importThreshold = Math . max ( membersTotal - events . meta . pagination . total , this . _importTriggerThreshold ) ;
2022-04-13 19:35:24 +03:00
if ( isFinite ( importThreshold ) && events . meta . pagination . total > importThreshold ) {
2022-08-25 04:47:59 +03:00
await this . _startVerificationProcess ( {
amount : events . meta . pagination . total ,
2022-04-13 19:35:24 +03:00
throwOnTrigger : false ,
source : 'import'
} ) ;
}
}
2022-02-04 14:54:48 +03:00
/ * *
* @ typedef IVerificationResult
2022-01-27 13:57:51 +03:00
* @ property { boolean } needsVerification Whether the verification workflow was triggered
* /
/ * *
2022-02-04 14:54:48 +03:00
*
2022-01-27 13:57:51 +03:00
* @ param { object } config
2022-08-25 04:47:59 +03:00
* @ param { number } config . amount The amount of members that triggered the verification process
2022-01-27 13:57:51 +03:00
* @ param { boolean } config . throwOnTrigger Whether to throw if verification is needed
2022-02-03 18:03:47 +03:00
* @ param { string } config . source Source of the verification trigger - currently either 'api' or 'import'
2022-02-04 14:54:48 +03:00
* @ returns { Promise < IVerificationResult > } Object containing property "needsVerification" - true when triggered
2022-01-27 13:57:51 +03:00
* /
2022-08-25 04:47:59 +03:00
async _startVerificationProcess ( {
amount ,
2022-02-03 18:03:47 +03:00
throwOnTrigger ,
source
2022-01-27 13:57:51 +03:00
} ) {
if ( ! this . _isVerified ( ) ) {
// Only trigger flag change and escalation email the first time
if ( ! this . _isVerificationRequired ( ) ) {
await this . _Settings . edit ( [ {
key : 'email_verification_required' ,
value : true
} ] , { context : { internal : true } } ) ;
2022-08-25 09:26:26 +03:00
// Setting import as a default message
let verificationMessage = messages . emailVerificationEmailMessageImport ;
if ( source === 'api' ) {
verificationMessage = messages . emailVerificationEmailMessageAPI ;
} else if ( source === 'admin' ) {
verificationMessage = messages . emailVerificationEmailMessageAdmin ;
}
2022-01-27 13:57:51 +03:00
this . _sendVerificationEmail ( {
2022-08-25 09:26:26 +03:00
message : verificationMessage ,
2022-01-27 13:57:51 +03:00
subject : messages . emailVerificationEmailSubject ,
2022-08-25 05:43:46 +03:00
amountTriggered : amount
2022-01-27 13:57:51 +03:00
} ) ;
if ( throwOnTrigger ) {
throw new errors . ValidationError ( {
message : messages . emailVerificationNeeded
} ) ;
}
return {
needsVerification : true
} ;
}
}
return {
needsVerification : false
} ;
}
}
2022-02-04 14:54:48 +03:00
module . exports = VerificationTrigger ;