// email.js // Copyright (C) 2021 Digital Telepresence, LLC // License: Apache-2.0 'use strict'; const path = require('path'); const pug = require('pug'); const nodemailer = require('nodemailer'); const mongoose = require('mongoose'); const EmailBlacklist = mongoose.model('EmailBlacklist'); const disposableEmailDomains = require('disposable-email-provider-domains'); const emailValidator = require('email-validator'); const emailDomainCheck = require('email-domain-check'); const { SiteService } = require('../../lib/site-lib'); class EmailService extends SiteService { constructor (dtp) { super(dtp, module.exports); } async start ( ) { await super.start(); if (process.env.DTP_EMAIL_ENABLED !== 'enabled') { return; } const SMTP_PORT = parseInt(process.env.DTP_EMAIL_SMTP_PORT || '587', 10); this.log.info('creating SMTP transport', { host: process.env.DTP_EMAIL_SMTP_HOST, port: SMTP_PORT, }); this.transport = nodemailer.createTransport({ host: process.env.DTP_EMAIL_SMTP_HOST, port: SMTP_PORT, secure: process.env.DTP_EMAIL_SMTP_SECURE === 'enabled', auth: { user: process.env.DTP_EMAIL_SMTP_USER, pass: process.env.DTP_EMAIL_SMTP_PASS, }, pool: true, maxConnections: parseInt(process.env.DTP_EMAIL_SMTP_POOL_MAX_CONN || '5', 10), maxMessages: parseInt(process.env.DTP_EMAIL_SMTP_POOL_MAX_MSGS || '5', 10), }); this.templates = { html: { welcome: pug.compileFile(path.join(this.dtp.config.root, 'app', 'templates', 'html', 'welcome.pug')), }, text: { welcome: pug.compileFile(path.join(this.dtp.config.root, 'app', 'templates', 'text', 'welcome.pug')), }, }; } async send (message) { this.log.info('sending email', { to: message.to, subject: message.subject }); await this.transport.sendMail(message); } async checkEmailAddress (emailAddress) { this.log.debug('validating email address', { emailAddress }); if (!emailValidator.validate(emailAddress)) { throw new Error('Email address is invalid'); } const domainCheck = await emailDomainCheck(emailAddress); this.log.debug('email domain check', { domainCheck }); if (!domainCheck) { throw new Error('Email address is invalid'); } await this.isEmailBlacklisted(emailAddress); } async isEmailBlacklisted (emailAddress) { emailAddress = emailAddress.toLowerCase().trim(); const domain = emailAddress.split('@')[1]; this.log.debug('checking email domain for blacklist', { domain }); if (disposableEmailDomains.domains.includes(domain)) { this.log.alert('blacklisted email domain blocked', { emailAddress, domain }); throw new Error('Invalid email address'); } const blacklistRecord = await EmailBlacklist.findOne({ email: emailAddress }); if (blacklistRecord) { throw new Error('Email address has requested to not receive emails', { blacklistRecord }); } return false; } async renderTemplate (templateId, templateType, message) { return this.templates[templateType][templateId](message); } } module.exports = { slug: 'email', name: 'email', create: (dtp) => { return new EmailService(dtp); }, };