Merge pull request #40 from mailvelope/dev/async-await

Dev/async await
This commit is contained in:
Tankred Hase 2017-08-17 17:46:49 +08:00 committed by GitHub
commit 8c76281666
15 changed files with 299 additions and 299 deletions

View File

@ -1,7 +1,7 @@
{ {
"extends": "eslint:recommended", "extends": "eslint:recommended",
"parserOptions": { "parserOptions": {
"ecmaVersion": 6 "ecmaVersion": 2017
}, },
"env": { "env": {
"node": true, "node": true,
@ -41,7 +41,11 @@
"semi": ["warn", "always"], // require or disallow semicolons instead of ASI "semi": ["warn", "always"], // require or disallow semicolons instead of ASI
"semi-spacing": 1, // enforce consistent spacing before and after semicolons "semi-spacing": 1, // enforce consistent spacing before and after semicolons
"space-before-blocks": 1, // enforce consistent spacing before blocks "space-before-blocks": 1, // enforce consistent spacing before blocks
"space-before-function-paren": ["warn", "never"], // enforce consistent spacing before function definition opening parenthesis "space-before-function-paren": ["warn", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}], // enforce consistent spacing before function definition opening parenthesis
"space-in-parens": ["warn", "never"], // enforce consistent spacing inside parentheses "space-in-parens": ["warn", "never"], // enforce consistent spacing inside parentheses
"space-infix-ops": 1, // require spacing around operators "space-infix-ops": 1, // require spacing around operators
/* ES6 */ /* ES6 */

View File

@ -21,12 +21,11 @@
}, },
"dependencies": { "dependencies": {
"addressparser": "^1.0.1", "addressparser": "^1.0.1",
"co": "^4.6.0",
"co-body": "^5.1.1",
"config": "^1.20.4", "config": "^1.20.4",
"koa": "^1.2.0", "koa": "^2.3.0",
"koa-router": "^5.4.0", "koa-body": "^2.3.0",
"koa-static": "^2.0.0", "koa-router": "^7.2.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", "nodemailer-openpgp": "^1.0.2",
@ -35,7 +34,6 @@
}, },
"devDependencies": { "devDependencies": {
"chai": "^4.1.1", "chai": "^4.1.1",
"co-mocha": "^1.1.2",
"eslint": "^4.4.1", "eslint": "^4.4.1",
"mocha": "^3.2.0", "mocha": "^3.2.0",
"sinon": "^1.17.4", "sinon": "^1.17.4",

View File

@ -17,8 +17,8 @@
'use strict'; 'use strict';
const co = require('co'); const Koa = require('koa');
const app = require('koa')(); const koaBody = require('koa-body');
const log = require('npmlog'); const log = require('npmlog');
const config = require('config'); const config = require('config');
const serve = require('koa-static'); const serve = require('koa-static');
@ -31,6 +31,8 @@ const PublicKey = require('./service/public-key');
const HKP = require('./route/hkp'); const HKP = require('./route/hkp');
const REST = require('./route/rest'); const REST = require('./route/rest');
const app = new Koa();
let mongo; let mongo;
let email; let email;
let pgp; let pgp;
@ -43,55 +45,50 @@ let rest;
// //
// HKP routes // HKP routes
router.post('/pks/add', function *() { router.post('/pks/add', ctx => hkp.add(ctx));
yield hkp.add(this); router.get('/pks/lookup', ctx => hkp.lookup(ctx));
});
router.get('/pks/lookup', function *() {
yield hkp.lookup(this);
});
// REST api routes // REST api routes
router.post('/api/v1/key', function *() { router.post('/api/v1/key', ctx => rest.create(ctx));
yield rest.create(this); router.get('/api/v1/key', ctx => rest.query(ctx));
}); router.del('/api/v1/key', ctx => rest.remove(ctx));
router.get('/api/v1/key', function *() {
yield rest.query(this);
});
router.del('/api/v1/key', function *() {
yield rest.remove(this);
});
// Redirect all http traffic to https // Redirect all http traffic to https
app.use(function *(next) { app.use(async (ctx, next) => {
if (util.isTrue(config.server.httpsUpgrade) && util.checkHTTP(this)) { if (util.isTrue(config.server.httpsUpgrade) && util.checkHTTP(ctx)) {
this.redirect(`https://${this.hostname}${this.url}`); ctx.redirect(`https://${ctx.hostname}${ctx.url}`);
} else { } else {
yield next; await next();
} }
}); });
// Set HTTP response headers // Set HTTP response headers
app.use(function *(next) { app.use(async (ctx, next) => {
// HSTS // HSTS
if (util.isTrue(config.server.httpsUpgrade)) { if (util.isTrue(config.server.httpsUpgrade)) {
this.set('Strict-Transport-Security', 'max-age=16070400'); ctx.set('Strict-Transport-Security', 'max-age=16070400');
} }
// HPKP // HPKP
if (config.server.httpsKeyPin && config.server.httpsKeyPinBackup) { if (config.server.httpsKeyPin && config.server.httpsKeyPinBackup) {
this.set('Public-Key-Pins', `pin-sha256="${config.server.httpsKeyPin}"; pin-sha256="${config.server.httpsKeyPinBackup}"; max-age=16070400`); ctx.set('Public-Key-Pins', `pin-sha256="${config.server.httpsKeyPin}"; pin-sha256="${config.server.httpsKeyPinBackup}"; max-age=16070400`);
} }
// CSP // CSP
this.set('Content-Security-Policy', "default-src 'self'; object-src 'none'; script-src 'self' code.jquery.com; style-src 'self' maxcdn.bootstrapcdn.com; font-src 'self' maxcdn.bootstrapcdn.com"); ctx.set('Content-Security-Policy', "default-src 'self'; object-src 'none'; script-src 'self' code.jquery.com; style-src 'self' maxcdn.bootstrapcdn.com; font-src 'self' maxcdn.bootstrapcdn.com");
// Prevent rendering website in foreign iframe (Clickjacking) // Prevent rendering website in foreign iframe (Clickjacking)
this.set('X-Frame-Options', 'DENY'); ctx.set('X-Frame-Options', 'DENY');
// CORS // CORS
this.set('Access-Control-Allow-Origin', '*'); ctx.set('Access-Control-Allow-Origin', '*');
this.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
this.set('Access-Control-Allow-Headers', 'Content-Type'); ctx.set('Access-Control-Allow-Headers', 'Content-Type');
this.set('Connection', 'keep-alive'); ctx.set('Connection', 'keep-alive');
yield next; await next();
}); });
app.use(koaBody({
multipart: true,
formLimit: '1mb'
}));
app.use(router.routes()); app.use(router.routes());
app.use(router.allowedMethods()); app.use(router.allowedMethods());
@ -124,19 +121,23 @@ function injectDependencies() {
// //
if (!global.testing) { // don't automatically start server in tests if (!global.testing) { // don't automatically start server in tests
co(function *() { (async () => {
const app = yield init(); try {
app.listen(config.server.port); const app = await init();
log.info('app', `Ready to rock! Listening on http://localhost:${config.server.port}`); app.listen(config.server.port);
}).catch(err => log.error('app', 'Initialization failed!', err)); log.info('app', `Ready to rock! Listening on http://localhost:${config.server.port}`);
} catch (err) {
log.error('app', 'Initialization failed!', err);
}
})();
} }
function *init() { async function init() {
log.level = config.log.level; // set log level depending on process.env.NODE_ENV log.level = config.log.level; // set log level depending on process.env.NODE_ENV
injectDependencies(); injectDependencies();
email.init(config.email); email.init(config.email);
log.info('app', 'Connecting to MongoDB ...'); log.info('app', 'Connecting to MongoDB ...');
yield mongo.init(config.mongo); await mongo.init(config.mongo);
return app; return app;
} }

View File

@ -30,9 +30,9 @@ class Mongo {
* @param {String} pass The database user's password * @param {String} pass The database user's password
* @yield {undefined} * @yield {undefined}
*/ */
*init({uri, user, pass}) { async init({uri, user, pass}) {
const url = `mongodb://${user}:${pass}@${uri}`; const url = `mongodb://${user}:${pass}@${uri}`;
this._db = yield MongoClient.connect(url); this._db = await MongoClient.connect(url);
} }
/** /**

View File

@ -58,7 +58,7 @@ class Email {
* @param {Object} origin origin of the server * @param {Object} origin origin of the server
* @yield {Object} send response from the SMTP server * @yield {Object} send response from the SMTP server
*/ */
*send({template, userId, keyId, origin}) { async send({template, userId, keyId, origin}) {
const message = { const message = {
from: this._sender, from: this._sender,
to: userId, to: userId,
@ -72,7 +72,7 @@ class Email {
nonce: userId.nonce nonce: userId.nonce
} }
}; };
return yield this._sendHelper(message); return this._sendHelper(message);
} }
/** /**
@ -85,7 +85,7 @@ class Email {
* @param {Object} params (optional) nodermailer template parameters * @param {Object} params (optional) nodermailer template parameters
* @yield {Object} reponse object containing SMTP info * @yield {Object} reponse object containing SMTP info
*/ */
*_sendHelper({from, to, subject, text, html, params = {}}) { async _sendHelper({from, to, subject, text, html, params = {}}) {
const template = { const template = {
subject, subject,
text, text,
@ -107,7 +107,7 @@ class Email {
try { try {
const sendFn = this._transport.templateSender(template, sender); const sendFn = this._transport.templateSender(template, sender);
const info = yield sendFn(recipient, params); 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);
} }

View File

@ -17,7 +17,6 @@
'use strict'; 'use strict';
const parse = require('co-body');
const util = require('../service/util'); const util = require('../service/util');
/** /**
@ -37,13 +36,13 @@ class HKP {
* Public key upload via http POST * Public key upload via http POST
* @param {Object} ctx The koa request/response context * @param {Object} ctx The koa request/response context
*/ */
*add(ctx) { async add(ctx) {
const {keytext: publicKeyArmored} = yield parse.form(ctx, {limit: '1mb'}); const publicKeyArmored = ctx.request.body.keytext;
if (!publicKeyArmored) { if (!publicKeyArmored) {
ctx.throw(400, 'Invalid request!'); ctx.throw(400, 'Invalid request!');
} }
const origin = util.origin(ctx); const origin = util.origin(ctx);
yield this._publicKey.put({publicKeyArmored, origin}); await this._publicKey.put({publicKeyArmored, origin});
ctx.body = 'Upload successful. Check your inbox to verify your email address.'; ctx.body = 'Upload successful. Check your inbox to verify your email address.';
ctx.status = 201; ctx.status = 201;
} }
@ -52,9 +51,9 @@ class HKP {
* Public key lookup via http GET * Public key lookup via http GET
* @param {Object} ctx The koa request/response context * @param {Object} ctx The koa request/response context
*/ */
*lookup(ctx) { async lookup(ctx) {
const params = this.parseQueryString(ctx); const params = this.parseQueryString(ctx);
const key = yield this._publicKey.get(params); const key = await this._publicKey.get(params);
this.setGetHeaders(ctx, params); this.setGetHeaders(ctx, params);
this.setGetBody(ctx, params, key); this.setGetBody(ctx, params, key);
} }

View File

@ -17,7 +17,6 @@
'use strict'; 'use strict';
const parse = require('co-body');
const util = require('../service/util'); const util = require('../service/util');
/** /**
@ -37,13 +36,13 @@ class REST {
* Public key upload via http POST * Public key upload via http POST
* @param {Object} ctx The koa request/response context * @param {Object} ctx The koa request/response context
*/ */
*create(ctx) { async create(ctx) {
const {publicKeyArmored, primaryEmail} = yield parse.json(ctx, {limit: '1mb'}); const {publicKeyArmored, primaryEmail} = ctx.request.body;
if (!publicKeyArmored || (primaryEmail && !util.isEmail(primaryEmail))) { if (!publicKeyArmored || (primaryEmail && !util.isEmail(primaryEmail))) {
ctx.throw(400, 'Invalid request!'); ctx.throw(400, 'Invalid request!');
} }
const origin = util.origin(ctx); const origin = util.origin(ctx);
yield this._publicKey.put({publicKeyArmored, primaryEmail, origin}); await this._publicKey.put({publicKeyArmored, primaryEmail, origin});
ctx.body = 'Upload successful. Check your inbox to verify your email address.'; ctx.body = 'Upload successful. Check your inbox to verify your email address.';
ctx.status = 201; ctx.status = 201;
} }
@ -52,29 +51,29 @@ class REST {
* Public key query via http GET * Public key query via http GET
* @param {Object} ctx The koa request/response context * @param {Object} ctx The koa request/response context
*/ */
*query(ctx) { async query(ctx) {
const op = ctx.query.op; const op = ctx.query.op;
if (op === 'verify' || op === 'verifyRemove') { if (op === 'verify' || op === 'verifyRemove') {
return yield this[op](ctx); // delegate operation return this[op](ctx); // delegate operation
} }
// do READ if no 'op' provided // do READ if no 'op' provided
const q = {keyId: ctx.query.keyId, fingerprint: ctx.query.fingerprint, email: ctx.query.email}; const q = {keyId: ctx.query.keyId, fingerprint: ctx.query.fingerprint, email: ctx.query.email};
if (!util.isKeyId(q.keyId) && !util.isFingerPrint(q.fingerprint) && !util.isEmail(q.email)) { if (!util.isKeyId(q.keyId) && !util.isFingerPrint(q.fingerprint) && !util.isEmail(q.email)) {
ctx.throw(400, 'Invalid request!'); ctx.throw(400, 'Invalid request!');
} }
ctx.body = yield this._publicKey.get(q); ctx.body = await this._publicKey.get(q);
} }
/** /**
* Verify a public key's user id via http GET * Verify a public key's user id via http GET
* @param {Object} ctx The koa request/response context * @param {Object} ctx The koa request/response context
*/ */
*verify(ctx) { async verify(ctx) {
const q = {keyId: ctx.query.keyId, nonce: ctx.query.nonce}; const q = {keyId: ctx.query.keyId, nonce: ctx.query.nonce};
if (!util.isKeyId(q.keyId) || !util.isString(q.nonce)) { if (!util.isKeyId(q.keyId) || !util.isString(q.nonce)) {
ctx.throw(400, 'Invalid request!'); ctx.throw(400, 'Invalid request!');
} }
yield this._publicKey.verify(q); await this._publicKey.verify(q);
// create link for sharing // create link for sharing
const link = util.url(util.origin(ctx), `/pks/lookup?op=get&search=0x${q.keyId.toUpperCase()}`); const link = util.url(util.origin(ctx), `/pks/lookup?op=get&search=0x${q.keyId.toUpperCase()}`);
ctx.body = `<p>Email address successfully verified!</p><p>Link to share your key: <a href="${link}" target="_blank">${link}</a></p>`; ctx.body = `<p>Email address successfully verified!</p><p>Link to share your key: <a href="${link}" target="_blank">${link}</a></p>`;
@ -85,12 +84,12 @@ class REST {
* Request public key removal via http DELETE * Request public key removal via http DELETE
* @param {Object} ctx The koa request/response context * @param {Object} ctx The koa request/response context
*/ */
*remove(ctx) { async remove(ctx) {
const q = {keyId: ctx.query.keyId, email: ctx.query.email, origin: util.origin(ctx)}; const q = {keyId: ctx.query.keyId, email: ctx.query.email, origin: util.origin(ctx)};
if (!util.isKeyId(q.keyId) && !util.isEmail(q.email)) { if (!util.isKeyId(q.keyId) && !util.isEmail(q.email)) {
ctx.throw(400, 'Invalid request!'); ctx.throw(400, 'Invalid request!');
} }
yield this._publicKey.requestRemove(q); await this._publicKey.requestRemove(q);
ctx.body = 'Check your inbox to verify the removal of your key.'; ctx.body = 'Check your inbox to verify the removal of your key.';
ctx.status = 202; ctx.status = 202;
} }
@ -99,12 +98,12 @@ class REST {
* Verify public key removal via http GET * Verify public key removal via http GET
* @param {Object} ctx The koa request/response context * @param {Object} ctx The koa request/response context
*/ */
*verifyRemove(ctx) { async verifyRemove(ctx) {
const q = {keyId: ctx.query.keyId, nonce: ctx.query.nonce}; const q = {keyId: ctx.query.keyId, nonce: ctx.query.nonce};
if (!util.isKeyId(q.keyId) || !util.isString(q.nonce)) { if (!util.isKeyId(q.keyId) || !util.isString(q.nonce)) {
ctx.throw(400, 'Invalid request!'); ctx.throw(400, 'Invalid request!');
} }
yield this._publicKey.verifyRemove(q); await this._publicKey.verifyRemove(q);
ctx.body = 'Key successfully removed!'; ctx.body = 'Key successfully removed!';
} }
} }

View File

@ -65,18 +65,18 @@ class PublicKey {
* @param {Object} origin Required for links to the keyserver e.g. { protocol:'https', host:'openpgpkeys@example.com' } * @param {Object} origin Required for links to the keyserver e.g. { protocol:'https', host:'openpgpkeys@example.com' }
* @yield {undefined} * @yield {undefined}
*/ */
*put({publicKeyArmored, primaryEmail, origin}) { async put({publicKeyArmored, primaryEmail, origin}) {
// parse key block // parse key block
const key = this._pgp.parseKey(publicKeyArmored); const key = this._pgp.parseKey(publicKeyArmored);
// check for existing verfied key by id or email addresses // check for existing verfied key by id or email addresses
const verified = yield this.getVerified(key); const verified = await this.getVerified(key);
if (verified) { if (verified) {
util.throw(304, 'Key for this user already exists'); util.throw(304, 'Key for this user already exists');
} }
// store key in database // store key in database
yield this._persisKey(key); await this._persisKey(key);
// send mails to verify user ids (send only one if primary email is provided) // send mails to verify user ids (send only one if primary email is provided)
yield this._sendVerifyEmail(key, primaryEmail, origin); await this._sendVerifyEmail(key, primaryEmail, origin);
} }
/** /**
@ -84,15 +84,15 @@ class PublicKey {
* @param {Object} key public key parameters * @param {Object} key public key parameters
* @yield {undefined} The persisted user id documents * @yield {undefined} The persisted user id documents
*/ */
*_persisKey(key) { async _persisKey(key) {
// delete old/unverified key // delete old/unverified key
yield this._mongo.remove({keyId: key.keyId}, DB_TYPE); await this._mongo.remove({keyId: key.keyId}, DB_TYPE);
// generate nonces for verification // generate nonces for verification
for (const uid of key.userIds) { for (const uid of key.userIds) {
uid.nonce = util.random(); uid.nonce = util.random();
} }
// persist new key // persist new key
const r = yield this._mongo.create(key, DB_TYPE); const r = await this._mongo.create(key, DB_TYPE);
if (r.insertedCount !== 1) { if (r.insertedCount !== 1) {
util.throw(500, 'Failed to persist key'); util.throw(500, 'Failed to persist key');
} }
@ -106,7 +106,7 @@ class PublicKey {
* @param {Object} origin the server's origin (required for email links) * @param {Object} origin the server's origin (required for email links)
* @yield {undefined} * @yield {undefined}
*/ */
*_sendVerifyEmail({userIds, keyId, publicKeyArmored}, primaryEmail, origin) { async _sendVerifyEmail({userIds, keyId, publicKeyArmored}, primaryEmail, origin) {
// check for primary email (send only one email) // check for primary email (send only one email)
const primaryUserId = userIds.find(uid => uid.email === primaryEmail); const primaryUserId = userIds.find(uid => uid.email === primaryEmail);
if (primaryUserId) { if (primaryUserId) {
@ -115,7 +115,7 @@ class PublicKey {
// send emails // send emails
for (const userId of userIds) { for (const userId of userIds) {
userId.publicKeyArmored = publicKeyArmored; // set key for encryption userId.publicKeyArmored = publicKeyArmored; // set key for encryption
yield this._email.send({template: tpl.verifyKey, userId, keyId, origin}); await this._email.send({template: tpl.verifyKey, userId, keyId, origin});
} }
} }
@ -125,20 +125,20 @@ class PublicKey {
* @param {string} nonce The verification nonce proving email address ownership * @param {string} nonce The verification nonce proving email address ownership
* @yield {undefined} * @yield {undefined}
*/ */
*verify({keyId, nonce}) { async verify({keyId, nonce}) {
// look for verification nonce in database // look for verification nonce in database
const query = {keyId, 'userIds.nonce': nonce}; const query = {keyId, 'userIds.nonce': nonce};
const key = yield this._mongo.get(query, DB_TYPE); const key = await this._mongo.get(query, DB_TYPE);
if (!key) { if (!key) {
util.throw(404, 'User id not found'); util.throw(404, 'User id not found');
} }
// check if user ids of this key have already been verified in another key // check if user ids of this key have already been verified in another key
const verified = yield this.getVerified(key); const verified = await this.getVerified(key);
if (verified && verified.keyId !== keyId) { if (verified && verified.keyId !== keyId) {
util.throw(304, 'Key for this user already exists'); util.throw(304, 'Key for this user already exists');
} }
// flag the user id as verified // flag the user id as verified
yield this._mongo.update(query, { await this._mongo.update(query, {
'userIds.$.verified': true, 'userIds.$.verified': true,
'userIds.$.nonce': null 'userIds.$.nonce': null
}, DB_TYPE); }, DB_TYPE);
@ -153,7 +153,7 @@ class PublicKey {
* @param {string} keyId (optional) The public key id * @param {string} keyId (optional) The public key id
* @yield {Object} The verified key document * @yield {Object} The verified key document
*/ */
*getVerified({userIds, fingerprint, keyId}) { async getVerified({userIds, fingerprint, keyId}) {
let queries = []; let queries = [];
// query by fingerprint // query by fingerprint
if (fingerprint) { if (fingerprint) {
@ -180,7 +180,7 @@ class PublicKey {
} }
}))); })));
} }
return yield this._mongo.get({$or: queries}, DB_TYPE); return this._mongo.get({$or: queries}, DB_TYPE);
} }
/** /**
@ -191,10 +191,10 @@ class PublicKey {
* @param {String} email (optional) The user's email address * @param {String} email (optional) The user's email address
* @yield {Object} The public key document * @yield {Object} The public key document
*/ */
*get({fingerprint, keyId, email}) { async get({fingerprint, keyId, email}) {
// look for verified key // look for verified key
const userIds = email ? [{email}] : undefined; const userIds = email ? [{email}] : undefined;
const key = yield this.getVerified({keyId, fingerprint, userIds}); const key = await this.getVerified({keyId, fingerprint, userIds});
if (!key) { if (!key) {
util.throw(404, 'Key not found'); util.throw(404, 'Key not found');
} }
@ -218,16 +218,16 @@ class PublicKey {
* @param {Object} origin Required for links to the keyserver e.g. { protocol:'https', host:'openpgpkeys@example.com' } * @param {Object} origin Required for links to the keyserver e.g. { protocol:'https', host:'openpgpkeys@example.com' }
* @yield {undefined} * @yield {undefined}
*/ */
*requestRemove({keyId, email, origin}) { async requestRemove({keyId, email, origin}) {
// flag user ids for removal // flag user ids for removal
const key = yield this._flagForRemove(keyId, email); const key = await this._flagForRemove(keyId, email);
if (!key) { if (!key) {
util.throw(404, 'User id not found'); util.throw(404, 'User id not found');
} }
// send verification mails // send verification mails
keyId = key.keyId; // get keyId in case request was by email keyId = key.keyId; // get keyId in case request was by email
for (const userId of key.userIds) { for (const userId of key.userIds) {
yield this._email.send({template: tpl.verifyRemove, userId, keyId, origin}); await this._email.send({template: tpl.verifyRemove, userId, keyId, origin});
} }
} }
@ -238,16 +238,16 @@ class PublicKey {
* @param {String} email (optional) The user's email address * @param {String} email (optional) The user's email address
* @yield {Array} A list of user ids with nonces * @yield {Array} A list of user ids with nonces
*/ */
*_flagForRemove(keyId, email) { async _flagForRemove(keyId, email) {
const query = email ? {'userIds.email': email} : {keyId}; const query = email ? {'userIds.email': email} : {keyId};
const key = yield this._mongo.get(query, DB_TYPE); const key = await this._mongo.get(query, DB_TYPE);
if (!key) { if (!key) {
return; return;
} }
// flag only the provided user id // flag only the provided user id
if (email) { if (email) {
const nonce = util.random(); const nonce = util.random();
yield this._mongo.update(query, {'userIds.$.nonce': nonce}, DB_TYPE); await this._mongo.update(query, {'userIds.$.nonce': nonce}, DB_TYPE);
const uid = key.userIds.find(u => u.email === email); const uid = key.userIds.find(u => u.email === email);
uid.nonce = nonce; uid.nonce = nonce;
return {userIds: [uid], keyId: key.keyId}; return {userIds: [uid], keyId: key.keyId};
@ -256,7 +256,7 @@ class PublicKey {
if (keyId) { if (keyId) {
for (const uid of key.userIds) { for (const uid of key.userIds) {
const nonce = util.random(); const nonce = util.random();
yield this._mongo.update({'userIds.email': uid.email}, {'userIds.$.nonce': nonce}, DB_TYPE); await this._mongo.update({'userIds.email': uid.email}, {'userIds.$.nonce': nonce}, DB_TYPE);
uid.nonce = nonce; uid.nonce = nonce;
} }
return key; return key;
@ -270,14 +270,14 @@ class PublicKey {
* @param {string} nonce The verification nonce proving email address ownership * @param {string} nonce The verification nonce proving email address ownership
* @yield {undefined} * @yield {undefined}
*/ */
*verifyRemove({keyId, nonce}) { async verifyRemove({keyId, nonce}) {
// check if key exists in database // check if key exists in database
const flagged = yield this._mongo.get({keyId, 'userIds.nonce': nonce}, DB_TYPE); const flagged = await this._mongo.get({keyId, 'userIds.nonce': nonce}, DB_TYPE);
if (!flagged) { if (!flagged) {
util.throw(404, 'User id not found'); util.throw(404, 'User id not found');
} }
// delete the key // delete the key
yield this._mongo.remove({keyId}, DB_TYPE); await this._mongo.remove({keyId}, DB_TYPE);
} }
} }

View File

@ -10,6 +10,7 @@ const log = require('npmlog');
describe('Koa App (HTTP Server) Integration Tests', function() { describe('Koa App (HTTP Server) Integration Tests', function() {
this.timeout(20000); this.timeout(20000);
let sandbox;
let app; let app;
let mongo; let mongo;
let sendEmailStub; let sendEmailStub;
@ -21,40 +22,41 @@ describe('Koa App (HTTP Server) Integration Tests', function() {
const primaryEmail = 'safewithme.testuser@gmail.com'; const primaryEmail = 'safewithme.testuser@gmail.com';
const fingerprint = '4277257930867231CE393FB8DBC0B3D92B1B86E9'; const fingerprint = '4277257930867231CE393FB8DBC0B3D92B1B86E9';
before(function *() { before(async () => {
sandbox = sinon.sandbox.create();
publicKeyArmored = fs.readFileSync(`${__dirname}/../key1.asc`, 'utf8'); publicKeyArmored = fs.readFileSync(`${__dirname}/../key1.asc`, 'utf8');
mongo = new Mongo(); mongo = new Mongo();
yield mongo.init(config.mongo); await mongo.init(config.mongo);
sendEmailStub = sinon.stub().returns(Promise.resolve({response: '250'})); sendEmailStub = sandbox.stub().returns(Promise.resolve({response: '250'}));
sendEmailStub.withArgs(sinon.match(recipient => recipient.to.address === primaryEmail), sinon.match(params => { sendEmailStub.withArgs(sinon.match(recipient => recipient.to.address === primaryEmail), sinon.match(params => {
emailParams = params; emailParams = params;
return Boolean(params.nonce); return Boolean(params.nonce);
})); }));
sinon.stub(nodemailer, 'createTransport').returns({ sandbox.stub(nodemailer, 'createTransport').returns({
templateSender: () => sendEmailStub, templateSender: () => sendEmailStub,
use() {} use() {}
}); });
sinon.stub(log); sandbox.stub(log);
global.testing = true; global.testing = true;
const init = require('../../src/app'); const init = require('../../src/app');
app = yield init(); app = await init();
}); });
beforeEach(function *() { beforeEach(async () => {
yield mongo.clear(DB_TYPE_PUB_KEY); await mongo.clear(DB_TYPE_PUB_KEY);
yield mongo.clear(DB_TYPE_USER_ID); await mongo.clear(DB_TYPE_USER_ID);
emailParams = null; emailParams = null;
}); });
after(function *() { after(async () => {
sinon.restore(log); sandbox.restore();
nodemailer.createTransport.restore(); await mongo.clear(DB_TYPE_PUB_KEY);
yield mongo.clear(DB_TYPE_PUB_KEY); await mongo.clear(DB_TYPE_USER_ID);
yield mongo.clear(DB_TYPE_USER_ID); await mongo.disconnect();
yield mongo.disconnect();
}); });
describe('REST api', () => { describe('REST api', () => {

View File

@ -36,7 +36,7 @@ describe('Email Integration Tests', function() {
}); });
describe("_sendHelper", () => { describe("_sendHelper", () => {
it('should work', function *() { it('should work', async () => {
const mailOptions = { const mailOptions = {
from: email._sender, from: email._sender,
to: recipient, to: recipient,
@ -44,30 +44,30 @@ describe('Email Integration Tests', function() {
text: 'Hello world 🐴', // plaintext body text: 'Hello world 🐴', // plaintext body
html: '<b>Hello world 🐴</b>' // html body html: '<b>Hello world 🐴</b>' // html body
}; };
const info = yield email._sendHelper(mailOptions); const info = await email._sendHelper(mailOptions);
expect(info).to.exist; expect(info).to.exist;
}); });
}); });
describe("send verifyKey template", () => { describe("send verifyKey template", () => {
it('should send plaintext email', function *() { it('should send plaintext email', async () => {
delete userId.publicKeyArmored; delete userId.publicKeyArmored;
yield email.send({template: tpl.verifyKey, userId, keyId, origin}); await email.send({template: tpl.verifyKey, userId, keyId, origin});
}); });
it('should send pgp encrypted email', function *() { it('should send pgp encrypted email', async () => {
yield email.send({template: tpl.verifyKey, userId, keyId, origin}); await email.send({template: tpl.verifyKey, userId, keyId, origin});
}); });
}); });
describe("send verifyRemove template", () => { describe("send verifyRemove template", () => {
it('should send plaintext email', function *() { it('should send plaintext email', async () => {
delete userId.publicKeyArmored; delete userId.publicKeyArmored;
yield email.send({template: tpl.verifyRemove, userId, keyId, origin}); await email.send({template: tpl.verifyRemove, userId, keyId, origin});
}); });
it('should send pgp encrypted email', function *() { it('should send pgp encrypted email', async () => {
yield email.send({template: tpl.verifyRemove, userId, keyId, origin}); await email.send({template: tpl.verifyRemove, userId, keyId, origin});
}); });
}); });
}); });

View File

@ -9,31 +9,31 @@ describe('Mongo Integration Tests', function() {
const DB_TYPE = 'apple'; const DB_TYPE = 'apple';
let mongo; let mongo;
before(function *() { before(async () => {
mongo = new Mongo(); mongo = new Mongo();
yield mongo.init(config.mongo); await mongo.init(config.mongo);
}); });
beforeEach(function *() { beforeEach(async () => {
yield mongo.clear(DB_TYPE); await mongo.clear(DB_TYPE);
}); });
after(function *() { after(async () => {
yield mongo.clear(DB_TYPE); await mongo.clear(DB_TYPE);
yield mongo.disconnect(); await mongo.disconnect();
}); });
describe("create", () => { describe("create", () => {
it('should insert a document', function *() { it('should insert a document', async () => {
const r = yield mongo.create({_id: '0'}, DB_TYPE); const r = await mongo.create({_id: '0'}, DB_TYPE);
expect(r.insertedCount).to.equal(1); expect(r.insertedCount).to.equal(1);
}); });
it('should fail if two with the same ID are inserted', function *() { it('should fail if two with the same ID are inserted', async () => {
let r = yield mongo.create({_id: '0'}, DB_TYPE); let r = await mongo.create({_id: '0'}, DB_TYPE);
expect(r.insertedCount).to.equal(1); expect(r.insertedCount).to.equal(1);
try { try {
r = yield mongo.create({_id: '0'}, DB_TYPE); r = await mongo.create({_id: '0'}, DB_TYPE);
} catch (e) { } catch (e) {
expect(e.message).to.match(/duplicate/); expect(e.message).to.match(/duplicate/);
} }
@ -41,16 +41,16 @@ describe('Mongo Integration Tests', function() {
}); });
describe("batch", () => { describe("batch", () => {
it('should insert a document', function *() { it('should insert a document', async () => {
const r = yield mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE); const r = await mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE);
expect(r.insertedCount).to.equal(2); expect(r.insertedCount).to.equal(2);
}); });
it('should fail if docs with the same ID are inserted', function *() { it('should fail if docs with the same ID are inserted', async () => {
let r = yield mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE); let r = await mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE);
expect(r.insertedCount).to.equal(2); expect(r.insertedCount).to.equal(2);
try { try {
r = yield mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE); r = await mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE);
} catch (e) { } catch (e) {
expect(e.message).to.match(/duplicate/); expect(e.message).to.match(/duplicate/);
} }
@ -58,36 +58,36 @@ describe('Mongo Integration Tests', function() {
}); });
describe("update", () => { describe("update", () => {
it('should update a document', function *() { it('should update a document', async () => {
let r = yield mongo.create({_id: '0'}, DB_TYPE); let r = await mongo.create({_id: '0'}, DB_TYPE);
r = yield mongo.update({_id: '0'}, {foo: 'bar'}, DB_TYPE); r = await mongo.update({_id: '0'}, {foo: 'bar'}, DB_TYPE);
expect(r.modifiedCount).to.equal(1); expect(r.modifiedCount).to.equal(1);
r = yield mongo.get({_id: '0'}, DB_TYPE); r = await mongo.get({_id: '0'}, DB_TYPE);
expect(r.foo).to.equal('bar'); expect(r.foo).to.equal('bar');
}); });
}); });
describe("get", () => { describe("get", () => {
it('should get a document', function *() { it('should get a document', async () => {
let r = yield mongo.create({_id: '0'}, DB_TYPE); let r = await mongo.create({_id: '0'}, DB_TYPE);
r = yield mongo.get({_id: '0'}, DB_TYPE); r = await mongo.get({_id: '0'}, DB_TYPE);
expect(r).to.exist; expect(r).to.exist;
}); });
}); });
describe("list", () => { describe("list", () => {
it('should list documents', function *() { it('should list documents', async () => {
let r = yield mongo.batch([{_id: '0', foo: 'bar'}, {_id: '1', foo: 'bar'}], DB_TYPE); let r = await mongo.batch([{_id: '0', foo: 'bar'}, {_id: '1', foo: 'bar'}], DB_TYPE);
r = yield mongo.list({foo: 'bar'}, DB_TYPE); r = await mongo.list({foo: 'bar'}, DB_TYPE);
expect(r).to.deep.equal([{_id: '0', foo: 'bar'}, {_id: '1', foo: 'bar'}], DB_TYPE); expect(r).to.deep.equal([{_id: '0', foo: 'bar'}, {_id: '1', foo: 'bar'}], DB_TYPE);
}); });
}); });
describe("remove", () => { describe("remove", () => {
it('should remove a document', function *() { it('should remove a document', async () => {
let r = yield mongo.create({_id: '0'}, DB_TYPE); let r = await mongo.create({_id: '0'}, DB_TYPE);
r = yield mongo.remove({_id: '0'}, DB_TYPE); r = await mongo.remove({_id: '0'}, DB_TYPE);
r = yield mongo.get({_id: '0'}, DB_TYPE); r = await mongo.get({_id: '0'}, DB_TYPE);
expect(r).to.not.exist; expect(r).to.not.exist;
}); });
}); });

View File

@ -10,6 +10,7 @@ const PublicKey = require('../../src/service/public-key');
describe('Public Key Integration Tests', function() { describe('Public Key Integration Tests', function() {
this.timeout(20000); this.timeout(20000);
let sandbox;
let publicKey; let publicKey;
let email; let email;
let mongo; let mongo;
@ -24,15 +25,17 @@ describe('Public Key Integration Tests', function() {
const primaryEmail2 = 'test2@example.com'; const primaryEmail2 = 'test2@example.com';
const origin = {host: 'localhost', protocol: 'http'}; const origin = {host: 'localhost', protocol: 'http'};
before(function *() { before(async () => {
publicKeyArmored = require('fs').readFileSync(`${__dirname}/../key3.asc`, 'utf8'); publicKeyArmored = require('fs').readFileSync(`${__dirname}/../key3.asc`, 'utf8');
publicKeyArmored2 = require('fs').readFileSync(`${__dirname}/../key4.asc`, 'utf8'); publicKeyArmored2 = require('fs').readFileSync(`${__dirname}/../key4.asc`, 'utf8');
mongo = new Mongo(); mongo = new Mongo();
yield mongo.init(config.mongo); await mongo.init(config.mongo);
}); });
beforeEach(function *() { beforeEach(async () => {
yield mongo.clear(DB_TYPE); sandbox = sinon.sandbox.create();
await mongo.clear(DB_TYPE);
mailsSent = []; mailsSent = [];
sendEmailStub = sinon.stub().returns(Promise.resolve({response: '250'})); sendEmailStub = sinon.stub().returns(Promise.resolve({response: '250'}));
sendEmailStub.withArgs(sinon.match(recipient => { sendEmailStub.withArgs(sinon.match(recipient => {
@ -44,7 +47,7 @@ describe('Public Key Integration Tests', function() {
expect(params.keyId).to.exist; expect(params.keyId).to.exist;
return true; return true;
})); }));
sinon.stub(nodemailer, 'createTransport').returns({ sandbox.stub(nodemailer, 'createTransport').returns({
templateSender: () => sendEmailStub templateSender: () => sendEmailStub
}); });
email = new Email(nodemailer); email = new Email(nodemailer);
@ -58,39 +61,39 @@ describe('Public Key Integration Tests', function() {
}); });
afterEach(() => { afterEach(() => {
nodemailer.createTransport.restore(); sandbox.restore();
}); });
after(function *() { after(async () => {
yield mongo.clear(DB_TYPE); await mongo.clear(DB_TYPE);
yield mongo.disconnect(); await mongo.disconnect();
}); });
describe('put', () => { describe('put', () => {
it('should persist key and send verification email with primaryEmail', function *() { it('should persist key and send verification email with primaryEmail', async () => {
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
expect(mailsSent.length).to.equal(1); expect(mailsSent.length).to.equal(1);
expect(mailsSent[0].to).to.equal(primaryEmail); expect(mailsSent[0].to).to.equal(primaryEmail);
expect(mailsSent[0].params.keyId).to.exist; expect(mailsSent[0].params.keyId).to.exist;
expect(mailsSent[0].params.nonce).to.exist; expect(mailsSent[0].params.nonce).to.exist;
}); });
it('should persist key and send verification email without primaryEmail', function *() { it('should persist key and send verification email without primaryEmail', async () => {
yield publicKey.put({publicKeyArmored, origin}); await publicKey.put({publicKeyArmored, origin});
expect(mailsSent.length).to.equal(4); expect(mailsSent.length).to.equal(4);
}); });
it('should work twice if not yet verified', function *() { it('should work twice if not yet verified', async () => {
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
expect(mailsSent.length).to.equal(1); expect(mailsSent.length).to.equal(1);
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
expect(mailsSent.length).to.equal(2); expect(mailsSent.length).to.equal(2);
}); });
it('should throw 304 if key already exists', function *() { it('should throw 304 if key already exists', async () => {
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
yield publicKey.verify(mailsSent[0].params); await publicKey.verify(mailsSent[0].params);
try { try {
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
expect(false).to.be.true; expect(false).to.be.true;
} catch (e) { } catch (e) {
expect(e.status).to.equal(304); expect(e.status).to.equal(304);
@ -99,60 +102,60 @@ describe('Public Key Integration Tests', function() {
}); });
describe('verify', () => { describe('verify', () => {
it('should update the document', function *() { it('should update the document', async () => {
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
const emailParams = mailsSent[0].params; const emailParams = mailsSent[0].params;
yield publicKey.verify(emailParams); await publicKey.verify(emailParams);
const gotten = yield mongo.get({keyId: emailParams.keyId}, DB_TYPE); const gotten = await mongo.get({keyId: emailParams.keyId}, DB_TYPE);
expect(gotten.userIds[0].verified).to.be.true; expect(gotten.userIds[0].verified).to.be.true;
expect(gotten.userIds[0].nonce).to.be.null; expect(gotten.userIds[0].nonce).to.be.null;
expect(gotten.userIds[1].verified).to.be.false; expect(gotten.userIds[1].verified).to.be.false;
expect(gotten.userIds[1].nonce).to.exist; expect(gotten.userIds[1].nonce).to.exist;
}); });
it('should not find the document', function *() { it('should not find the document', async () => {
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
const emailParams = mailsSent[0].params; const emailParams = mailsSent[0].params;
try { try {
yield publicKey.verify({keyId: emailParams.keyId, nonce: 'fake_nonce'}); await publicKey.verify({keyId: emailParams.keyId, nonce: 'fake_nonce'});
expect(true).to.be.false; expect(true).to.be.false;
} catch (e) { } catch (e) {
expect(e.status).to.equal(404); expect(e.status).to.equal(404);
} }
const gotten = yield mongo.get({keyId: emailParams.keyId}, DB_TYPE); const gotten = await mongo.get({keyId: emailParams.keyId}, DB_TYPE);
expect(gotten.userIds[0].verified).to.be.false; expect(gotten.userIds[0].verified).to.be.false;
expect(gotten.userIds[0].nonce).to.equal(emailParams.nonce); expect(gotten.userIds[0].nonce).to.equal(emailParams.nonce);
expect(gotten.userIds[1].verified).to.be.false; expect(gotten.userIds[1].verified).to.be.false;
expect(gotten.userIds[1].nonce).to.exist; expect(gotten.userIds[1].nonce).to.exist;
}); });
it('should not verify a second key for already verified user id of another key', function *() { it('should not verify a second key for already verified user id of another key', async () => {
yield publicKey.put({publicKeyArmored, primaryEmail: primaryEmail2, origin}); await publicKey.put({publicKeyArmored, primaryEmail: primaryEmail2, origin});
expect(mailsSent.length).to.equal(1); expect(mailsSent.length).to.equal(1);
yield publicKey.put({publicKeyArmored: publicKeyArmored2, primaryEmail: primaryEmail2, origin}); await publicKey.put({publicKeyArmored: publicKeyArmored2, primaryEmail: primaryEmail2, origin});
expect(mailsSent.length).to.equal(2); expect(mailsSent.length).to.equal(2);
yield publicKey.verify(mailsSent[1].params); await publicKey.verify(mailsSent[1].params);
try { try {
yield publicKey.verify(mailsSent[0].params); await publicKey.verify(mailsSent[0].params);
expect(true).to.be.false; expect(true).to.be.false;
} catch (e) { } catch (e) {
expect(e.status).to.equal(304); expect(e.status).to.equal(304);
} }
const gotten = yield mongo.get({keyId: mailsSent[0].params.keyId}, DB_TYPE); const gotten = await mongo.get({keyId: mailsSent[0].params.keyId}, DB_TYPE);
expect(gotten.userIds[1].email).to.equal(primaryEmail2); expect(gotten.userIds[1].email).to.equal(primaryEmail2);
expect(gotten.userIds[1].verified).to.be.false; expect(gotten.userIds[1].verified).to.be.false;
expect(gotten.userIds[1].nonce).to.equal(mailsSent[0].params.nonce); expect(gotten.userIds[1].nonce).to.equal(mailsSent[0].params.nonce);
}); });
it('should be able to verify multiple user ids', function *() { it('should be able to verify multiple user ids', async () => {
yield publicKey.put({publicKeyArmored, origin}); await publicKey.put({publicKeyArmored, origin});
expect(mailsSent.length).to.equal(4); expect(mailsSent.length).to.equal(4);
yield publicKey.verify(mailsSent[0].params); await publicKey.verify(mailsSent[0].params);
yield publicKey.verify(mailsSent[1].params); await publicKey.verify(mailsSent[1].params);
yield publicKey.verify(mailsSent[2].params); await publicKey.verify(mailsSent[2].params);
yield publicKey.verify(mailsSent[3].params); await publicKey.verify(mailsSent[3].params);
const gotten = yield mongo.get({keyId: mailsSent[0].params.keyId}, DB_TYPE); const gotten = await mongo.get({keyId: mailsSent[0].params.keyId}, DB_TYPE);
expect(gotten.userIds[0].verified).to.be.true; expect(gotten.userIds[0].verified).to.be.true;
expect(gotten.userIds[1].verified).to.be.true; expect(gotten.userIds[1].verified).to.be.true;
expect(gotten.userIds[2].verified).to.be.true; expect(gotten.userIds[2].verified).to.be.true;
@ -164,67 +167,67 @@ describe('Public Key Integration Tests', function() {
let key; let key;
describe('should find a verified key', () => { describe('should find a verified key', () => {
beforeEach(function *() { beforeEach(async () => {
key = pgp.parseKey(publicKeyArmored); key = pgp.parseKey(publicKeyArmored);
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
yield publicKey.verify(mailsSent[0].params); await publicKey.verify(mailsSent[0].params);
}); });
it('by fingerprint', function *() { it('by fingerprint', async () => {
const verified = yield publicKey.getVerified({fingerprint: key.fingerprint}); const verified = await publicKey.getVerified({fingerprint: key.fingerprint});
expect(verified).to.exist; expect(verified).to.exist;
}); });
it('by all userIds', function *() { it('by all userIds', async () => {
const verified = yield publicKey.getVerified({userIds: key.userIds}); const verified = await publicKey.getVerified({userIds: key.userIds});
expect(verified).to.exist; expect(verified).to.exist;
}); });
it('by verified userId', function *() { it('by verified userId', async () => {
const verified = yield publicKey.getVerified({userIds: [key.userIds[0]]}); const verified = await publicKey.getVerified({userIds: [key.userIds[0]]});
expect(verified).to.exist; expect(verified).to.exist;
}); });
it('by unverified userId', function *() { it('by unverified userId', async () => {
const verified = yield publicKey.getVerified({userIds: [key.userIds[1]]}); const verified = await publicKey.getVerified({userIds: [key.userIds[1]]});
expect(verified).to.not.exist; expect(verified).to.not.exist;
}); });
it('by keyId', function *() { it('by keyId', async () => {
const verified = yield publicKey.getVerified({keyId: key.keyId}); const verified = await publicKey.getVerified({keyId: key.keyId});
expect(verified).to.exist; expect(verified).to.exist;
}); });
it('by all params', function *() { it('by all params', async () => {
const verified = yield publicKey.getVerified(key); const verified = await publicKey.getVerified(key);
expect(verified).to.exist; expect(verified).to.exist;
}); });
}); });
describe('should not find an unverified key', () => { describe('should not find an unverified key', () => {
beforeEach(function *() { beforeEach(async () => {
key = pgp.parseKey(publicKeyArmored); key = pgp.parseKey(publicKeyArmored);
key.userIds[0].verified = false; key.userIds[0].verified = false;
yield mongo.create(key, DB_TYPE); await mongo.create(key, DB_TYPE);
}); });
it('by fingerprint', function *() { it('by fingerprint', async () => {
const verified = yield publicKey.getVerified({fingerprint: key.fingerprint}); const verified = await publicKey.getVerified({fingerprint: key.fingerprint});
expect(verified).to.not.exist; expect(verified).to.not.exist;
}); });
it('by userIds', function *() { it('by userIds', async () => {
const verified = yield publicKey.getVerified({userIds: key.userIds}); const verified = await publicKey.getVerified({userIds: key.userIds});
expect(verified).to.not.exist; expect(verified).to.not.exist;
}); });
it('by keyId', function *() { it('by keyId', async () => {
const verified = yield publicKey.getVerified({keyId: key.keyId}); const verified = await publicKey.getVerified({keyId: key.keyId});
expect(verified).to.not.exist; expect(verified).to.not.exist;
}); });
it('by all params', function *() { it('by all params', async () => {
const verified = yield publicKey.getVerified(key); const verified = await publicKey.getVerified(key);
expect(verified).to.not.exist; expect(verified).to.not.exist;
}); });
}); });
@ -233,52 +236,52 @@ describe('Public Key Integration Tests', function() {
describe('get', () => { describe('get', () => {
let emailParams; let emailParams;
beforeEach(function *() { beforeEach(async () => {
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
emailParams = mailsSent[0].params; emailParams = mailsSent[0].params;
}); });
it('should return verified key by key id', function *() { it('should return verified key by key id', async () => {
yield publicKey.verify(emailParams); await publicKey.verify(emailParams);
const key = yield publicKey.get({keyId: emailParams.keyId}); const key = await publicKey.get({keyId: emailParams.keyId});
expect(key.publicKeyArmored).to.exist; expect(key.publicKeyArmored).to.exist;
}); });
it('should return verified key by key id (uppercase)', function *() { it('should return verified key by key id (uppercase)', async () => {
yield publicKey.verify(emailParams); await publicKey.verify(emailParams);
const key = yield publicKey.get({keyId: emailParams.keyId.toUpperCase()}); const key = await publicKey.get({keyId: emailParams.keyId.toUpperCase()});
expect(key.publicKeyArmored).to.exist; expect(key.publicKeyArmored).to.exist;
}); });
it('should return verified key by fingerprint', function *() { it('should return verified key by fingerprint', async () => {
yield publicKey.verify(emailParams); await publicKey.verify(emailParams);
const fingerprint = pgp.parseKey(publicKeyArmored).fingerprint; const fingerprint = pgp.parseKey(publicKeyArmored).fingerprint;
const key = yield publicKey.get({fingerprint}); const key = await publicKey.get({fingerprint});
expect(key.publicKeyArmored).to.exist; expect(key.publicKeyArmored).to.exist;
}); });
it('should return verified key by fingerprint (uppercase)', function *() { it('should return verified key by fingerprint (uppercase)', async () => {
yield publicKey.verify(emailParams); await publicKey.verify(emailParams);
const fingerprint = pgp.parseKey(publicKeyArmored).fingerprint.toUpperCase(); const fingerprint = pgp.parseKey(publicKeyArmored).fingerprint.toUpperCase();
const key = yield publicKey.get({fingerprint}); const key = await publicKey.get({fingerprint});
expect(key.publicKeyArmored).to.exist; expect(key.publicKeyArmored).to.exist;
}); });
it('should return verified key by email address', function *() { it('should return verified key by email address', async () => {
yield publicKey.verify(emailParams); await publicKey.verify(emailParams);
const key = yield publicKey.get({email: primaryEmail}); const key = await publicKey.get({email: primaryEmail});
expect(key.publicKeyArmored).to.exist; expect(key.publicKeyArmored).to.exist;
}); });
it('should return verified key by email address (uppercase)', function *() { it('should return verified key by email address (uppercase)', async () => {
yield publicKey.verify(emailParams); await publicKey.verify(emailParams);
const key = yield publicKey.get({email: primaryEmail.toUpperCase()}); const key = await publicKey.get({email: primaryEmail.toUpperCase()});
expect(key.publicKeyArmored).to.exist; expect(key.publicKeyArmored).to.exist;
}); });
it('should throw 404 for unverified key', function *() { it('should throw 404 for unverified key', async () => {
try { try {
yield publicKey.get({keyId: emailParams.keyId}); await publicKey.get({keyId: emailParams.keyId});
expect(false).to.be.true; expect(false).to.be.true;
} catch (e) { } catch (e) {
expect(e.status).to.equal(404); expect(e.status).to.equal(404);
@ -289,31 +292,31 @@ describe('Public Key Integration Tests', function() {
describe('requestRemove', () => { describe('requestRemove', () => {
let keyId; let keyId;
beforeEach(function *() { beforeEach(async () => {
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
keyId = mailsSent[0].params.keyId; keyId = mailsSent[0].params.keyId;
}); });
it('should work for verified key', function *() { it('should work for verified key', async () => {
yield publicKey.verify(mailsSent[0].params); await publicKey.verify(mailsSent[0].params);
yield publicKey.requestRemove({keyId, origin}); await publicKey.requestRemove({keyId, origin});
expect(mailsSent.length).to.equal(5); expect(mailsSent.length).to.equal(5);
}); });
it('should work for unverified key', function *() { it('should work for unverified key', async () => {
yield publicKey.requestRemove({keyId, origin}); await publicKey.requestRemove({keyId, origin});
expect(mailsSent.length).to.equal(5); expect(mailsSent.length).to.equal(5);
}); });
it('should work by email address', function *() { it('should work by email address', async () => {
yield publicKey.requestRemove({email: primaryEmail, origin}); await publicKey.requestRemove({email: primaryEmail, origin});
expect(mailsSent.length).to.equal(2); expect(mailsSent.length).to.equal(2);
}); });
it('should throw 404 for no key', function *() { it('should throw 404 for no key', async () => {
yield mongo.remove({keyId}, DB_TYPE); await mongo.remove({keyId}, DB_TYPE);
try { try {
yield publicKey.requestRemove({keyId, origin}); await publicKey.requestRemove({keyId, origin});
expect(false).to.be.true; expect(false).to.be.true;
} catch (e) { } catch (e) {
expect(e.status).to.equal(404); expect(e.status).to.equal(404);
@ -324,22 +327,22 @@ describe('Public Key Integration Tests', function() {
describe('verifyRemove', () => { describe('verifyRemove', () => {
let keyId; let keyId;
beforeEach(function *() { beforeEach(async () => {
yield publicKey.put({publicKeyArmored, primaryEmail, origin}); await publicKey.put({publicKeyArmored, primaryEmail, origin});
keyId = mailsSent[0].params.keyId; keyId = mailsSent[0].params.keyId;
yield publicKey.requestRemove({keyId, origin}); await publicKey.requestRemove({keyId, origin});
}); });
it('should remove key', function *() { it('should remove key', async () => {
yield publicKey.verifyRemove(mailsSent[1].params); await publicKey.verifyRemove(mailsSent[1].params);
const key = yield mongo.get({keyId}, DB_TYPE); const key = await mongo.get({keyId}, DB_TYPE);
expect(key).to.not.exist; expect(key).to.not.exist;
}); });
it('should throw 404 for no key', function *() { it('should throw 404 for no key', async () => {
yield mongo.remove({keyId}, DB_TYPE); await mongo.remove({keyId}, DB_TYPE);
try { try {
yield publicKey.verifyRemove(mailsSent[1].params); await publicKey.verifyRemove(mailsSent[1].params);
expect(false).to.be.true; expect(false).to.be.true;
} catch (e) { } catch (e) {
expect(e.status).to.equal(404); expect(e.status).to.equal(404);

View File

@ -1,7 +1,5 @@
'use strict'; 'use strict';
require('co-mocha')(require('mocha')); // monkey patch mocha for generators
const expect = require('chai').expect; const expect = require('chai').expect;
const sinon = require('sinon'); const sinon = require('sinon');

View File

@ -5,6 +5,7 @@ const Email = require('../../src/email/email');
const nodemailer = require('nodemailer'); const nodemailer = require('nodemailer');
describe('Email Unit Tests', () => { describe('Email Unit Tests', () => {
let sandbox;
let email; let email;
let sendFnStub; let sendFnStub;
@ -36,13 +37,14 @@ describe('Email Unit Tests', () => {
}; };
beforeEach(() => { beforeEach(() => {
sandbox = sinon.sandbox.create();
sendFnStub = sinon.stub(); sendFnStub = sinon.stub();
sinon.stub(nodemailer, 'createTransport').returns({ sandbox.stub(nodemailer, 'createTransport').returns({
templateSender: () => sendFnStub templateSender: () => sendFnStub
}); });
sinon.stub(log, 'warn'); sandbox.stub(log);
sinon.stub(log, 'error');
email = new Email(nodemailer); email = new Email(nodemailer);
email.init({ email.init({
@ -54,50 +56,44 @@ describe('Email Unit Tests', () => {
}); });
afterEach(() => { afterEach(() => {
nodemailer.createTransport.restore(); sandbox.restore();
log.warn.restore();
log.error.restore();
}); });
describe("send", () => { describe("send", () => {
beforeEach(() => { beforeEach(() => {
sinon.stub(email, '_sendHelper').returns(Promise.resolve({response: '250'})); sandbox.stub(email, '_sendHelper').returns(Promise.resolve({response: '250'}));
}); });
afterEach(() => { it('should work', async () => {
email._sendHelper.restore(); const info = await email.send({template, userId: userId1, keyId, origin});
});
it('should work', function *() {
const info = yield email.send({template, userId: userId1, keyId, origin});
expect(info.response).to.match(/^250/); expect(info.response).to.match(/^250/);
}); });
}); });
describe("_sendHelper", () => { describe("_sendHelper", () => {
it('should work', function *() { it('should work', async () => {
sendFnStub.returns(Promise.resolve({response: '250'})); sendFnStub.returns(Promise.resolve({response: '250'}));
const info = yield email._sendHelper(mailOptions); const info = await email._sendHelper(mailOptions);
expect(info.response).to.match(/^250/); expect(info.response).to.match(/^250/);
}); });
it('should log warning for reponse error', function *() { it('should log warning for reponse error', async () => {
sendFnStub.returns(Promise.resolve({response: '554'})); sendFnStub.returns(Promise.resolve({response: '554'}));
const info = yield email._sendHelper(mailOptions); const info = await email._sendHelper(mailOptions);
expect(info.response).to.match(/^554/); expect(info.response).to.match(/^554/);
expect(log.warn.calledOnce).to.be.true; expect(log.warn.calledOnce).to.be.true;
}); });
it('should fail', function *() { it('should fail', async () => {
sendFnStub.returns(Promise.reject(new Error('boom'))); sendFnStub.returns(Promise.reject(new Error('boom')));
try { try {
yield email._sendHelper(mailOptions); await email._sendHelper(mailOptions);
} catch (e) { } catch (e) {
expect(log.error.calledOnce).to.be.true; expect(log.error.calledOnce).to.be.true;
expect(e.status).to.equal(500); expect(e.status).to.equal(500);

View File

@ -6,47 +6,50 @@ const openpgp = require('openpgp');
const PGP = require('../../src/service/pgp'); const PGP = require('../../src/service/pgp');
describe('PGP Unit Tests', () => { describe('PGP Unit Tests', () => {
let sandbox;
let pgp; let pgp;
let key1Armored; let key1Armored;
let key2Armored; let key2Armored;
let key3Armored; let key3Armored;
beforeEach(() => { beforeEach(() => {
sandbox = sinon.sandbox.create();
sandbox.stub(log);
key1Armored = fs.readFileSync(`${__dirname}/../key1.asc`, 'utf8'); key1Armored = fs.readFileSync(`${__dirname}/../key1.asc`, 'utf8');
key2Armored = fs.readFileSync(`${__dirname}/../key2.asc`, 'utf8'); key2Armored = fs.readFileSync(`${__dirname}/../key2.asc`, 'utf8');
key3Armored = fs.readFileSync(`${__dirname}/../key3.asc`, 'utf8'); key3Armored = fs.readFileSync(`${__dirname}/../key3.asc`, 'utf8');
pgp = new PGP(); pgp = new PGP();
}); });
afterEach(() => {
sandbox.restore();
});
describe('parseKey', () => { describe('parseKey', () => {
it('should should throw error on key parsing', () => { it('should should throw error on key parsing', () => {
const readStub = sinon.stub(openpgp.key, 'readArmored').returns({err: [new Error()]}); sandbox.stub(openpgp.key, 'readArmored').returns({err: [new Error()]});
sinon.stub(log, 'error');
expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/Failed to parse/); expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/Failed to parse/);
expect(log.error.calledOnce).to.be.true; expect(log.error.calledOnce).to.be.true;
log.error.restore();
readStub.restore();
}); });
it('should should throw error when more than one key', () => { it('should should throw error when more than one key', () => {
const readStub = sinon.stub(openpgp.key, 'readArmored').returns({keys: [{}, {}]}); sandbox.stub(openpgp.key, 'readArmored').returns({keys: [{}, {}]});
expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/only one key/); expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/only one key/);
readStub.restore();
}); });
it('should should throw error when more than one key', () => { it('should should throw error when more than one key', () => {
const readStub = sinon.stub(openpgp.key, 'readArmored').returns({ sandbox.stub(openpgp.key, 'readArmored').returns({
keys: [{ keys: [{
primaryKey: {}, primaryKey: {},
verifyPrimaryKey() { return false; } verifyPrimaryKey() { return false; }
}] }]
}); });
expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/primary key verification/); expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/primary key verification/);
readStub.restore();
}); });
it('should only accept 16 char key id', () => { it('should only accept 16 char key id', () => {
const readStub = sinon.stub(openpgp.key, 'readArmored').returns({ sandbox.stub(openpgp.key, 'readArmored').returns({
keys: [{ keys: [{
primaryKey: { primaryKey: {
fingerprint: '4277257930867231ce393fb8dbc0b3d92b1b86e9', fingerprint: '4277257930867231ce393fb8dbc0b3d92b1b86e9',
@ -60,11 +63,10 @@ describe('PGP Unit Tests', () => {
}] }]
}); });
expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/only v4 keys/); expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/only v4 keys/);
readStub.restore();
}); });
it('should only accept version 4 fingerprint', () => { it('should only accept version 4 fingerprint', () => {
const readStub = sinon.stub(openpgp.key, 'readArmored').returns({ sandbox.stub(openpgp.key, 'readArmored').returns({
keys: [{ keys: [{
primaryKey: { primaryKey: {
fingerprint: '4277257930867231ce393fb8dbc0b3d92b1b86e', fingerprint: '4277257930867231ce393fb8dbc0b3d92b1b86e',
@ -78,11 +80,10 @@ describe('PGP Unit Tests', () => {
}] }]
}); });
expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/only v4 keys/); expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/only v4 keys/);
readStub.restore();
}); });
it('should only accept valid user ids', () => { it('should only accept valid user ids', () => {
sinon.stub(pgp, 'parseUserIds').returns([]); sandbox.stub(pgp, 'parseUserIds').returns([]);
expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/invalid user ids/); expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/invalid user ids/);
}); });
@ -182,11 +183,10 @@ describe('PGP Unit Tests', () => {
}); });
it('should throw for a invalid email address', () => { it('should throw for a invalid email address', () => {
const verifyStub = sinon.stub(key.users[0], 'isValidSelfCertificate').returns(true); sandbox.stub(key.users[0], 'isValidSelfCertificate').returns(true);
key.users[0].userId.userid = 'safewithme testuser <safewithme.testusergmail.com>'; key.users[0].userId.userid = 'safewithme testuser <safewithme.testusergmail.com>';
const parsed = pgp.parseUserIds(key.users, key.primaryKey); const parsed = pgp.parseUserIds(key.users, key.primaryKey);
expect(parsed.length).to.equal(0); expect(parsed.length).to.equal(0);
verifyStub.restore();
}); });
}); });
}); });