You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by an...@apache.org on 2019/03/07 08:37:58 UTC
[ignite] branch master updated: IGNITE-11500 Web Console: Create
template for email. (#6244)
This is an automated email from the ASF dual-hosted git repository.
anovikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 3068fdc IGNITE-11500 Web Console: Create template for email. (#6244)
3068fdc is described below
commit 3068fdc14ebad77c15b0a050f5660534b7296622
Author: Andrey Novikov <an...@apache.org>
AuthorDate: Thu Mar 7 15:37:51 2019 +0700
IGNITE-11500 Web Console: Create template for email. (#6244)
---
modules/web-console/backend/package.json | 3 +-
modules/web-console/backend/routes/admin.js | 5 +-
modules/web-console/backend/services/auth.js | 6 +-
modules/web-console/backend/services/mails.js | 158 +++++++++++++++---------
modules/web-console/backend/services/users.js | 22 ++--
modules/web-console/backend/templates/base.html | 21 ++++
6 files changed, 141 insertions(+), 74 deletions(-)
diff --git a/modules/web-console/backend/package.json b/modules/web-console/backend/package.json
index 9d1918e..45a538d 100644
--- a/modules/web-console/backend/package.json
+++ b/modules/web-console/backend/package.json
@@ -34,6 +34,7 @@
"migrations/*",
"routes/*",
"services/*",
+ "templates/*",
"node_modules/getos/logic/*",
"node_modules/mongodb-download/node_modules/getos/logic/*"
],
@@ -63,7 +64,7 @@
"mongoose": "4.11.4",
"morgan": "1.8.2",
"nconf": "0.8.4",
- "nodemailer": "4.0.1",
+ "nodemailer": "^5.1.1",
"passport": "0.3.2",
"passport-local": "1.0.0",
"passport-local-mongoose": "4.0.0",
diff --git a/modules/web-console/backend/routes/admin.js b/modules/web-console/backend/routes/admin.js
index 4a38723..70f07b0 100644
--- a/modules/web-console/backend/routes/admin.js
+++ b/modules/web-console/backend/routes/admin.js
@@ -23,20 +23,19 @@ const express = require('express');
module.exports = {
implements: 'routes/admin',
- inject: ['settings', 'mongo', 'services/spaces', 'services/mails', 'services/sessions', 'services/users', 'services/notifications']
+ inject: ['settings', 'mongo', 'services/spaces', 'services/sessions', 'services/users', 'services/notifications']
};
/**
* @param settings
* @param mongo
* @param spacesService
- * @param {MailsService} mailsService
* @param {SessionsService} sessionsService
* @param {UsersService} usersService
* @param {NotificationsService} notificationsService
* @returns {Promise}
*/
-module.exports.factory = function(settings, mongo, spacesService, mailsService, sessionsService, usersService, notificationsService) {
+module.exports.factory = function(settings, mongo, spacesService, sessionsService, usersService, notificationsService) {
return new Promise((factoryResolve) => {
const router = new express.Router();
diff --git a/modules/web-console/backend/services/auth.js b/modules/web-console/backend/services/auth.js
index c6da86e..95fe006 100644
--- a/modules/web-console/backend/services/auth.js
+++ b/modules/web-console/backend/services/auth.js
@@ -57,7 +57,7 @@ module.exports.factory = (mongo, settings, errors, utilsService, mailsService) =
return user.save();
})
- .then((user) => mailsService.emailUserResetLink(host, user));
+ .then((user) => mailsService.sendResetLink(host, user));
}
/**
@@ -88,7 +88,7 @@ module.exports.factory = (mongo, settings, errors, utilsService, mailsService) =
});
});
})
- .then((user) => mailsService.emailPasswordChanged(host, user));
+ .then((user) => mailsService.sendPasswordChanged(host, user));
}
/**
@@ -168,7 +168,7 @@ module.exports.factory = (mongo, settings, errors, utilsService, mailsService) =
return user.save();
})
- .then((user) => mailsService.emailUserActivation(host, user));
+ .then((user) => mailsService.sendActivationLink(host, user));
}
}
diff --git a/modules/web-console/backend/services/mails.js b/modules/web-console/backend/services/mails.js
index 183fbe1..50d61e8 100644
--- a/modules/web-console/backend/services/mails.js
+++ b/modules/web-console/backend/services/mails.js
@@ -17,6 +17,7 @@
'use strict';
+const fs = require('fs');
const _ = require('lodash');
const nodemailer = require('nodemailer');
@@ -34,16 +35,67 @@ module.exports = {
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 {Account} user
- * @param {String} subject
- * @param {String} html
- * @param {String} sendErr
+ * @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(user, subject, html, sendErr) {
+ send(template, host, user, subject, message, customCtx = {}) {
const options = settings.mail;
return new Promise((resolve, reject) => {
@@ -61,22 +113,21 @@ module.exports.factory = (settings) => {
return transporter.verify().then(() => transporter);
})
.then((transporter) => {
- const sign = options.sign ? `<br><br>--------------<br>${options.sign}<br>` : '';
- const to = `"${user.firstName} ${user.lastName}" <${user.email}>`;
+ const context = this.buildContext(host, user, message, customCtx);
+
+ context.subject = this.getMessage(subject, context);
- const mail = {
+ return transporter.sendMail({
from: options.from,
- to,
- subject,
- html: html + sign
- };
-
- return transporter.sendMail(mail);
+ 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(sendErr ? new Error(sendErr) : err);
+ return Promise.reject(err);
});
}
@@ -87,23 +138,22 @@ module.exports.factory = (settings) => {
* @param user User that signed up.
* @param createdByAdmin Whether user was created by admin.
*/
- emailUserSignUp(host, user, createdByAdmin) {
- const resetLink = `${host}/password/reset?token=${user.resetPasswordToken}`;
-
- const sbj = createdByAdmin
- ? 'Account was created for'
- : 'Thanks for signing up for';
-
- const reason = createdByAdmin
- ? 'administrator created account for you'
- : 'you have signed up';
-
- return this.send(user, `${sbj} ${settings.mail.greeting}.`,
- `Hello ${user.firstName} ${user.lastName}!<br><br>` +
- `You are receiving this email because ${reason} to use <a href="${host}">${settings.mail.greeting}</a>.<br><br>` +
+ 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>`);
+ '<a href="${resetLink}">${resetLink}</a>'
+ );
}
/**
@@ -112,15 +162,13 @@ module.exports.factory = (settings) => {
* @param host
* @param user
*/
- emailUserActivation(host, user) {
- const activationLink = `${host}/signin?activationToken=${user.activationToken}`;
-
- return this.send(user, `Confirm your account on ${settings.mail.greeting}`,
- `Hello ${user.firstName} ${user.lastName}!<br><br>` +
- `You are receiving this email because you have signed up to use <a href="${host}">${settings.mail.greeting}</a>.<br><br>` +
+ 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>`,
- 'Failed to send email with confirm account link!');
+ '<a href="${activationLink}">${activationLink}</a>'
+ )
+ .catch(() => Promise.reject(new Error('Failed to send email with confirm account link!')));
}
/**
@@ -129,16 +177,14 @@ module.exports.factory = (settings) => {
* @param host
* @param user
*/
- emailUserResetLink(host, user) {
- const resetLink = `${host}/password/reset?token=${user.resetPasswordToken}`;
-
- return this.send(user, 'Password Reset',
- `Hello ${user.firstName} ${user.lastName}!<br><br>` +
+ 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.',
- 'Failed to send email with reset link!');
+ '<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!')));
}
/**
@@ -146,11 +192,11 @@ module.exports.factory = (settings) => {
* @param host
* @param user
*/
- emailPasswordChanged(host, user) {
- return this.send(user, 'Your password has been changed',
- `Hello ${user.firstName} ${user.lastName}!<br><br>` +
- `This is a confirmation that the password for your account on <a href="${host}">${settings.mail.greeting}</a> has just been changed.<br><br>`,
- 'Password was changed, but failed to send confirmation email!');
+ 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!')));
}
/**
@@ -158,11 +204,11 @@ module.exports.factory = (settings) => {
* @param host
* @param user
*/
- emailUserDeletion(host, user) {
- return this.send(user, 'Your account was removed',
- `Hello ${user.firstName} ${user.lastName}!<br><br>` +
- `You are receiving this email because your account for <a href="${host}">${settings.mail.greeting}</a> was removed.`,
- 'Account was removed, but failed to send email notification to 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!')));
}
}
diff --git a/modules/web-console/backend/services/users.js b/modules/web-console/backend/services/users.js
index ecfdc0b..377efed 100644
--- a/modules/web-console/backend/services/users.js
+++ b/modules/web-console/backend/services/users.js
@@ -56,6 +56,11 @@ module.exports.factory = (errors, settings, mongo, spacesService, mailsService,
user.resetPasswordToken = utilsService.randomString(settings.tokenLength);
user.activated = false;
+ if (settings.activation.enabled) {
+ user.activationToken = utilsService.randomString(settings.tokenLength);
+ user.activationSentAt = new Date();
+ }
+
if (settings.server.disableSignup && !user.admin && !createdByAdmin)
throw new errors.ServerErrorException('Sign-up is not allowed. Ask your Web Console administrator to create account for you.');
@@ -80,20 +85,15 @@ module.exports.factory = (errors, settings, mongo, spacesService, mailsService,
})
.then((registered) => {
if (settings.activation.enabled) {
- registered.activationToken = utilsService.randomString(settings.tokenLength);
- registered.activationSentAt = new Date();
+ mailsService.sendActivationLink(host, registered);
- if (!createdByAdmin) {
- return registered.save()
- .then(() => {
- mailsService.emailUserActivation(host, registered);
+ if (createdByAdmin)
+ return registered;
- throw new errors.MissingConfirmRegistrationException(registered.email);
- });
- }
+ throw new errors.MissingConfirmRegistrationException(registered.email);
}
- mailsService.emailUserSignUp(host, registered, createdByAdmin);
+ mailsService.sendWelcomeLetter(host, registered, createdByAdmin);
return registered;
});
@@ -244,7 +244,7 @@ module.exports.factory = (errors, settings, mongo, spacesService, mailsService,
.catch((err) => console.error(`Failed to cleanup spaces [user=${user.username}, err=${err}`))
.then(() => user);
})
- .then((user) => mailsService.emailUserDeletion(host, user));
+ .then((user) => mailsService.sendAccountDeleted(host, user));
}
/**
diff --git a/modules/web-console/backend/templates/base.html b/modules/web-console/backend/templates/base.html
new file mode 100644
index 0000000..4b741b1
--- /dev/null
+++ b/modules/web-console/backend/templates/base.html
@@ -0,0 +1,21 @@
+<!--
+ 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.
+-->
+
+Hello ${firstName} ${lastName}!<br><br>
+${message}<br><br>
+--------------<br>
+${sign}<br>
\ No newline at end of file