modules/backend/services/mails.js (113 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; const fs = require('fs'); const _ = require('lodash'); const nodemailer = require('nodemailer'); // Fire me up! module.exports = { implements: 'services/mails', inject: ['settings'] }; /** * @param settings * @returns {MailsService} */ module.exports.factory = (settings) => { class MailsService { /** * Read template file. * @param {String} template Path to template file. * @param template */ readTemplate(template) { try { return fs.readFileSync(template, 'utf8'); } catch (ignored) { throw new Error('Failed to find email template: ' + template); } } /** * Get message with resolved variables. * * @param {string} template Message template. * @param {object} ctx Context. * @return Prepared template. * @throws IOException If failed to prepare template. */ getMessage(template, ctx) { _.forIn(ctx, (value, key) => template = template.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value || 'n/a')); return template; } /** * @param {string} host Web Console host. * @param {Account} user User that signed up. * @param {string} message Message. * @param {object} customCtx Custom context parameters. */ buildContext(host, user, message, customCtx) { return { message, ...customCtx, greeting: settings.mail.greeting, sign: settings.mail.sign, firstName: user.firstName, lastName: user.lastName, email: user.res, host, activationLink: `${host}/signin?activationToken=${user.activationToken}`, resetLink: `${host}/password/reset?token=${user.resetPasswordToken}` }; } /** * Send mail to user. * * @param {string} template Path to template file. * @param {string} host Web Console host. * @param {Account} user User that signed up. * @param {string} subject Email subject. * @param {string} message Email message. * @param {object} customCtx Custom context parameters. * @throws {Error} * @return {Promise} */ send(template, host, user, subject, message, customCtx = {}) { const options = settings.mail; return new Promise((resolve, reject) => { if (_.isEmpty(options)) reject(new Error('SMTP server is not configured.')); if (!_.isEmpty(options.service)) { if (_.isEmpty(options.auth) || _.isEmpty(options.auth.user) || _.isEmpty(options.auth.pass)) reject(new Error(`Credentials is not configured for service: ${options.service}`)); } resolve(nodemailer.createTransport(options)); }) .then((transporter) => { return transporter.verify().then(() => transporter); }) .then((transporter) => { const context = this.buildContext(host, user, message, customCtx); context.subject = this.getMessage(subject, context); return transporter.sendMail({ from: options.from, to: `"${user.firstName} ${user.lastName}" <${user.email}>`, subject: context.subject, html: this.getMessage(this.readTemplate(template), context) }); }) .catch((err) => { console.log('Failed to send email.', err); return Promise.reject(err); }); } /** * Send email when user signed up. * * @param host Web Console host. * @param user User that signed up. * @param createdByAdmin Whether user was created by admin. */ sendWelcomeLetter(host, user, createdByAdmin) { if (createdByAdmin) { return this.send('templates/base.html', host, user, 'Account was created for ${greeting}.', 'You are receiving this email because administrator created account for you to use <a href="${host}">${greeting}</a>.<br><br>' + 'If you do not know what this email is about, please ignore it.<br>' + 'You may reset the password by clicking on the following link, or paste this into your browser:<br><br>' + '<a href="${resetLink}">${resetLink}</a>' ); } return this.send('templates/base.html', host, user, 'Thanks for signing up for ${greeting}.', 'You are receiving this email because you have signed up to use <a href="${host}">${greeting}</a>.<br><br>' + 'If you do not know what this email is about, please ignore it.<br>' + 'You may reset the password by clicking on the following link, or paste this into your browser:<br><br>' + '<a href="${resetLink}">${resetLink}</a>' ); } /** * Send email to user for password reset. * * @param host * @param user */ sendActivationLink(host, user) { return this.send('templates/base.html', host, user, 'Confirm your account on ${greeting}', 'You are receiving this email because you have signed up to use <a href="${host}">${greeting}</a>.<br><br>' + 'Please click on the following link, or paste this into your browser to activate your account:<br><br>' + '<a href="${activationLink}">${activationLink}</a>' ) .catch(() => Promise.reject(new Error('Failed to send email with confirm account link!'))); } /** * Send email to user for password reset. * * @param host * @param user */ sendResetLink(host, user) { return this.send('templates/base.html', host, user, 'Password Reset', 'You are receiving this because you (or someone else) have requested the reset of the password for your account.<br><br>' + 'Please click on the following link, or paste this into your browser to complete the process:<br><br>' + '<a href="${resetLink}">${resetLink}</a><br><br>' + 'If you did not request this, please ignore this email and your password will remain unchanged.' ) .catch(() => Promise.reject(new Error('Failed to send email with reset link!'))); } /** * Send email to user for password reset. * @param host * @param user */ sendPasswordChanged(host, user) { return this.send('templates/base.html', host, user, 'Your password has been changed', 'This is a confirmation that the password for your account on <a href="${host}">${greeting}</a> has just been changed.' ) .catch(() => Promise.reject(new Error('Password was changed, but failed to send confirmation email!'))); } /** * Send email to user when it was deleted. * @param host * @param user */ sendAccountDeleted(host, user) { return this.send('templates/base.html', host, user, 'Your account was removed', 'You are receiving this email because your account for <a href="${host}">${greeting}</a> was removed.', 'Account was removed, but failed to send email notification to user!') .catch(() => Promise.reject(new Error('Password was changed, but failed to send confirmation email!'))); } } return new MailsService(); };