commit
8c76281666
@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6
|
||||
"ecmaVersion": 2017
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
@ -41,7 +41,11 @@
|
||||
"semi": ["warn", "always"], // require or disallow semicolons instead of ASI
|
||||
"semi-spacing": 1, // enforce consistent spacing before and after semicolons
|
||||
"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-infix-ops": 1, // require spacing around operators
|
||||
/* ES6 */
|
||||
|
10
package.json
10
package.json
@ -21,12 +21,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"addressparser": "^1.0.1",
|
||||
"co": "^4.6.0",
|
||||
"co-body": "^5.1.1",
|
||||
"config": "^1.20.4",
|
||||
"koa": "^1.2.0",
|
||||
"koa-router": "^5.4.0",
|
||||
"koa-static": "^2.0.0",
|
||||
"koa": "^2.3.0",
|
||||
"koa-body": "^2.3.0",
|
||||
"koa-router": "^7.2.1",
|
||||
"koa-static": "^4.0.1",
|
||||
"mongodb": "^2.2.31",
|
||||
"nodemailer": "^2.4.2",
|
||||
"nodemailer-openpgp": "^1.0.2",
|
||||
@ -35,7 +34,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.1.1",
|
||||
"co-mocha": "^1.1.2",
|
||||
"eslint": "^4.4.1",
|
||||
"mocha": "^3.2.0",
|
||||
"sinon": "^1.17.4",
|
||||
|
77
src/app.js
77
src/app.js
@ -17,8 +17,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const co = require('co');
|
||||
const app = require('koa')();
|
||||
const Koa = require('koa');
|
||||
const koaBody = require('koa-body');
|
||||
const log = require('npmlog');
|
||||
const config = require('config');
|
||||
const serve = require('koa-static');
|
||||
@ -31,6 +31,8 @@ const PublicKey = require('./service/public-key');
|
||||
const HKP = require('./route/hkp');
|
||||
const REST = require('./route/rest');
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
let mongo;
|
||||
let email;
|
||||
let pgp;
|
||||
@ -43,55 +45,50 @@ let rest;
|
||||
//
|
||||
|
||||
// HKP routes
|
||||
router.post('/pks/add', function *() {
|
||||
yield hkp.add(this);
|
||||
});
|
||||
router.get('/pks/lookup', function *() {
|
||||
yield hkp.lookup(this);
|
||||
});
|
||||
router.post('/pks/add', ctx => hkp.add(ctx));
|
||||
router.get('/pks/lookup', ctx => hkp.lookup(ctx));
|
||||
|
||||
// REST api routes
|
||||
router.post('/api/v1/key', function *() {
|
||||
yield rest.create(this);
|
||||
});
|
||||
router.get('/api/v1/key', function *() {
|
||||
yield rest.query(this);
|
||||
});
|
||||
router.del('/api/v1/key', function *() {
|
||||
yield rest.remove(this);
|
||||
});
|
||||
router.post('/api/v1/key', ctx => rest.create(ctx));
|
||||
router.get('/api/v1/key', ctx => rest.query(ctx));
|
||||
router.del('/api/v1/key', ctx => rest.remove(ctx));
|
||||
|
||||
// Redirect all http traffic to https
|
||||
app.use(function *(next) {
|
||||
if (util.isTrue(config.server.httpsUpgrade) && util.checkHTTP(this)) {
|
||||
this.redirect(`https://${this.hostname}${this.url}`);
|
||||
app.use(async (ctx, next) => {
|
||||
if (util.isTrue(config.server.httpsUpgrade) && util.checkHTTP(ctx)) {
|
||||
ctx.redirect(`https://${ctx.hostname}${ctx.url}`);
|
||||
} else {
|
||||
yield next;
|
||||
await next();
|
||||
}
|
||||
});
|
||||
|
||||
// Set HTTP response headers
|
||||
app.use(function *(next) {
|
||||
app.use(async (ctx, next) => {
|
||||
// HSTS
|
||||
if (util.isTrue(config.server.httpsUpgrade)) {
|
||||
this.set('Strict-Transport-Security', 'max-age=16070400');
|
||||
ctx.set('Strict-Transport-Security', 'max-age=16070400');
|
||||
}
|
||||
// HPKP
|
||||
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
|
||||
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)
|
||||
this.set('X-Frame-Options', 'DENY');
|
||||
ctx.set('X-Frame-Options', 'DENY');
|
||||
// CORS
|
||||
this.set('Access-Control-Allow-Origin', '*');
|
||||
this.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
this.set('Access-Control-Allow-Headers', 'Content-Type');
|
||||
this.set('Connection', 'keep-alive');
|
||||
yield next;
|
||||
ctx.set('Access-Control-Allow-Origin', '*');
|
||||
ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
ctx.set('Access-Control-Allow-Headers', 'Content-Type');
|
||||
ctx.set('Connection', 'keep-alive');
|
||||
await next();
|
||||
});
|
||||
|
||||
app.use(koaBody({
|
||||
multipart: true,
|
||||
formLimit: '1mb'
|
||||
}));
|
||||
|
||||
app.use(router.routes());
|
||||
app.use(router.allowedMethods());
|
||||
|
||||
@ -124,19 +121,23 @@ function injectDependencies() {
|
||||
//
|
||||
|
||||
if (!global.testing) { // don't automatically start server in tests
|
||||
co(function *() {
|
||||
const app = yield init();
|
||||
app.listen(config.server.port);
|
||||
log.info('app', `Ready to rock! Listening on http://localhost:${config.server.port}`);
|
||||
}).catch(err => log.error('app', 'Initialization failed!', err));
|
||||
(async () => {
|
||||
try {
|
||||
const app = await init();
|
||||
app.listen(config.server.port);
|
||||
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
|
||||
injectDependencies();
|
||||
email.init(config.email);
|
||||
log.info('app', 'Connecting to MongoDB ...');
|
||||
yield mongo.init(config.mongo);
|
||||
await mongo.init(config.mongo);
|
||||
return app;
|
||||
}
|
||||
|
||||
|
@ -30,9 +30,9 @@ class Mongo {
|
||||
* @param {String} pass The database user's password
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*init({uri, user, pass}) {
|
||||
async init({uri, user, pass}) {
|
||||
const url = `mongodb://${user}:${pass}@${uri}`;
|
||||
this._db = yield MongoClient.connect(url);
|
||||
this._db = await MongoClient.connect(url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,7 +58,7 @@ class Email {
|
||||
* @param {Object} origin origin of the server
|
||||
* @yield {Object} send response from the SMTP server
|
||||
*/
|
||||
*send({template, userId, keyId, origin}) {
|
||||
async send({template, userId, keyId, origin}) {
|
||||
const message = {
|
||||
from: this._sender,
|
||||
to: userId,
|
||||
@ -72,7 +72,7 @@ class Email {
|
||||
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
|
||||
* @yield {Object} reponse object containing SMTP info
|
||||
*/
|
||||
*_sendHelper({from, to, subject, text, html, params = {}}) {
|
||||
async _sendHelper({from, to, subject, text, html, params = {}}) {
|
||||
const template = {
|
||||
subject,
|
||||
text,
|
||||
@ -107,7 +107,7 @@ class Email {
|
||||
|
||||
try {
|
||||
const sendFn = this._transport.templateSender(template, sender);
|
||||
const info = yield sendFn(recipient, params);
|
||||
const info = await sendFn(recipient, params);
|
||||
if (!this._checkResponse(info)) {
|
||||
log.warn('email', 'Message may not have been received.', info);
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const parse = require('co-body');
|
||||
const util = require('../service/util');
|
||||
|
||||
/**
|
||||
@ -37,13 +36,13 @@ class HKP {
|
||||
* Public key upload via http POST
|
||||
* @param {Object} ctx The koa request/response context
|
||||
*/
|
||||
*add(ctx) {
|
||||
const {keytext: publicKeyArmored} = yield parse.form(ctx, {limit: '1mb'});
|
||||
async add(ctx) {
|
||||
const publicKeyArmored = ctx.request.body.keytext;
|
||||
if (!publicKeyArmored) {
|
||||
ctx.throw(400, 'Invalid request!');
|
||||
}
|
||||
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.status = 201;
|
||||
}
|
||||
@ -52,9 +51,9 @@ class HKP {
|
||||
* Public key lookup via http GET
|
||||
* @param {Object} ctx The koa request/response context
|
||||
*/
|
||||
*lookup(ctx) {
|
||||
async lookup(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.setGetBody(ctx, params, key);
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const parse = require('co-body');
|
||||
const util = require('../service/util');
|
||||
|
||||
/**
|
||||
@ -37,13 +36,13 @@ class REST {
|
||||
* Public key upload via http POST
|
||||
* @param {Object} ctx The koa request/response context
|
||||
*/
|
||||
*create(ctx) {
|
||||
const {publicKeyArmored, primaryEmail} = yield parse.json(ctx, {limit: '1mb'});
|
||||
async create(ctx) {
|
||||
const {publicKeyArmored, primaryEmail} = ctx.request.body;
|
||||
if (!publicKeyArmored || (primaryEmail && !util.isEmail(primaryEmail))) {
|
||||
ctx.throw(400, 'Invalid request!');
|
||||
}
|
||||
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.status = 201;
|
||||
}
|
||||
@ -52,29 +51,29 @@ class REST {
|
||||
* Public key query via http GET
|
||||
* @param {Object} ctx The koa request/response context
|
||||
*/
|
||||
*query(ctx) {
|
||||
async query(ctx) {
|
||||
const op = ctx.query.op;
|
||||
if (op === 'verify' || op === 'verifyRemove') {
|
||||
return yield this[op](ctx); // delegate operation
|
||||
return this[op](ctx); // delegate operation
|
||||
}
|
||||
// do READ if no 'op' provided
|
||||
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)) {
|
||||
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
|
||||
* @param {Object} ctx The koa request/response context
|
||||
*/
|
||||
*verify(ctx) {
|
||||
async verify(ctx) {
|
||||
const q = {keyId: ctx.query.keyId, nonce: ctx.query.nonce};
|
||||
if (!util.isKeyId(q.keyId) || !util.isString(q.nonce)) {
|
||||
ctx.throw(400, 'Invalid request!');
|
||||
}
|
||||
yield this._publicKey.verify(q);
|
||||
await this._publicKey.verify(q);
|
||||
// create link for sharing
|
||||
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>`;
|
||||
@ -85,12 +84,12 @@ class REST {
|
||||
* Request public key removal via http DELETE
|
||||
* @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)};
|
||||
if (!util.isKeyId(q.keyId) && !util.isEmail(q.email)) {
|
||||
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.status = 202;
|
||||
}
|
||||
@ -99,12 +98,12 @@ class REST {
|
||||
* Verify public key removal via http GET
|
||||
* @param {Object} ctx The koa request/response context
|
||||
*/
|
||||
*verifyRemove(ctx) {
|
||||
async verifyRemove(ctx) {
|
||||
const q = {keyId: ctx.query.keyId, nonce: ctx.query.nonce};
|
||||
if (!util.isKeyId(q.keyId) || !util.isString(q.nonce)) {
|
||||
ctx.throw(400, 'Invalid request!');
|
||||
}
|
||||
yield this._publicKey.verifyRemove(q);
|
||||
await this._publicKey.verifyRemove(q);
|
||||
ctx.body = 'Key successfully removed!';
|
||||
}
|
||||
}
|
||||
|
@ -65,18 +65,18 @@ class PublicKey {
|
||||
* @param {Object} origin Required for links to the keyserver e.g. { protocol:'https', host:'openpgpkeys@example.com' }
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*put({publicKeyArmored, primaryEmail, origin}) {
|
||||
async put({publicKeyArmored, primaryEmail, origin}) {
|
||||
// parse key block
|
||||
const key = this._pgp.parseKey(publicKeyArmored);
|
||||
// check for existing verfied key by id or email addresses
|
||||
const verified = yield this.getVerified(key);
|
||||
const verified = await this.getVerified(key);
|
||||
if (verified) {
|
||||
util.throw(304, 'Key for this user already exists');
|
||||
}
|
||||
// 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)
|
||||
yield this._sendVerifyEmail(key, primaryEmail, origin);
|
||||
await this._sendVerifyEmail(key, primaryEmail, origin);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,15 +84,15 @@ class PublicKey {
|
||||
* @param {Object} key public key parameters
|
||||
* @yield {undefined} The persisted user id documents
|
||||
*/
|
||||
*_persisKey(key) {
|
||||
async _persisKey(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
|
||||
for (const uid of key.userIds) {
|
||||
uid.nonce = util.random();
|
||||
}
|
||||
// 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) {
|
||||
util.throw(500, 'Failed to persist key');
|
||||
}
|
||||
@ -106,7 +106,7 @@ class PublicKey {
|
||||
* @param {Object} origin the server's origin (required for email links)
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*_sendVerifyEmail({userIds, keyId, publicKeyArmored}, primaryEmail, origin) {
|
||||
async _sendVerifyEmail({userIds, keyId, publicKeyArmored}, primaryEmail, origin) {
|
||||
// check for primary email (send only one email)
|
||||
const primaryUserId = userIds.find(uid => uid.email === primaryEmail);
|
||||
if (primaryUserId) {
|
||||
@ -115,7 +115,7 @@ class PublicKey {
|
||||
// send emails
|
||||
for (const userId of userIds) {
|
||||
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
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*verify({keyId, nonce}) {
|
||||
async verify({keyId, nonce}) {
|
||||
// look for verification nonce in database
|
||||
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) {
|
||||
util.throw(404, 'User id not found');
|
||||
}
|
||||
// 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) {
|
||||
util.throw(304, 'Key for this user already exists');
|
||||
}
|
||||
// flag the user id as verified
|
||||
yield this._mongo.update(query, {
|
||||
await this._mongo.update(query, {
|
||||
'userIds.$.verified': true,
|
||||
'userIds.$.nonce': null
|
||||
}, DB_TYPE);
|
||||
@ -153,7 +153,7 @@ class PublicKey {
|
||||
* @param {string} keyId (optional) The public key id
|
||||
* @yield {Object} The verified key document
|
||||
*/
|
||||
*getVerified({userIds, fingerprint, keyId}) {
|
||||
async getVerified({userIds, fingerprint, keyId}) {
|
||||
let queries = [];
|
||||
// query by 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
|
||||
* @yield {Object} The public key document
|
||||
*/
|
||||
*get({fingerprint, keyId, email}) {
|
||||
async get({fingerprint, keyId, email}) {
|
||||
// look for verified key
|
||||
const userIds = email ? [{email}] : undefined;
|
||||
const key = yield this.getVerified({keyId, fingerprint, userIds});
|
||||
const key = await this.getVerified({keyId, fingerprint, userIds});
|
||||
if (!key) {
|
||||
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' }
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*requestRemove({keyId, email, origin}) {
|
||||
async requestRemove({keyId, email, origin}) {
|
||||
// flag user ids for removal
|
||||
const key = yield this._flagForRemove(keyId, email);
|
||||
const key = await this._flagForRemove(keyId, email);
|
||||
if (!key) {
|
||||
util.throw(404, 'User id not found');
|
||||
}
|
||||
// send verification mails
|
||||
keyId = key.keyId; // get keyId in case request was by email
|
||||
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
|
||||
* @yield {Array} A list of user ids with nonces
|
||||
*/
|
||||
*_flagForRemove(keyId, email) {
|
||||
async _flagForRemove(keyId, email) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
// flag only the provided user id
|
||||
if (email) {
|
||||
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);
|
||||
uid.nonce = nonce;
|
||||
return {userIds: [uid], keyId: key.keyId};
|
||||
@ -256,7 +256,7 @@ class PublicKey {
|
||||
if (keyId) {
|
||||
for (const uid of key.userIds) {
|
||||
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;
|
||||
}
|
||||
return key;
|
||||
@ -270,14 +270,14 @@ class PublicKey {
|
||||
* @param {string} nonce The verification nonce proving email address ownership
|
||||
* @yield {undefined}
|
||||
*/
|
||||
*verifyRemove({keyId, nonce}) {
|
||||
async verifyRemove({keyId, nonce}) {
|
||||
// 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) {
|
||||
util.throw(404, 'User id not found');
|
||||
}
|
||||
// delete the key
|
||||
yield this._mongo.remove({keyId}, DB_TYPE);
|
||||
await this._mongo.remove({keyId}, DB_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ const log = require('npmlog');
|
||||
describe('Koa App (HTTP Server) Integration Tests', function() {
|
||||
this.timeout(20000);
|
||||
|
||||
let sandbox;
|
||||
let app;
|
||||
let mongo;
|
||||
let sendEmailStub;
|
||||
@ -21,40 +22,41 @@ describe('Koa App (HTTP Server) Integration Tests', function() {
|
||||
const primaryEmail = 'safewithme.testuser@gmail.com';
|
||||
const fingerprint = '4277257930867231CE393FB8DBC0B3D92B1B86E9';
|
||||
|
||||
before(function *() {
|
||||
before(async () => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
publicKeyArmored = fs.readFileSync(`${__dirname}/../key1.asc`, 'utf8');
|
||||
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 => {
|
||||
emailParams = params;
|
||||
return Boolean(params.nonce);
|
||||
}));
|
||||
sinon.stub(nodemailer, 'createTransport').returns({
|
||||
sandbox.stub(nodemailer, 'createTransport').returns({
|
||||
templateSender: () => sendEmailStub,
|
||||
use() {}
|
||||
});
|
||||
|
||||
sinon.stub(log);
|
||||
sandbox.stub(log);
|
||||
|
||||
global.testing = true;
|
||||
const init = require('../../src/app');
|
||||
app = yield init();
|
||||
app = await init();
|
||||
});
|
||||
|
||||
beforeEach(function *() {
|
||||
yield mongo.clear(DB_TYPE_PUB_KEY);
|
||||
yield mongo.clear(DB_TYPE_USER_ID);
|
||||
beforeEach(async () => {
|
||||
await mongo.clear(DB_TYPE_PUB_KEY);
|
||||
await mongo.clear(DB_TYPE_USER_ID);
|
||||
emailParams = null;
|
||||
});
|
||||
|
||||
after(function *() {
|
||||
sinon.restore(log);
|
||||
nodemailer.createTransport.restore();
|
||||
yield mongo.clear(DB_TYPE_PUB_KEY);
|
||||
yield mongo.clear(DB_TYPE_USER_ID);
|
||||
yield mongo.disconnect();
|
||||
after(async () => {
|
||||
sandbox.restore();
|
||||
await mongo.clear(DB_TYPE_PUB_KEY);
|
||||
await mongo.clear(DB_TYPE_USER_ID);
|
||||
await mongo.disconnect();
|
||||
});
|
||||
|
||||
describe('REST api', () => {
|
||||
|
@ -36,7 +36,7 @@ describe('Email Integration Tests', function() {
|
||||
});
|
||||
|
||||
describe("_sendHelper", () => {
|
||||
it('should work', function *() {
|
||||
it('should work', async () => {
|
||||
const mailOptions = {
|
||||
from: email._sender,
|
||||
to: recipient,
|
||||
@ -44,30 +44,30 @@ describe('Email Integration Tests', function() {
|
||||
text: 'Hello world 🐴', // plaintext body
|
||||
html: '<b>Hello world 🐴</b>' // html body
|
||||
};
|
||||
const info = yield email._sendHelper(mailOptions);
|
||||
const info = await email._sendHelper(mailOptions);
|
||||
expect(info).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe("send verifyKey template", () => {
|
||||
it('should send plaintext email', function *() {
|
||||
it('should send plaintext email', async () => {
|
||||
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 *() {
|
||||
yield email.send({template: tpl.verifyKey, userId, keyId, origin});
|
||||
it('should send pgp encrypted email', async () => {
|
||||
await email.send({template: tpl.verifyKey, userId, keyId, origin});
|
||||
});
|
||||
});
|
||||
|
||||
describe("send verifyRemove template", () => {
|
||||
it('should send plaintext email', function *() {
|
||||
it('should send plaintext email', async () => {
|
||||
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 *() {
|
||||
yield email.send({template: tpl.verifyRemove, userId, keyId, origin});
|
||||
it('should send pgp encrypted email', async () => {
|
||||
await email.send({template: tpl.verifyRemove, userId, keyId, origin});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -9,31 +9,31 @@ describe('Mongo Integration Tests', function() {
|
||||
const DB_TYPE = 'apple';
|
||||
let mongo;
|
||||
|
||||
before(function *() {
|
||||
before(async () => {
|
||||
mongo = new Mongo();
|
||||
yield mongo.init(config.mongo);
|
||||
await mongo.init(config.mongo);
|
||||
});
|
||||
|
||||
beforeEach(function *() {
|
||||
yield mongo.clear(DB_TYPE);
|
||||
beforeEach(async () => {
|
||||
await mongo.clear(DB_TYPE);
|
||||
});
|
||||
|
||||
after(function *() {
|
||||
yield mongo.clear(DB_TYPE);
|
||||
yield mongo.disconnect();
|
||||
after(async () => {
|
||||
await mongo.clear(DB_TYPE);
|
||||
await mongo.disconnect();
|
||||
});
|
||||
|
||||
describe("create", () => {
|
||||
it('should insert a document', function *() {
|
||||
const r = yield mongo.create({_id: '0'}, DB_TYPE);
|
||||
it('should insert a document', async () => {
|
||||
const r = await mongo.create({_id: '0'}, DB_TYPE);
|
||||
expect(r.insertedCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should fail if two with the same ID are inserted', function *() {
|
||||
let r = yield mongo.create({_id: '0'}, DB_TYPE);
|
||||
it('should fail if two with the same ID are inserted', async () => {
|
||||
let r = await mongo.create({_id: '0'}, DB_TYPE);
|
||||
expect(r.insertedCount).to.equal(1);
|
||||
try {
|
||||
r = yield mongo.create({_id: '0'}, DB_TYPE);
|
||||
r = await mongo.create({_id: '0'}, DB_TYPE);
|
||||
} catch (e) {
|
||||
expect(e.message).to.match(/duplicate/);
|
||||
}
|
||||
@ -41,16 +41,16 @@ describe('Mongo Integration Tests', function() {
|
||||
});
|
||||
|
||||
describe("batch", () => {
|
||||
it('should insert a document', function *() {
|
||||
const r = yield mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE);
|
||||
it('should insert a document', async () => {
|
||||
const r = await mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE);
|
||||
expect(r.insertedCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('should fail if docs with the same ID are inserted', function *() {
|
||||
let r = yield mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE);
|
||||
it('should fail if docs with the same ID are inserted', async () => {
|
||||
let r = await mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE);
|
||||
expect(r.insertedCount).to.equal(2);
|
||||
try {
|
||||
r = yield mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE);
|
||||
r = await mongo.batch([{_id: '0'}, {_id: '1'}], DB_TYPE);
|
||||
} catch (e) {
|
||||
expect(e.message).to.match(/duplicate/);
|
||||
}
|
||||
@ -58,36 +58,36 @@ describe('Mongo Integration Tests', function() {
|
||||
});
|
||||
|
||||
describe("update", () => {
|
||||
it('should update a document', function *() {
|
||||
let r = yield mongo.create({_id: '0'}, DB_TYPE);
|
||||
r = yield mongo.update({_id: '0'}, {foo: 'bar'}, DB_TYPE);
|
||||
it('should update a document', async () => {
|
||||
let r = await mongo.create({_id: '0'}, DB_TYPE);
|
||||
r = await mongo.update({_id: '0'}, {foo: 'bar'}, DB_TYPE);
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe("get", () => {
|
||||
it('should get a document', function *() {
|
||||
let r = yield mongo.create({_id: '0'}, DB_TYPE);
|
||||
r = yield mongo.get({_id: '0'}, DB_TYPE);
|
||||
it('should get a document', async () => {
|
||||
let r = await mongo.create({_id: '0'}, DB_TYPE);
|
||||
r = await mongo.get({_id: '0'}, DB_TYPE);
|
||||
expect(r).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe("list", () => {
|
||||
it('should list documents', function *() {
|
||||
let r = yield mongo.batch([{_id: '0', foo: 'bar'}, {_id: '1', foo: 'bar'}], DB_TYPE);
|
||||
r = yield mongo.list({foo: 'bar'}, DB_TYPE);
|
||||
it('should list documents', async () => {
|
||||
let r = await mongo.batch([{_id: '0', foo: 'bar'}, {_id: '1', 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);
|
||||
});
|
||||
});
|
||||
|
||||
describe("remove", () => {
|
||||
it('should remove a document', function *() {
|
||||
let r = yield mongo.create({_id: '0'}, DB_TYPE);
|
||||
r = yield mongo.remove({_id: '0'}, DB_TYPE);
|
||||
r = yield mongo.get({_id: '0'}, DB_TYPE);
|
||||
it('should remove a document', async () => {
|
||||
let r = await mongo.create({_id: '0'}, DB_TYPE);
|
||||
r = await mongo.remove({_id: '0'}, DB_TYPE);
|
||||
r = await mongo.get({_id: '0'}, DB_TYPE);
|
||||
expect(r).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
@ -10,6 +10,7 @@ const PublicKey = require('../../src/service/public-key');
|
||||
describe('Public Key Integration Tests', function() {
|
||||
this.timeout(20000);
|
||||
|
||||
let sandbox;
|
||||
let publicKey;
|
||||
let email;
|
||||
let mongo;
|
||||
@ -24,15 +25,17 @@ describe('Public Key Integration Tests', function() {
|
||||
const primaryEmail2 = 'test2@example.com';
|
||||
const origin = {host: 'localhost', protocol: 'http'};
|
||||
|
||||
before(function *() {
|
||||
before(async () => {
|
||||
publicKeyArmored = require('fs').readFileSync(`${__dirname}/../key3.asc`, 'utf8');
|
||||
publicKeyArmored2 = require('fs').readFileSync(`${__dirname}/../key4.asc`, 'utf8');
|
||||
mongo = new Mongo();
|
||||
yield mongo.init(config.mongo);
|
||||
await mongo.init(config.mongo);
|
||||
});
|
||||
|
||||
beforeEach(function *() {
|
||||
yield mongo.clear(DB_TYPE);
|
||||
beforeEach(async () => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
await mongo.clear(DB_TYPE);
|
||||
mailsSent = [];
|
||||
sendEmailStub = sinon.stub().returns(Promise.resolve({response: '250'}));
|
||||
sendEmailStub.withArgs(sinon.match(recipient => {
|
||||
@ -44,7 +47,7 @@ describe('Public Key Integration Tests', function() {
|
||||
expect(params.keyId).to.exist;
|
||||
return true;
|
||||
}));
|
||||
sinon.stub(nodemailer, 'createTransport').returns({
|
||||
sandbox.stub(nodemailer, 'createTransport').returns({
|
||||
templateSender: () => sendEmailStub
|
||||
});
|
||||
email = new Email(nodemailer);
|
||||
@ -58,39 +61,39 @@ describe('Public Key Integration Tests', function() {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
nodemailer.createTransport.restore();
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
after(function *() {
|
||||
yield mongo.clear(DB_TYPE);
|
||||
yield mongo.disconnect();
|
||||
after(async () => {
|
||||
await mongo.clear(DB_TYPE);
|
||||
await mongo.disconnect();
|
||||
});
|
||||
|
||||
describe('put', () => {
|
||||
it('should persist key and send verification email with primaryEmail', function *() {
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
it('should persist key and send verification email with primaryEmail', async () => {
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
expect(mailsSent.length).to.equal(1);
|
||||
expect(mailsSent[0].to).to.equal(primaryEmail);
|
||||
expect(mailsSent[0].params.keyId).to.exist;
|
||||
expect(mailsSent[0].params.nonce).to.exist;
|
||||
});
|
||||
it('should persist key and send verification email without primaryEmail', function *() {
|
||||
yield publicKey.put({publicKeyArmored, origin});
|
||||
it('should persist key and send verification email without primaryEmail', async () => {
|
||||
await publicKey.put({publicKeyArmored, origin});
|
||||
expect(mailsSent.length).to.equal(4);
|
||||
});
|
||||
|
||||
it('should work twice if not yet verified', function *() {
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
it('should work twice if not yet verified', async () => {
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
expect(mailsSent.length).to.equal(1);
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
expect(mailsSent.length).to.equal(2);
|
||||
});
|
||||
|
||||
it('should throw 304 if key already exists', function *() {
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
yield publicKey.verify(mailsSent[0].params);
|
||||
it('should throw 304 if key already exists', async () => {
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
await publicKey.verify(mailsSent[0].params);
|
||||
try {
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
expect(false).to.be.true;
|
||||
} catch (e) {
|
||||
expect(e.status).to.equal(304);
|
||||
@ -99,60 +102,60 @@ describe('Public Key Integration Tests', function() {
|
||||
});
|
||||
|
||||
describe('verify', () => {
|
||||
it('should update the document', function *() {
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
it('should update the document', async () => {
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
const emailParams = mailsSent[0].params;
|
||||
yield publicKey.verify(emailParams);
|
||||
const gotten = yield mongo.get({keyId: emailParams.keyId}, DB_TYPE);
|
||||
await publicKey.verify(emailParams);
|
||||
const gotten = await mongo.get({keyId: emailParams.keyId}, DB_TYPE);
|
||||
expect(gotten.userIds[0].verified).to.be.true;
|
||||
expect(gotten.userIds[0].nonce).to.be.null;
|
||||
expect(gotten.userIds[1].verified).to.be.false;
|
||||
expect(gotten.userIds[1].nonce).to.exist;
|
||||
});
|
||||
|
||||
it('should not find the document', function *() {
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
it('should not find the document', async () => {
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
const emailParams = mailsSent[0].params;
|
||||
try {
|
||||
yield publicKey.verify({keyId: emailParams.keyId, nonce: 'fake_nonce'});
|
||||
await publicKey.verify({keyId: emailParams.keyId, nonce: 'fake_nonce'});
|
||||
expect(true).to.be.false;
|
||||
} catch (e) {
|
||||
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].nonce).to.equal(emailParams.nonce);
|
||||
expect(gotten.userIds[1].verified).to.be.false;
|
||||
expect(gotten.userIds[1].nonce).to.exist;
|
||||
});
|
||||
|
||||
it('should not verify a second key for already verified user id of another key', function *() {
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail: primaryEmail2, origin});
|
||||
it('should not verify a second key for already verified user id of another key', async () => {
|
||||
await publicKey.put({publicKeyArmored, primaryEmail: primaryEmail2, origin});
|
||||
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);
|
||||
yield publicKey.verify(mailsSent[1].params);
|
||||
await publicKey.verify(mailsSent[1].params);
|
||||
|
||||
try {
|
||||
yield publicKey.verify(mailsSent[0].params);
|
||||
await publicKey.verify(mailsSent[0].params);
|
||||
expect(true).to.be.false;
|
||||
} catch (e) {
|
||||
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].verified).to.be.false;
|
||||
expect(gotten.userIds[1].nonce).to.equal(mailsSent[0].params.nonce);
|
||||
});
|
||||
|
||||
it('should be able to verify multiple user ids', function *() {
|
||||
yield publicKey.put({publicKeyArmored, origin});
|
||||
it('should be able to verify multiple user ids', async () => {
|
||||
await publicKey.put({publicKeyArmored, origin});
|
||||
expect(mailsSent.length).to.equal(4);
|
||||
yield publicKey.verify(mailsSent[0].params);
|
||||
yield publicKey.verify(mailsSent[1].params);
|
||||
yield publicKey.verify(mailsSent[2].params);
|
||||
yield publicKey.verify(mailsSent[3].params);
|
||||
const gotten = yield mongo.get({keyId: mailsSent[0].params.keyId}, DB_TYPE);
|
||||
await publicKey.verify(mailsSent[0].params);
|
||||
await publicKey.verify(mailsSent[1].params);
|
||||
await publicKey.verify(mailsSent[2].params);
|
||||
await publicKey.verify(mailsSent[3].params);
|
||||
const gotten = await mongo.get({keyId: mailsSent[0].params.keyId}, DB_TYPE);
|
||||
expect(gotten.userIds[0].verified).to.be.true;
|
||||
expect(gotten.userIds[1].verified).to.be.true;
|
||||
expect(gotten.userIds[2].verified).to.be.true;
|
||||
@ -164,67 +167,67 @@ describe('Public Key Integration Tests', function() {
|
||||
let key;
|
||||
|
||||
describe('should find a verified key', () => {
|
||||
beforeEach(function *() {
|
||||
beforeEach(async () => {
|
||||
key = pgp.parseKey(publicKeyArmored);
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
yield publicKey.verify(mailsSent[0].params);
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
await publicKey.verify(mailsSent[0].params);
|
||||
});
|
||||
|
||||
it('by fingerprint', function *() {
|
||||
const verified = yield publicKey.getVerified({fingerprint: key.fingerprint});
|
||||
it('by fingerprint', async () => {
|
||||
const verified = await publicKey.getVerified({fingerprint: key.fingerprint});
|
||||
expect(verified).to.exist;
|
||||
});
|
||||
|
||||
it('by all userIds', function *() {
|
||||
const verified = yield publicKey.getVerified({userIds: key.userIds});
|
||||
it('by all userIds', async () => {
|
||||
const verified = await publicKey.getVerified({userIds: key.userIds});
|
||||
expect(verified).to.exist;
|
||||
});
|
||||
|
||||
it('by verified userId', function *() {
|
||||
const verified = yield publicKey.getVerified({userIds: [key.userIds[0]]});
|
||||
it('by verified userId', async () => {
|
||||
const verified = await publicKey.getVerified({userIds: [key.userIds[0]]});
|
||||
expect(verified).to.exist;
|
||||
});
|
||||
|
||||
it('by unverified userId', function *() {
|
||||
const verified = yield publicKey.getVerified({userIds: [key.userIds[1]]});
|
||||
it('by unverified userId', async () => {
|
||||
const verified = await publicKey.getVerified({userIds: [key.userIds[1]]});
|
||||
expect(verified).to.not.exist;
|
||||
});
|
||||
|
||||
it('by keyId', function *() {
|
||||
const verified = yield publicKey.getVerified({keyId: key.keyId});
|
||||
it('by keyId', async () => {
|
||||
const verified = await publicKey.getVerified({keyId: key.keyId});
|
||||
expect(verified).to.exist;
|
||||
});
|
||||
|
||||
it('by all params', function *() {
|
||||
const verified = yield publicKey.getVerified(key);
|
||||
it('by all params', async () => {
|
||||
const verified = await publicKey.getVerified(key);
|
||||
expect(verified).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not find an unverified key', () => {
|
||||
beforeEach(function *() {
|
||||
beforeEach(async () => {
|
||||
key = pgp.parseKey(publicKeyArmored);
|
||||
key.userIds[0].verified = false;
|
||||
yield mongo.create(key, DB_TYPE);
|
||||
await mongo.create(key, DB_TYPE);
|
||||
});
|
||||
|
||||
it('by fingerprint', function *() {
|
||||
const verified = yield publicKey.getVerified({fingerprint: key.fingerprint});
|
||||
it('by fingerprint', async () => {
|
||||
const verified = await publicKey.getVerified({fingerprint: key.fingerprint});
|
||||
expect(verified).to.not.exist;
|
||||
});
|
||||
|
||||
it('by userIds', function *() {
|
||||
const verified = yield publicKey.getVerified({userIds: key.userIds});
|
||||
it('by userIds', async () => {
|
||||
const verified = await publicKey.getVerified({userIds: key.userIds});
|
||||
expect(verified).to.not.exist;
|
||||
});
|
||||
|
||||
it('by keyId', function *() {
|
||||
const verified = yield publicKey.getVerified({keyId: key.keyId});
|
||||
it('by keyId', async () => {
|
||||
const verified = await publicKey.getVerified({keyId: key.keyId});
|
||||
expect(verified).to.not.exist;
|
||||
});
|
||||
|
||||
it('by all params', function *() {
|
||||
const verified = yield publicKey.getVerified(key);
|
||||
it('by all params', async () => {
|
||||
const verified = await publicKey.getVerified(key);
|
||||
expect(verified).to.not.exist;
|
||||
});
|
||||
});
|
||||
@ -233,52 +236,52 @@ describe('Public Key Integration Tests', function() {
|
||||
describe('get', () => {
|
||||
let emailParams;
|
||||
|
||||
beforeEach(function *() {
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
beforeEach(async () => {
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
emailParams = mailsSent[0].params;
|
||||
});
|
||||
|
||||
it('should return verified key by key id', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
const key = yield publicKey.get({keyId: emailParams.keyId});
|
||||
it('should return verified key by key id', async () => {
|
||||
await publicKey.verify(emailParams);
|
||||
const key = await publicKey.get({keyId: emailParams.keyId});
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should return verified key by key id (uppercase)', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
const key = yield publicKey.get({keyId: emailParams.keyId.toUpperCase()});
|
||||
it('should return verified key by key id (uppercase)', async () => {
|
||||
await publicKey.verify(emailParams);
|
||||
const key = await publicKey.get({keyId: emailParams.keyId.toUpperCase()});
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should return verified key by fingerprint', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
it('should return verified key by fingerprint', async () => {
|
||||
await publicKey.verify(emailParams);
|
||||
const fingerprint = pgp.parseKey(publicKeyArmored).fingerprint;
|
||||
const key = yield publicKey.get({fingerprint});
|
||||
const key = await publicKey.get({fingerprint});
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should return verified key by fingerprint (uppercase)', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
it('should return verified key by fingerprint (uppercase)', async () => {
|
||||
await publicKey.verify(emailParams);
|
||||
const fingerprint = pgp.parseKey(publicKeyArmored).fingerprint.toUpperCase();
|
||||
const key = yield publicKey.get({fingerprint});
|
||||
const key = await publicKey.get({fingerprint});
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should return verified key by email address', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
const key = yield publicKey.get({email: primaryEmail});
|
||||
it('should return verified key by email address', async () => {
|
||||
await publicKey.verify(emailParams);
|
||||
const key = await publicKey.get({email: primaryEmail});
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should return verified key by email address (uppercase)', function *() {
|
||||
yield publicKey.verify(emailParams);
|
||||
const key = yield publicKey.get({email: primaryEmail.toUpperCase()});
|
||||
it('should return verified key by email address (uppercase)', async () => {
|
||||
await publicKey.verify(emailParams);
|
||||
const key = await publicKey.get({email: primaryEmail.toUpperCase()});
|
||||
expect(key.publicKeyArmored).to.exist;
|
||||
});
|
||||
|
||||
it('should throw 404 for unverified key', function *() {
|
||||
it('should throw 404 for unverified key', async () => {
|
||||
try {
|
||||
yield publicKey.get({keyId: emailParams.keyId});
|
||||
await publicKey.get({keyId: emailParams.keyId});
|
||||
expect(false).to.be.true;
|
||||
} catch (e) {
|
||||
expect(e.status).to.equal(404);
|
||||
@ -289,31 +292,31 @@ describe('Public Key Integration Tests', function() {
|
||||
describe('requestRemove', () => {
|
||||
let keyId;
|
||||
|
||||
beforeEach(function *() {
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
beforeEach(async () => {
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
keyId = mailsSent[0].params.keyId;
|
||||
});
|
||||
|
||||
it('should work for verified key', function *() {
|
||||
yield publicKey.verify(mailsSent[0].params);
|
||||
yield publicKey.requestRemove({keyId, origin});
|
||||
it('should work for verified key', async () => {
|
||||
await publicKey.verify(mailsSent[0].params);
|
||||
await publicKey.requestRemove({keyId, origin});
|
||||
expect(mailsSent.length).to.equal(5);
|
||||
});
|
||||
|
||||
it('should work for unverified key', function *() {
|
||||
yield publicKey.requestRemove({keyId, origin});
|
||||
it('should work for unverified key', async () => {
|
||||
await publicKey.requestRemove({keyId, origin});
|
||||
expect(mailsSent.length).to.equal(5);
|
||||
});
|
||||
|
||||
it('should work by email address', function *() {
|
||||
yield publicKey.requestRemove({email: primaryEmail, origin});
|
||||
it('should work by email address', async () => {
|
||||
await publicKey.requestRemove({email: primaryEmail, origin});
|
||||
expect(mailsSent.length).to.equal(2);
|
||||
});
|
||||
|
||||
it('should throw 404 for no key', function *() {
|
||||
yield mongo.remove({keyId}, DB_TYPE);
|
||||
it('should throw 404 for no key', async () => {
|
||||
await mongo.remove({keyId}, DB_TYPE);
|
||||
try {
|
||||
yield publicKey.requestRemove({keyId, origin});
|
||||
await publicKey.requestRemove({keyId, origin});
|
||||
expect(false).to.be.true;
|
||||
} catch (e) {
|
||||
expect(e.status).to.equal(404);
|
||||
@ -324,22 +327,22 @@ describe('Public Key Integration Tests', function() {
|
||||
describe('verifyRemove', () => {
|
||||
let keyId;
|
||||
|
||||
beforeEach(function *() {
|
||||
yield publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
beforeEach(async () => {
|
||||
await publicKey.put({publicKeyArmored, primaryEmail, origin});
|
||||
keyId = mailsSent[0].params.keyId;
|
||||
yield publicKey.requestRemove({keyId, origin});
|
||||
await publicKey.requestRemove({keyId, origin});
|
||||
});
|
||||
|
||||
it('should remove key', function *() {
|
||||
yield publicKey.verifyRemove(mailsSent[1].params);
|
||||
const key = yield mongo.get({keyId}, DB_TYPE);
|
||||
it('should remove key', async () => {
|
||||
await publicKey.verifyRemove(mailsSent[1].params);
|
||||
const key = await mongo.get({keyId}, DB_TYPE);
|
||||
expect(key).to.not.exist;
|
||||
});
|
||||
|
||||
it('should throw 404 for no key', function *() {
|
||||
yield mongo.remove({keyId}, DB_TYPE);
|
||||
it('should throw 404 for no key', async () => {
|
||||
await mongo.remove({keyId}, DB_TYPE);
|
||||
try {
|
||||
yield publicKey.verifyRemove(mailsSent[1].params);
|
||||
await publicKey.verifyRemove(mailsSent[1].params);
|
||||
expect(false).to.be.true;
|
||||
} catch (e) {
|
||||
expect(e.status).to.equal(404);
|
||||
|
@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
require('co-mocha')(require('mocha')); // monkey patch mocha for generators
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const sinon = require('sinon');
|
||||
|
||||
|
@ -5,6 +5,7 @@ const Email = require('../../src/email/email');
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
describe('Email Unit Tests', () => {
|
||||
let sandbox;
|
||||
let email;
|
||||
let sendFnStub;
|
||||
|
||||
@ -36,13 +37,14 @@ describe('Email Unit Tests', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
sendFnStub = sinon.stub();
|
||||
sinon.stub(nodemailer, 'createTransport').returns({
|
||||
sandbox.stub(nodemailer, 'createTransport').returns({
|
||||
templateSender: () => sendFnStub
|
||||
});
|
||||
|
||||
sinon.stub(log, 'warn');
|
||||
sinon.stub(log, 'error');
|
||||
sandbox.stub(log);
|
||||
|
||||
email = new Email(nodemailer);
|
||||
email.init({
|
||||
@ -54,50 +56,44 @@ describe('Email Unit Tests', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
nodemailer.createTransport.restore();
|
||||
log.warn.restore();
|
||||
log.error.restore();
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe("send", () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(email, '_sendHelper').returns(Promise.resolve({response: '250'}));
|
||||
sandbox.stub(email, '_sendHelper').returns(Promise.resolve({response: '250'}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
email._sendHelper.restore();
|
||||
});
|
||||
|
||||
it('should work', function *() {
|
||||
const info = yield email.send({template, userId: userId1, keyId, origin});
|
||||
it('should work', async () => {
|
||||
const info = await email.send({template, userId: userId1, keyId, origin});
|
||||
|
||||
expect(info.response).to.match(/^250/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("_sendHelper", () => {
|
||||
it('should work', function *() {
|
||||
it('should work', async () => {
|
||||
sendFnStub.returns(Promise.resolve({response: '250'}));
|
||||
|
||||
const info = yield email._sendHelper(mailOptions);
|
||||
const info = await email._sendHelper(mailOptions);
|
||||
|
||||
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'}));
|
||||
|
||||
const info = yield email._sendHelper(mailOptions);
|
||||
const info = await email._sendHelper(mailOptions);
|
||||
|
||||
expect(info.response).to.match(/^554/);
|
||||
expect(log.warn.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('should fail', function *() {
|
||||
it('should fail', async () => {
|
||||
sendFnStub.returns(Promise.reject(new Error('boom')));
|
||||
|
||||
try {
|
||||
yield email._sendHelper(mailOptions);
|
||||
await email._sendHelper(mailOptions);
|
||||
} catch (e) {
|
||||
expect(log.error.calledOnce).to.be.true;
|
||||
expect(e.status).to.equal(500);
|
||||
|
@ -6,47 +6,50 @@ const openpgp = require('openpgp');
|
||||
const PGP = require('../../src/service/pgp');
|
||||
|
||||
describe('PGP Unit Tests', () => {
|
||||
let sandbox;
|
||||
let pgp;
|
||||
let key1Armored;
|
||||
let key2Armored;
|
||||
let key3Armored;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
sandbox.stub(log);
|
||||
|
||||
key1Armored = fs.readFileSync(`${__dirname}/../key1.asc`, 'utf8');
|
||||
key2Armored = fs.readFileSync(`${__dirname}/../key2.asc`, 'utf8');
|
||||
key3Armored = fs.readFileSync(`${__dirname}/../key3.asc`, 'utf8');
|
||||
pgp = new PGP();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('parseKey', () => {
|
||||
it('should should throw error on key parsing', () => {
|
||||
const readStub = sinon.stub(openpgp.key, 'readArmored').returns({err: [new Error()]});
|
||||
sinon.stub(log, 'error');
|
||||
sandbox.stub(openpgp.key, 'readArmored').returns({err: [new Error()]});
|
||||
expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/Failed to parse/);
|
||||
expect(log.error.calledOnce).to.be.true;
|
||||
log.error.restore();
|
||||
readStub.restore();
|
||||
});
|
||||
|
||||
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/);
|
||||
readStub.restore();
|
||||
});
|
||||
|
||||
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: [{
|
||||
primaryKey: {},
|
||||
verifyPrimaryKey() { return false; }
|
||||
}]
|
||||
});
|
||||
expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/primary key verification/);
|
||||
readStub.restore();
|
||||
});
|
||||
|
||||
it('should only accept 16 char key id', () => {
|
||||
const readStub = sinon.stub(openpgp.key, 'readArmored').returns({
|
||||
sandbox.stub(openpgp.key, 'readArmored').returns({
|
||||
keys: [{
|
||||
primaryKey: {
|
||||
fingerprint: '4277257930867231ce393fb8dbc0b3d92b1b86e9',
|
||||
@ -60,11 +63,10 @@ describe('PGP Unit Tests', () => {
|
||||
}]
|
||||
});
|
||||
expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/only v4 keys/);
|
||||
readStub.restore();
|
||||
});
|
||||
|
||||
it('should only accept version 4 fingerprint', () => {
|
||||
const readStub = sinon.stub(openpgp.key, 'readArmored').returns({
|
||||
sandbox.stub(openpgp.key, 'readArmored').returns({
|
||||
keys: [{
|
||||
primaryKey: {
|
||||
fingerprint: '4277257930867231ce393fb8dbc0b3d92b1b86e',
|
||||
@ -78,11 +80,10 @@ describe('PGP Unit Tests', () => {
|
||||
}]
|
||||
});
|
||||
expect(pgp.parseKey.bind(pgp, key3Armored)).to.throw(/only v4 keys/);
|
||||
readStub.restore();
|
||||
});
|
||||
|
||||
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/);
|
||||
});
|
||||
|
||||
@ -182,11 +183,10 @@ describe('PGP Unit Tests', () => {
|
||||
});
|
||||
|
||||
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>';
|
||||
const parsed = pgp.parseUserIds(key.users, key.primaryKey);
|
||||
expect(parsed.length).to.equal(0);
|
||||
verifyStub.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user