Send email message with PGP inline not PGP/MIME
* Use OpenPGP.js directly instead of nodemailer-openpgp plugin * Use native ES6 string templates instead of nodemailer template engine
This commit is contained in:
parent
656aa9b6ed
commit
6ec72aef06
@ -28,7 +28,6 @@
|
|||||||
"koa-static": "^4.0.1",
|
"koa-static": "^4.0.1",
|
||||||
"mongodb": "^2.2.31",
|
"mongodb": "^2.2.31",
|
||||||
"nodemailer": "^2.4.2",
|
"nodemailer": "^2.4.2",
|
||||||
"nodemailer-openpgp": "^1.0.2",
|
|
||||||
"openpgp": "^2.3.0",
|
"openpgp": "^2.3.0",
|
||||||
"winston": "^2.3.1",
|
"winston": "^2.3.1",
|
||||||
"winston-papertrail": "^1.0.5"
|
"winston-papertrail": "^1.0.5"
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
const log = require('winston');
|
const log = require('winston');
|
||||||
const util = require('../service/util');
|
const util = require('../service/util');
|
||||||
|
const openpgp = require('openpgp');
|
||||||
const nodemailer = require('nodemailer');
|
const nodemailer = require('nodemailer');
|
||||||
const openpgpEncrypt = require('nodemailer-openpgp').openpgpEncrypt;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple wrapper around Nodemailer to send verification emails
|
* A simple wrapper around Nodemailer to send verification emails
|
||||||
@ -44,70 +44,63 @@ class Email {
|
|||||||
secure: (tls !== undefined) ? util.isTrue(tls) : true,
|
secure: (tls !== undefined) ? util.isTrue(tls) : true,
|
||||||
requireTLS: (starttls !== undefined) ? util.isTrue(starttls) : true,
|
requireTLS: (starttls !== undefined) ? util.isTrue(starttls) : true,
|
||||||
});
|
});
|
||||||
if (util.isTrue(pgp)) {
|
this._usePGPEncryption = util.isTrue(pgp);
|
||||||
this._transport.use('stream', openpgpEncrypt());
|
|
||||||
}
|
|
||||||
this._sender = sender;
|
this._sender = sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the verification email to the user using a template.
|
* Send the verification email to the user using a template.
|
||||||
* @param {Object} template the email template to use
|
* @param {Object} template the email template function to use
|
||||||
* @param {Object} userId user id document
|
* @param {Object} userId recipient user id object: { name:'Jon Smith', email:'j@smith.com', publicKeyArmored:'...' }
|
||||||
* @param {string} keyId key id of public key
|
* @param {string} keyId key id of public key
|
||||||
* @param {Object} origin origin of the server
|
* @param {Object} origin origin of the server
|
||||||
* @yield {Object} send response from the SMTP server
|
* @yield {Object} reponse object containing SMTP info
|
||||||
*/
|
*/
|
||||||
async send({template, userId, keyId, origin}) {
|
async send({template, userId, keyId, origin}) {
|
||||||
const message = {
|
const compiled = template({
|
||||||
from: this._sender,
|
name: userId.name,
|
||||||
to: userId,
|
baseUrl: util.url(origin),
|
||||||
subject: template.subject,
|
keyId,
|
||||||
text: template.text,
|
nonce: userId.nonce
|
||||||
html: template.html,
|
});
|
||||||
params: {
|
if (this._usePGPEncryption && userId.publicKeyArmored) {
|
||||||
name: userId.name,
|
compiled.text = await this._pgpEncrypt(compiled.text, userId.publicKeyArmored);
|
||||||
baseUrl: util.url(origin),
|
}
|
||||||
keyId,
|
const sendOptions = {
|
||||||
nonce: userId.nonce
|
from: {name: this._sender.name, address: this._sender.email},
|
||||||
}
|
to: {name: userId.name, address: userId.email},
|
||||||
|
subject: compiled.subject,
|
||||||
|
text: compiled.text
|
||||||
};
|
};
|
||||||
return this._sendHelper(message);
|
return this._sendHelper(sendOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt the message body using OpenPGP.js
|
||||||
|
* @param {string} plaintext the plaintex message body
|
||||||
|
* @param {string} publicKeyArmored the recipient's public key
|
||||||
|
* @return {string} the encrypted PGP message block
|
||||||
|
*/
|
||||||
|
async _pgpEncrypt(plaintext, publicKeyArmored) {
|
||||||
|
const ciphertext = await openpgp.encrypt({
|
||||||
|
data: plaintext,
|
||||||
|
publicKeys: openpgp.key.readArmored(publicKeyArmored).keys,
|
||||||
|
});
|
||||||
|
return ciphertext.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic method to send an email message via nodemailer.
|
* A generic method to send an email message via nodemailer.
|
||||||
* @param {Object} from sender user id object: { name:'Jon Smith', email:'j@smith.com' }
|
* @param {Object} from sender object: { name:'Jon Smith', address:'j@smith.com' }
|
||||||
* @param {Object} to recipient user id object: { name:'Jon Smith', email:'j@smith.com' }
|
* @param {Object} to recipient object: { name:'Jon Smith', address:'j@smith.com' }
|
||||||
* @param {string} subject message subject
|
* @param {string} subject message subject
|
||||||
* @param {string} text message plaintext body template
|
* @param {string} text message text body
|
||||||
* @param {string} html message html body template
|
* @param {string} html message html body
|
||||||
* @param {Object} params (optional) nodermailer template parameters
|
|
||||||
* @yield {Object} reponse object containing SMTP info
|
* @yield {Object} reponse object containing SMTP info
|
||||||
*/
|
*/
|
||||||
async _sendHelper({from, to, subject, text, html, params = {}}) {
|
async _sendHelper(sendOptions) {
|
||||||
const template = {
|
|
||||||
subject,
|
|
||||||
text,
|
|
||||||
html,
|
|
||||||
encryptionKeys: [to.publicKeyArmored]
|
|
||||||
};
|
|
||||||
const sender = {
|
|
||||||
from: {
|
|
||||||
name: from.name,
|
|
||||||
address: from.email
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const recipient = {
|
|
||||||
to: {
|
|
||||||
name: to.name,
|
|
||||||
address: to.email
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sendFn = this._transport.templateSender(template, sender);
|
const info = await this._transport.sendMail(sendOptions);
|
||||||
const info = await sendFn(recipient, params);
|
|
||||||
if (!this._checkResponse(info)) {
|
if (!this._checkResponse(info)) {
|
||||||
log.warn('email', 'Message may not have been received.', info);
|
log.warn('email', 'Message may not have been received.', info);
|
||||||
}
|
}
|
||||||
|
13
src/email/templates.js
Normal file
13
src/email/templates.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
exports.verifyKey = ({name, baseUrl, keyId, nonce}) => ({
|
||||||
|
subject: `Verify Your Key`,
|
||||||
|
text: `Hello ${name},\n\nplease click here to verify your key:\n\n${baseUrl}/api/v1/key?op=verify&keyId=${keyId}&nonce=${nonce}`,
|
||||||
|
html: `<p>Hello ${name},</p><p>please <a href=\"${baseUrl}/api/v1/key?op=verify&keyId=${keyId}&nonce=${nonce}\">click here to verify</a> your key.</p>`
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.verifyRemove = ({name, baseUrl, keyId, nonce}) => ({
|
||||||
|
subject: `Verify Key Removal`,
|
||||||
|
text: `Hello ${name},\n\nplease click here to verify the removal of your key:\n\n${baseUrl}/api/v1/key?op=verifyRemove&keyId=${keyId}&nonce=${nonce}`,
|
||||||
|
html: `<p>Hello ${name},</p><p>please <a href=\"${baseUrl}/api/v1/key?op=verifyRemove&keyId=${keyId}&nonce=${nonce}\">click here to verify</a> the removal of your key.</p>`
|
||||||
|
});
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"verifyKey": {
|
|
||||||
"subject": "Verify Your Key",
|
|
||||||
"text": "Hello {{name}},\n\nplease click here to verify your key:\n\n{{baseUrl}}/api/v1/key?op=verify&keyId={{keyId}}&nonce={{nonce}}",
|
|
||||||
"html": "<p>Hello {{name}},</p><p>please <a href=\"{{baseUrl}}/api/v1/key?op=verify&keyId={{keyId}}&nonce={{nonce}}\">click here to verify</a> your key.</p>"
|
|
||||||
},
|
|
||||||
"verifyRemove": {
|
|
||||||
"subject": "Verify Key Removal",
|
|
||||||
"text": "Hello {{name}},\n\nplease click here to verify the removal of your key:\n\n{{baseUrl}}/api/v1/key?op=verifyRemove&keyId={{keyId}}&nonce={{nonce}}",
|
|
||||||
"html": "<p>Hello {{name}},</p><p>please <a href=\"{{baseUrl}}/api/v1/key?op=verifyRemove&keyId={{keyId}}&nonce={{nonce}}\">click here to verify</a> the removal of your key.</p>"
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const util = require('./util');
|
const util = require('./util');
|
||||||
const tpl = require('../email/templates.json');
|
const tpl = require('../email/templates');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database documents have the format:
|
* Database documents have the format:
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const Email = require('../../src/email/email');
|
const Email = require('../../src/email/email');
|
||||||
const tpl = require('../../src/email/templates.json');
|
const tpl = require('../../src/email/templates');
|
||||||
|
|
||||||
describe('Email Integration Tests', function() {
|
describe('Email Integration Tests', function() {
|
||||||
this.timeout(20000);
|
this.timeout(20000);
|
||||||
@ -38,8 +38,8 @@ describe('Email Integration Tests', function() {
|
|||||||
describe("_sendHelper", () => {
|
describe("_sendHelper", () => {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const mailOptions = {
|
const mailOptions = {
|
||||||
from: email._sender,
|
from: {name: email._sender.name, address: email._sender.email},
|
||||||
to: recipient,
|
to: {name: recipient.name, address: recipient.email},
|
||||||
subject: 'Hello ✔', // Subject line
|
subject: 'Hello ✔', // Subject line
|
||||||
text: 'Hello world 🐴', // plaintext body
|
text: 'Hello world 🐴', // plaintext body
|
||||||
html: '<b>Hello world 🐴</b>' // html body
|
html: '<b>Hello world 🐴</b>' // html body
|
||||||
|
Loading…
Reference in New Issue
Block a user