From 3b6fd47a61aa182b71c24d36d1a0c31985cfe75f Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 15 Apr 2016 22:27:45 -0700 Subject: [PATCH 1/4] Added new custom fields for GPG keys --- lib/models/fields.js | 14 ++++++++++++-- package.json | 1 + public/css/footer.css | 4 ++++ routes/lists.js | 21 +++++++++++++++++++++ views/lists/fields/create.hbs | 2 ++ views/lists/fields/edit.hbs | 2 ++ views/lists/subscription/add.hbs | 9 +++++++++ views/lists/subscription/edit.hbs | 9 +++++++++ views/subscription/manage.hbs | 9 +++++++++ views/subscription/subscribe.hbs | 9 +++++++++ 10 files changed, 78 insertions(+), 2 deletions(-) diff --git a/lib/models/fields.js b/lib/models/fields.js index 10857885..b9076439 100644 --- a/lib/models/fields.js +++ b/lib/models/fields.js @@ -12,6 +12,8 @@ module.exports.grouped = ['radio', 'checkbox', 'dropdown']; module.exports.types = { text: 'Text', website: 'Website', + longtext: 'Multi-line text', + gpg: 'GPG Public Key', number: 'Number', radio: 'Radio Buttons', checkbox: 'Checkboxes', @@ -26,6 +28,8 @@ module.exports.types = { module.exports.genericTypes = { text: 'string', website: 'string', + longtext: 'textarea', + gpg: 'textarea', number: 'number', 'date-us': 'date', 'date-eur': 'date', @@ -264,7 +268,7 @@ function addCustomField(listId, name, defaultValue, type, group, visible, callba let column = null; let key = slugify('merge ' + name, '_').toUpperCase(); - let allowedTypes = ['text', 'number', 'radio', 'checkbox', 'dropdown', 'date-us', 'date-eur', 'birthday-us', 'birthday-eur', 'website', 'option']; + let allowedTypes = ['text', 'number', 'radio', 'checkbox', 'dropdown', 'date-us', 'date-eur', 'birthday-us', 'birthday-eur', 'website', 'option', 'gpg', 'longtext']; if (allowedTypes.indexOf(type) < 0) { return callback(new Error('Unknown column type ' + type)); @@ -308,7 +312,11 @@ function addCustomField(listId, name, defaultValue, type, group, visible, callba switch (type) { case 'text': case 'website': - query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` VARCHAR(255) DEFAULT NULL'; + query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` VARCHAR(255) DEFAULT NULL, ADD INDEX (' + column + ')'; + break; + case 'gpg': + case 'longtext': + query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` TEXT DEFAULT NULL'; break; case 'number': query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` INT(11) DEFAULT NULL, ADD INDEX (' + column + ')'; @@ -358,6 +366,8 @@ module.exports.getRow = (fieldList, values, useDate, showAll) => { switch (field.type) { case 'text': case 'website': + case 'gpg': + case 'longtext': { let item = { type: field.type, diff --git a/package.json b/package.json index bb2b5ba0..c2edc989 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "mysql": "^2.10.2", "nodemailer": "^2.3.2", "npmlog": "^2.0.3", + "openpgp": "^2.2.1", "passport": "^0.3.2", "passport-local": "^1.0.0", "request": "^2.71.0", diff --git a/public/css/footer.css b/public/css/footer.css index bcf52f02..01f241aa 100644 --- a/public/css/footer.css +++ b/public/css/footer.css @@ -31,3 +31,7 @@ div.jumbotron{ .code-editor { height: 400px; } + +.gpg-text { + font-family: monospace; +} diff --git a/routes/lists.js b/routes/lists.js index 9e781fea..bb856834 100644 --- a/routes/lists.js +++ b/routes/lists.js @@ -1,5 +1,6 @@ 'use strict'; +let openpgp = require('openpgp'); let passport = require('../lib/passport'); let express = require('express'); let router = new express.Router(); @@ -156,6 +157,26 @@ router.post('/ajax/:id', (req, res) => { ].concat(fields.getRow(fieldList, row).map(cRow => { if (cRow.type === 'number') { return htmlescape(cRow.value && humanize.numberFormat(cRow.value, 0) || ''); + } else if (cRow.type === 'longtext') { + let value = (cRow.value || ''); + if (value.length > 50) { + value = value.substr(0, 47).trim() + '…'; + } + return htmlescape(value); + } else if (cRow.type === 'gpg') { + let value = (cRow.value || '').trim(); + try { + value = openpgp.key.readArmored(value); + if (value) { + value = value.keys && value.keys[0] && value.keys[0].primaryKey.fingerprint; + if (value) { + value = '0x' + value.substr(-16).toUpperCase(); + } + } + } catch (E) { + value = 'parse error'; + } + return htmlescape(value || ''); } else { return htmlescape(cRow.value || ''); } diff --git a/views/lists/fields/create.hbs b/views/lists/fields/create.hbs index d276fccc..5abd0a23 100644 --- a/views/lists/fields/create.hbs +++ b/views/lists/fields/create.hbs @@ -26,6 +26,8 @@ + + diff --git a/views/lists/fields/edit.hbs b/views/lists/fields/edit.hbs index 6dba82eb..bbb141e0 100644 --- a/views/lists/fields/edit.hbs +++ b/views/lists/fields/edit.hbs @@ -33,6 +33,8 @@ + + diff --git a/views/lists/subscription/add.hbs b/views/lists/subscription/add.hbs index 6b45acc3..5ab8aac1 100644 --- a/views/lists/subscription/add.hbs +++ b/views/lists/subscription/add.hbs @@ -50,6 +50,15 @@ {{/if}} + {{#if typeLongtext}} + + {{/if}} + + {{#if typeGpg}} + + Insert a GPG public key that will be used to encrypt messages sent this subscriber + {{/if}} + {{#if typeDateUs}}
diff --git a/views/lists/subscription/edit.hbs b/views/lists/subscription/edit.hbs index d22e9f50..b3cd5e97 100644 --- a/views/lists/subscription/edit.hbs +++ b/views/lists/subscription/edit.hbs @@ -63,6 +63,15 @@ {{/if}} + {{#if typeLongtext}} + + {{/if}} + + {{#if typeGpg}} + + Insert a GPG public key that will be used to encrypt messages sent this subscriber + {{/if}} + {{#if typeDateUs}}
diff --git a/views/subscription/manage.hbs b/views/subscription/manage.hbs index 9f6509aa..996af8ed 100644 --- a/views/subscription/manage.hbs +++ b/views/subscription/manage.hbs @@ -36,6 +36,15 @@ {{/if}} + {{#if typeLongtext}} + + {{/if}} + + {{#if typeGpg}} + + Insert your GPG public key here to encrypt messages sent to your address + {{/if}} + {{#if typeDateUs}}
diff --git a/views/subscription/subscribe.hbs b/views/subscription/subscribe.hbs index 8ed4e151..b8129989 100644 --- a/views/subscription/subscribe.hbs +++ b/views/subscription/subscribe.hbs @@ -33,6 +33,15 @@ {{/if}} + {{#if typeLongtext}} + + {{/if}} + + {{#if typeGpg}} + + Insert your GPG public key here to encrypt messages sent to your address + {{/if}} + {{#if typeDateUs}}
From 0f1bc6ab72bd5d7c3c0ac6c2817b0c23f7660b4e Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 15 Apr 2016 22:33:37 -0700 Subject: [PATCH 2/4] Add option to filter based on gpg keys --- views/lists/segments/rule-configure.hbs | 12 ++++++++++++ views/lists/segments/rule-edit.hbs | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/views/lists/segments/rule-configure.hbs b/views/lists/segments/rule-configure.hbs index e8509374..b2a364c7 100644 --- a/views/lists/segments/rule-configure.hbs +++ b/views/lists/segments/rule-configure.hbs @@ -32,6 +32,18 @@
{{/if}} + {{#if columnTypeTextarea}} +
+
+
+ +
+
+
+ {{/if}} + {{#if columnTypeNumber}}
diff --git a/views/lists/segments/rule-edit.hbs b/views/lists/segments/rule-edit.hbs index 6d84ee35..99cb604c 100644 --- a/views/lists/segments/rule-edit.hbs +++ b/views/lists/segments/rule-edit.hbs @@ -37,6 +37,18 @@
{{/if}} + {{#if columnTypeTextarea}} +
+
+
+ +
+
+
+ {{/if}} + {{#if columnTypeNumber}}
From ac8ac7c6d642c69604492636a514ee6801094e56 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sat, 16 Apr 2016 10:11:10 -0700 Subject: [PATCH 3/4] Send encrypted messages to users with GPG key set --- lib/mailer.js | 2 ++ lib/models/segments.js | 3 +++ package.json | 3 ++- routes/segments.js | 2 ++ services/sender.js | 9 ++++++++- views/lists/segments/rule-configure.hbs | 12 ------------ views/lists/segments/rule-edit.hbs | 12 ------------ 7 files changed, 17 insertions(+), 26 deletions(-) diff --git a/lib/mailer.js b/lib/mailer.js index dce34cb4..28a5c32c 100644 --- a/lib/mailer.js +++ b/lib/mailer.js @@ -3,6 +3,7 @@ let log = require('npmlog'); let nodemailer = require('nodemailer'); +let openpgpEncrypt = require('nodemailer-openpgp').openpgpEncrypt; let settings = require('./models/settings'); let Handlebars = require('handlebars'); let fs = require('fs'); @@ -108,6 +109,7 @@ function createMailer(callback) { rejectUnauthorized: !configItems.smtpSelfSigned } }); + module.exports.transport.use('stream', openpgpEncrypt()); return callback(null, module.exports.transport); }); diff --git a/lib/models/segments.js b/lib/models/segments.js index edd28e17..63025fb8 100644 --- a/lib/models/segments.js +++ b/lib/models/segments.js @@ -99,6 +99,9 @@ module.exports.get = (id, callback) => { segment.columns = [].concat(module.exports.defaultColumns); fieldList.forEach(field => { + if (fields.genericTypes[field.type] === 'textarea') { + return; + } if (field.column) { segment.columns.push({ column: field.column, diff --git a/package.json b/package.json index c2edc989..536cb510 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "grunt": "^1.0.1", "grunt-cli": "^1.2.0", "grunt-contrib-nodeunit": "^1.0.0", - "grunt-eslint": "^18.0.0" + "grunt-eslint": "^18.1.0" }, "dependencies": { "bcrypt-nodejs": "0.0.3", @@ -47,6 +47,7 @@ "multer": "^1.1.0", "mysql": "^2.10.2", "nodemailer": "^2.3.2", + "nodemailer-openpgp": "^1.0.1", "npmlog": "^2.0.3", "openpgp": "^2.2.1", "passport": "^0.3.2", diff --git a/routes/segments.js b/routes/segments.js index 8ce723a5..f9c5d48d 100644 --- a/routes/segments.js +++ b/routes/segments.js @@ -235,6 +235,8 @@ router.get('/:list/rules/:segment/create', passport.csrfProtection, (req, res) = } segment.csrfToken = req.csrfToken(); + + console.log(segment); segment.list = list; res.render('lists/segments/rule-create', segment); diff --git a/services/sender.js b/services/sender.js index 3388a80e..289a3956 100644 --- a/services/sender.js +++ b/services/sender.js @@ -130,10 +130,16 @@ function formatMessage(message, callback) { FULL_NAME: [].concat(message.subscription.firstName || []).concat(message.subscription.lastName || []).join(' ') }; + let encryptionKeys = []; fields.getRow(fieldList, message.subscription, true, true).forEach(field => { if (field.mergeTag) { message.subscription.mergeTags[field.mergeTag] = field.mergeValue || ''; } + + if (field.type === 'gpg' && field.value) { + encryptionKeys.push(field.value.trim()); + } + if (field.options) { field.options.forEach(subField => { if (subField.mergeTag) { @@ -206,7 +212,8 @@ function formatMessage(message, callback) { html: tools.formatMessage(configItems.serviceUrl, campaign, list, message.subscription, html), text: tools.formatMessage(configItems.serviceUrl, campaign, list, message.subscription, campaign.text), - attachments + attachments, + encryptionKeys }); }); }); diff --git a/views/lists/segments/rule-configure.hbs b/views/lists/segments/rule-configure.hbs index b2a364c7..e8509374 100644 --- a/views/lists/segments/rule-configure.hbs +++ b/views/lists/segments/rule-configure.hbs @@ -32,18 +32,6 @@
{{/if}} - {{#if columnTypeTextarea}} -
-
-
- -
-
-
- {{/if}} - {{#if columnTypeNumber}}
diff --git a/views/lists/segments/rule-edit.hbs b/views/lists/segments/rule-edit.hbs index 99cb604c..6d84ee35 100644 --- a/views/lists/segments/rule-edit.hbs +++ b/views/lists/segments/rule-edit.hbs @@ -37,18 +37,6 @@
{{/if}} - {{#if columnTypeTextarea}} -
-
-
- -
-
-
- {{/if}} - {{#if columnTypeNumber}}
From db2a7f8aff9e7cbbf8e8279366b4ae064b90aae4 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sat, 16 Apr 2016 10:51:22 -0700 Subject: [PATCH 4/4] sign gpg messages --- lib/mailer.js | 7 +++++-- package.json | 2 +- routes/settings.js | 2 +- views/settings.hbs | 31 +++++++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/lib/mailer.js b/lib/mailer.js index 28a5c32c..30ff5aef 100644 --- a/lib/mailer.js +++ b/lib/mailer.js @@ -83,7 +83,7 @@ function getTemplate(template, callback) { } function createMailer(callback) { - settings.list(['smtpHostname', 'smtpPort', 'smtpEncryption', 'smtpUser', 'smtpPass', 'smtpLog', 'smtpDisableAuth', 'smtpMaxConnections', 'smtpMaxMessages', 'smtpSelfSigned'], (err, configItems) => { + settings.list(['smtpHostname', 'smtpPort', 'smtpEncryption', 'smtpUser', 'smtpPass', 'smtpLog', 'smtpDisableAuth', 'smtpMaxConnections', 'smtpMaxMessages', 'smtpSelfSigned', 'pgpPrivateKey', 'pgpPassphrase'], (err, configItems) => { if (err) { return callback(err); } @@ -109,7 +109,10 @@ function createMailer(callback) { rejectUnauthorized: !configItems.smtpSelfSigned } }); - module.exports.transport.use('stream', openpgpEncrypt()); + module.exports.transport.use('stream', openpgpEncrypt({ + signingKey: configItems.pgpPrivateKey, + passphrase: configItems.pgpPassphrase + })); return callback(null, module.exports.transport); }); diff --git a/package.json b/package.json index 536cb510..e2a90037 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "multer": "^1.1.0", "mysql": "^2.10.2", "nodemailer": "^2.3.2", - "nodemailer-openpgp": "^1.0.1", + "nodemailer-openpgp": "^1.0.2", "npmlog": "^2.0.3", "openpgp": "^2.2.1", "passport": "^0.3.2", diff --git a/routes/settings.js b/routes/settings.js index d9a581bd..faf5c6dd 100644 --- a/routes/settings.js +++ b/routes/settings.js @@ -11,7 +11,7 @@ let url = require('url'); let settings = require('../lib/models/settings'); -let allowedKeys = ['service_url', 'smtp_hostname', 'smtp_port', 'smtp_encryption', 'smtp_disable_auth', 'smtp_user', 'smtp_pass', 'admin_email', 'smtp_log', 'smtp_max_connections', 'smtp_max_messages', 'smtp_self_signed', 'default_from', 'default_address', 'default_subject', 'default_homepage', 'default_postaddress', 'default_sender', 'verp_hostname', 'verp_use', 'disable_wysiwyg']; +let allowedKeys = ['service_url', 'smtp_hostname', 'smtp_port', 'smtp_encryption', 'smtp_disable_auth', 'smtp_user', 'smtp_pass', 'admin_email', 'smtp_log', 'smtp_max_connections', 'smtp_max_messages', 'smtp_self_signed', 'default_from', 'default_address', 'default_subject', 'default_homepage', 'default_postaddress', 'default_sender', 'verp_hostname', 'verp_use', 'disable_wysiwyg', 'pgp_private_key', 'pgp_passphrase']; router.all('/*', (req, res, next) => { if (!req.user) { diff --git a/views/settings.hbs b/views/settings.hbs index 31341890..7fd02c40 100644 --- a/views/settings.hbs +++ b/views/settings.hbs @@ -251,6 +251,37 @@ {{/if}} +
+ + PGP Signing + + +

+ Only messages that are encrypted can be signed. Subsribers who have not set up a PGP public key in their profile receive normal email messages. Users with PGP key set receive encrypted messages and if you have signing key also set, the messages are signed + with this key. +

+

+ Do not use sensitive keys here. The private key and passphrase are not encrypted in the database. +

+ +
+ +
+ + Only fill this if your private key is encrypted with a passphrase +
+
+ +
+ +
+ + This value is optional. if you do not provide a private key, then PGP encrypted messages are sent without signing. +
+
+ +
+