From a4004e86d47251cb6bd07a390986d8e523c31e9a Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 26 Oct 2023 18:21:56 +0100 Subject: [PATCH] update mailer util --- mod/provider/cloudfront.js | 11 +---- mod/user/delete.js | 19 +++------ mod/user/login.js | 28 ++++++------- mod/user/register.js | 19 ++++----- mod/user/update.js | 11 ++--- mod/user/verify.js | 25 +++++------ mod/utils/languageTemplates.js | 44 ------------------- mod/utils/mailer.js | 77 +++++++++++++++++++++++++--------- 8 files changed, 102 insertions(+), 132 deletions(-) diff --git a/mod/provider/cloudfront.js b/mod/provider/cloudfront.js index 35d8d46db..d4107b0f6 100644 --- a/mod/provider/cloudfront.js +++ b/mod/provider/cloudfront.js @@ -1,10 +1,3 @@ -const https = require('https') - -const httpsAgent = new https.Agent({ - keepAlive: true, - maxSockets: parseInt(process.env.CUSTOM_AGENT) || 1 -}) - const { readFileSync } = require('fs') const { join } = require('path') @@ -36,9 +29,7 @@ module.exports = async ref => { return signedURL; } - const response = await fetch(signedURL, { - agent: process.env.CUSTOM_AGENT && httpsAgent - }) + const response = await fetch(signedURL) logger(`${response.status} - ${url}`,'cloudfront') diff --git a/mod/user/delete.js b/mod/user/delete.js index 0b4cd7ffc..734cfd453 100644 --- a/mod/user/delete.js +++ b/mod/user/delete.js @@ -2,8 +2,6 @@ const acl = require('./acl')() const mailer = require('../utils/mailer') -const languageTemplates = require('../utils/languageTemplates') - module.exports = async (req, res) => { const email = req.params.email.replace(/\s+/g, '') @@ -18,21 +16,14 @@ module.exports = async (req, res) => { const user = rows[0] - const protocol = `${req.headers.host.includes('localhost') && 'http' || 'https'}://` - - const host = `${req.headers.host.includes('localhost') && req.headers.host || process.env.ALIAS || req.headers.host}${process.env.DIR}` - // Sent email to inform user that their account has been deleted. - const mail_template = await languageTemplates('deleted_account', user.language) - - // Assign user email to mail_template. - Object.assign(mail_template, { + await mailer({ + template: 'deleted_account', + language: user.language, to: user.email, - host, - protocol + host: `${req.headers.host.includes('localhost') && req.headers.host || process.env.ALIAS || req.headers.host}${process.env.DIR}`, + protocol: `${req.headers.host.includes('localhost') && 'http' || 'https'}://` }) - - await mailer(mail_template) res.send('User account deleted.') } \ No newline at end of file diff --git a/mod/user/login.js b/mod/user/login.js index d97389d1f..f4afcc9a1 100644 --- a/mod/user/login.js +++ b/mod/user/login.js @@ -150,15 +150,15 @@ async function post(req, res) { // Accounts must be verified and approved for login if (!user.verified || !user.approved) { - - var mail_template = await languageTemplates('failed_login', user.language) - - await mailer(Object.assign(mail_template, { + + await mailer({ + template: 'failed_login', + language: user.language, to: user.email, host: host, protocol: protocol, remote_address - })) + }) return new Error(await languageTemplates('user_not_verified', user.language)) } @@ -218,29 +218,29 @@ async function post(req, res) { WHERE lower(email) = lower($1);`, [req.body.email]) if (rows instanceof Error) return new Error(await languageTemplates('failed_query', req.params.language)) - - var mail_template = await languageTemplates('locked_account', user.language) - - await mailer(Object.assign(mail_template, { + + await mailer({ + template: 'locked_account', + language: user.language, to: user.email, host: host, failed_attempts: parseInt(process.env.FAILED_ATTEMPTS) || 3, protocol: protocol, verificationtoken: verificationtoken, remote_address - })) + }) return new Error(await languageTemplates('user_locked', user.language)) } // Login has failed but account is not locked (yet). - var mail_template = await languageTemplates('login_incorrect', user.language) - - await mailer(Object.assign(mail_template, { + await mailer({ + template: 'login_incorrect', + language: user.language, to: user.email, host: host, remote_address - })) + }) return new Error(await languageTemplates('auth_failed', req.params.language)) } \ No newline at end of file diff --git a/mod/user/register.js b/mod/user/register.js index ee3cbe57e..5e6e86595 100644 --- a/mod/user/register.js +++ b/mod/user/register.js @@ -114,15 +114,15 @@ async function post(req, res) { if (rows instanceof Error) return res.status(500).send(await languageTemplates('failed_query', req.params.language)) - // Sent mail with verification token to the account email address. - var mail_template = await languageTemplates('verify_password_reset', user.language) - - await mailer(Object.assign(mail_template, { + // Sent mail with verification token to the account email address. + await mailer({ + template: 'verify_password_reset', + language: user.language, to: user.email, host: host, link: `${protocol}${host}/api/user/verify/${verificationtoken}`, remote_address - })) + }) const password_reset_verification = await languageTemplates('password_reset_verification', user.language) @@ -151,15 +151,14 @@ async function post(req, res) { if (rows instanceof Error) return res.status(500).send(await languageTemplates('failed_query', req.params.language)) - // Sent mail with verification token to the account email address. - var mail_template = await languageTemplates('verify_account', language) - - await mailer(Object.assign(mail_template, { + await mailer({ + template: 'verify_account', + language, to: req.body.email, host: host, link: `${protocol}${host}/api/user/verify/${verificationtoken}`, remote_address - })) + }) // Return msg. No redirect for password reset. res.send(await languageTemplates('new_account_registered', language)) diff --git a/mod/user/update.js b/mod/user/update.js index 79bba61a2..fe8f1d183 100644 --- a/mod/user/update.js +++ b/mod/user/update.js @@ -37,17 +37,14 @@ module.exports = async (req, res) => { // Send email to the user account if an account has been approved. if (req.params.field === 'approved' && req.params.value === 'true') { - - const mail_template = await languageTemplates('approved_account', req.params.user.language) - - // Assign email to mail template - Object.assign(mail_template, { + + await mailer({ + template: 'approved_account', + language: req.params.user.language, to: email, host: host, protocol: protocol }) - - await mailer(mail_template) } const update_ok = await languageTemplates('update_ok', req.params.user.language) diff --git a/mod/user/verify.js b/mod/user/verify.js index b890178a6..6db19d46c 100644 --- a/mod/user/verify.js +++ b/mod/user/verify.js @@ -82,30 +82,27 @@ module.exports = async (req, res) => { // One or more administrator have been if (rows.length > 0) { - // Create protocol and host for mail templates. - const protocol = `${req.headers.host.includes('localhost') && 'http' || 'https'}://` - const host = `${req.headers.host.includes('localhost') && req.headers.host || process.env.ALIAS || req.headers.host}${process.env.DIR}` - // Get array of mail promises. const mail_promises = rows.map(async row => { - const mail_template = await languageTemplates('admin_email', row.language || req.params.language) - - // Assign email to mail template. - Object.assign(mail_template, { + await mailer({ + template: 'admin_email', + language: row.language, to: row.email, email: user.email, - host: host, - protocol: protocol + host: `${req.headers.host.includes('localhost') && req.headers.host || process.env.ALIAS || req.headers.host}${process.env.DIR}`, + protocol: `${req.headers.host.includes('localhost') && 'http' || 'https'}://` }) - - return mailer(mail_template) }) // Continue after all mail promises have been resolved. Promise - .all(mail_promises) - .then(async arr => res.send(await languageTemplates('account_await_approval', user.language))) + .allSettled(mail_promises) + .then(async arr => { + + console.log(arr) + res.send(await languageTemplates('account_await_approval', user.language)) + }) .catch(error => console.error(error)) } else { diff --git a/mod/utils/languageTemplates.js b/mod/utils/languageTemplates.js index fc4d6aa66..b0fde04dc 100644 --- a/mod/utils/languageTemplates.js +++ b/mod/utils/languageTemplates.js @@ -18,55 +18,11 @@ module.exports = async (key, lang = 'en') => { let template = Object.hasOwn(allLanguages, lang)? allLanguages[lang]: allLanguages.en; - console.log(template) - if (typeof template === 'string' && Object.hasOwn(getFrom, template.split(':')[0])) { // Get template from method. template = await getFrom[template.split(':')[0]](template) - - } else if (typeof template === 'object' && (Object.hasOwn(template, 'text') || Object.hasOwn(template, 'html'))) { - - if (Object.hasOwn(getFrom, template.text?.split(':')[0])) { - - template.text = await getFrom[template.text.split(':')[0]](template.text) - } - - if (Object.hasOwn(getFrom, template.html?.split(':')[0])) { - - template.html = await getFrom[template.html.split(':')[0]](template.html) - } - } - // // Prevent prototype polluting assignment. - // Object.freeze(Object.getPrototypeOf(template)); - - // for (key in template) { - - // // Prevent prototype polluting assignment. - // if (/__proto__/.test(key)) continue; - - // // Template key / value is a string with a valid get method. - // if (typeof template[key] === 'string' - // && Object.hasOwn(template, key) - // && Object.hasOwn(getFrom, template[key].split(':')[0])) { - - // // Assign template key value from method. - // template[key] = await getFrom[template[key].split(':')[0]](template[key]) - // } - - // // Template key value is still string after assignment - // if (typeof template[key] === 'string') { - - // // Look for template params to be substituted. - // template[key] = template[key].replace(/\$\{{1}(.*?)\}{1}/g, - - // // Replace matched params in string values - // matched => params[matched.replace(/\$\{{1}|\}{1}/g, '')] || '') - // } - - // } - return template } diff --git a/mod/utils/mailer.js b/mod/utils/mailer.js index 8fe306f21..adabdb567 100644 --- a/mod/utils/mailer.js +++ b/mod/utils/mailer.js @@ -1,36 +1,75 @@ const logger = require('./logger') +const languageTemplates = require('./languageTemplates') + +const getFrom = require('../provider/getFrom') + const mailer = require('nodemailer') -module.exports = async mail => { +let transport - if (!process.env.TRANSPORT && !process.env.TRANSPORT_HOST) return console.error(new Error('Transport not set.')) +module.exports = async params => { + + if (!process.env.TRANSPORT && !process.env.TRANSPORT_HOST) { + console.warn('No transport method set for mail.') + return; + } const email = process.env.TRANSPORT_EMAIL || process.env.TRANSPORT.split(':')[1] - Object.assign(mail, { - from: email, - sender: email, - }) + if (!transport) { + + transport = process.env.TRANSPORT ? + mailer.createTransport(process.env.TRANSPORT) : + mailer.createTransport({ + host: process.env.TRANSPORT_HOST, + name: email.match(/[^@]*$/)[0], + port: process.env.TRANSPORT_PORT || 587, + secure: false, + requireTLS: process.env.TRANSPORT_TLS && true, + auth: { + user: email, + pass: process.env.TRANSPORT_PASSWORD + } + }) + } - mail.text = mail.text && mail.text.replace(/^(?!\s+$)\s+/gm, '') + const template = await languageTemplates(params.template, params.language) - if (!process.env.TRANSPORT_HOST) return mailer.createTransport(process.env.TRANSPORT).sendMail(mail) + if (template.text) { - const transporter = mailer.createTransport( { - host: process.env.TRANSPORT_HOST, - name: email.match(/[^@]*$/)[0], - port: process.env.TRANSPORT_PORT || 587, - secure: false, - requireTLS: process.env.TRANSPORT_TLS && true, - auth: { - user: email, - pass: process.env.TRANSPORT_PASSWORD + if (Object.hasOwn(getFrom, template.text?.split(':')[0])) { + + template.text = await getFrom[template.text.split(':')[0]](template.text) } - }) - const result = await transporter.sendMail(mail).catch(err => console.error(err)) + template.text = replaceStringParams(template.text, params) + + template.text = template.text.replace(/^(?!\s+$)\s+/gm, '') + } + + if (Object.hasOwn(getFrom, template.html?.split(':')[0])) { + + template.html = await getFrom[template.html.split(':')[0]](template.html) + + template.html = replaceStringParams(template.html, params) + } + + template.subject = replaceStringParams(template.subject, params) + + template.to = params.to + template.from = email + template.sender = email + + const result = await transport.sendMail(template).catch(err => console.error(err)) logger(result, 'mailer') +} + +function replaceStringParams(string, params) { + + return string.replace(/\$\{{1}(.*?)\}{1}/g, + // Replace matched params in string values + matched => params[matched.replace(/\$\{{1}|\}{1}/g, '')] || '') } \ No newline at end of file