diff --git a/package.json b/package.json index 99ad112..598c080 100644 --- a/package.json +++ b/package.json @@ -21,22 +21,24 @@ }, "dependencies": { "addressparser": "^1.0.1", - "co-body": "^5.1.1", - "config": "^1.20.4", + "co-body": "^6.0.0", + "config": "^3.0.1", + "ejs": "^2.6.1", + "email-templates": "^5.0.3", "koa": "^2.3.0", "koa-router": "^7.2.1", - "koa-static": "^4.0.1", - "mongodb": "^2.2.31", - "nodemailer": "^4.0.1", - "openpgp": "^2.3.0", - "winston": "^2.3.1", + "koa-static": "^5.0.0", + "mongodb": "^3.1.13", + "nodemailer": "^5.1.1", + "openpgp": "^4.4.6", + "winston": "^3.2.1", "winston-papertrail": "^1.0.5" }, "devDependencies": { "chai": "^4.1.1", - "eslint": "^4.4.1", - "mocha": "^3.2.0", - "sinon": "^1.17.4", + "eslint": "^5.13.0", + "mocha": "^5.2.0", + "sinon": "^7.2.3", "supertest": "^3.0.0" }, "greenkeeper": { diff --git a/src/dao/mongo.js b/src/dao/mongo.js index 8107a2d..4e4e2d9 100644 --- a/src/dao/mongo.js +++ b/src/dao/mongo.js @@ -34,7 +34,8 @@ class Mongo { async init({uri, user, pass}) { log.info('mongo', 'Connecting to MongoDB ...'); const url = `mongodb://${user}:${pass}@${uri}`; - this._db = await MongoClient.connect(url); + const client = await MongoClient.connect(url, {useNewUrlParser: true}); + this._db = client.db(); } /** diff --git a/src/dao/papertrail.js b/src/dao/papertrail.js index e41034c..7fc18cb 100644 --- a/src/dao/papertrail.js +++ b/src/dao/papertrail.js @@ -18,19 +18,36 @@ 'use strict'; const log = require('winston'); +const {SPLAT} = require('triple-beam'); const config = require('config'); require('winston-papertrail'); log.exitOnError = false; log.level = config.log.level; + +// Reformat logging text, due to deprecated logger usage +const formatLogs = log.format(info => { + info.message = `${info.message} -> ${info[SPLAT].join(', ')}`; + return info; +}); + exports.init = function({host, port}) { if (!host || !port) { + if (process.env.NODE_ENV !== 'production') { + log.add(new log.transports.Console({ + format: log.format.combine( + formatLogs(), + log.format.simple() + ) + })); + } return; } - log.add(log.transports.Papertrail, { + log.add(new log.transports.Papertrail({ + format: formatLogs(), level: config.log.level, host, port - }); + })); }; diff --git a/src/email/email.js b/src/email/email.js index 0cf4371..e6d7c7c 100644 --- a/src/email/email.js +++ b/src/email/email.js @@ -37,7 +37,7 @@ class Email { * @param {boolean} pgp (optional) if outgoing emails are encrypted to the user's public key. */ init({host, port = 465, auth, tls, starttls, pgp, sender}) { - this._transport = nodemailer.createTransport({ + const transporter = nodemailer.createTransport({ host, port, auth, diff --git a/src/email/templates/verifyKey/html.ejs b/src/email/templates/verifyKey/html.ejs new file mode 100644 index 0000000..2be890e --- /dev/null +++ b/src/email/templates/verifyKey/html.ejs @@ -0,0 +1,2 @@ +

Hello <%= name %>,

+

please click here to verify your key.

\ No newline at end of file diff --git a/src/email/templates/verifyKey/subject.ejs b/src/email/templates/verifyKey/subject.ejs new file mode 100644 index 0000000..3216cd9 --- /dev/null +++ b/src/email/templates/verifyKey/subject.ejs @@ -0,0 +1 @@ +Verify Your Key \ No newline at end of file diff --git a/src/email/templates/verifyRemove/html.ejs b/src/email/templates/verifyRemove/html.ejs new file mode 100644 index 0000000..5c1b86c --- /dev/null +++ b/src/email/templates/verifyRemove/html.ejs @@ -0,0 +1,2 @@ +

Hello <%= name %>,

+

please click here to verify the removal of your key.

\ No newline at end of file diff --git a/src/email/templates/verifyRemove/subject.ejs b/src/email/templates/verifyRemove/subject.ejs new file mode 100644 index 0000000..83f6089 --- /dev/null +++ b/src/email/templates/verifyRemove/subject.ejs @@ -0,0 +1 @@ +Verify Key Removal \ No newline at end of file diff --git a/src/service/pgp.js b/src/service/pgp.js index 3e525c9..25f1436 100644 --- a/src/service/pgp.js +++ b/src/service/pgp.js @@ -34,10 +34,10 @@ class PGP { * @param {String} publicKeyArmored ascii armored pgp key block * @return {Object} public key document to persist */ - parseKey(publicKeyArmored) { + async parseKey(publicKeyArmored) { publicKeyArmored = this.trimKey(publicKeyArmored); - const r = openpgp.key.readArmored(publicKeyArmored); + const r = await openpgp.key.readArmored(publicKeyArmored); if (r.err) { const error = r.err[0]; log.error('pgp', 'Failed to parse PGP key:\n%s', publicKeyArmored, error); @@ -49,23 +49,26 @@ class PGP { // verify primary key const key = r.keys[0]; const primaryKey = key.primaryKey; - if (key.verifyPrimaryKey() !== openpgp.enums.keyStatus.valid) { + if (await key.verifyPrimaryKey() !== openpgp.enums.keyStatus.valid) { util.throw(400, 'Invalid PGP key: primary key verification failed'); } // accept version 4 keys only const keyId = primaryKey.getKeyId().toHex(); - const fingerprint = primaryKey.fingerprint; + const fingerprint = primaryKey.getFingerprint(); if (!util.isKeyId(keyId) || !util.isFingerPrint(fingerprint)) { util.throw(400, 'Invalid PGP key: only v4 keys are accepted'); } // check for at least one valid user id - const userIds = this.parseUserIds(key.users, primaryKey); + const userIds = await this.parseUserIds(key.users, primaryKey); if (!userIds.length) { util.throw(400, 'Invalid PGP key: invalid user ids'); } + // get algorithm details from primary key + const keyInfo = key.primaryKey.getAlgorithmInfo(); + // public key document that is stored in the database return { keyId, @@ -73,8 +76,8 @@ class PGP { userIds, created: primaryKey.created, uploaded: new Date(), - algorithm: primaryKey.algorithm, - keySize: primaryKey.getBitSize(), + algorithm: keyInfo.algorithm, + keySize: keyInfo.bits, publicKeyArmored }; } @@ -110,20 +113,15 @@ class PGP { * @param {Array} users A list of openpgp.js user objects * @return {Array} An array of user id objects */ - parseUserIds(users, primaryKey) { + async parseUserIds(users, primaryKey) { if (!users || !users.length) { util.throw(400, 'Invalid PGP key: no user id found'); } - // at least one user id signature must be valid + // at least one user id must be valid, revoked or expired const result = []; for (const user of users) { - let oneValid = false; - for (const cert of user.selfCertifications) { - if (user.isValidSelfCertificate(primaryKey, cert)) { - oneValid = true; - } - } - if (oneValid && user.userId && user.userId.userid) { + const userStatus = await user.verify(primaryKey); + if (userStatus !== openpgp.enums.keyStatus.invalid && user.userId && user.userId.userid) { const uid = addressparser(user.userId.userid)[0]; if (util.isEmail(uid.address)) { result.push(uid); diff --git a/src/service/public-key.js b/src/service/public-key.js index a311081..ce91ca6 100644 --- a/src/service/public-key.js +++ b/src/service/public-key.js @@ -70,7 +70,7 @@ class PublicKey { // lazily purge old/unverified keys on every key upload await this._purgeOldUnverified(); // parse key block - const key = this._pgp.parseKey(publicKeyArmored); + const key = await this._pgp.parseKey(publicKeyArmored); // check for existing verified key with same id const verified = await this.getVerified({keyId: key.keyId}); if (verified) {