Write public-key integration tests
This commit is contained in:
parent
5d4e162c01
commit
a27e833a8d
@ -40,13 +40,13 @@ class PublicKey {
|
|||||||
* @param {Object} openpgp An instance of OpenPGP.js
|
* @param {Object} openpgp An instance of OpenPGP.js
|
||||||
* @param {Object} mongo An instance of the MongoDB client
|
* @param {Object} mongo An instance of the MongoDB client
|
||||||
* @param {Object} email An instance of the Email Sender
|
* @param {Object} email An instance of the Email Sender
|
||||||
* @param {Object} userid An instance of the UserId service
|
* @param {Object} userId An instance of the UserId service
|
||||||
*/
|
*/
|
||||||
constructor(openpgp, mongo, email, userid) {
|
constructor(openpgp, mongo, email, userId) {
|
||||||
this._openpgp = openpgp;
|
this._openpgp = openpgp;
|
||||||
this._mongo = mongo;
|
this._mongo = mongo;
|
||||||
this._email = email;
|
this._email = email;
|
||||||
this._userid = userid;
|
this._userId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,39 +59,25 @@ class PublicKey {
|
|||||||
*put(options) {
|
*put(options) {
|
||||||
// parse key block
|
// parse key block
|
||||||
let publicKeyArmored = options.publicKeyArmored, primaryEmail = options.primaryEmail, origin = options.origin;
|
let publicKeyArmored = options.publicKeyArmored, primaryEmail = options.primaryEmail, origin = options.origin;
|
||||||
let params = this.parseKey(publicKeyArmored);
|
publicKeyArmored = publicKeyArmored.trim(); // remove whitespace
|
||||||
|
let params = this._parseKey(publicKeyArmored);
|
||||||
// check for existing verfied key by id or email addresses
|
// check for existing verfied key by id or email addresses
|
||||||
let verified = yield this._userid.getVerfied(params);
|
let verified = yield this._userId.getVerfied(params);
|
||||||
if (verified) {
|
if (verified) {
|
||||||
util.throw(304, 'Key for this user already exists: ' + JSON.stringify(verified));
|
util.throw(304, 'Key for this user already exists');
|
||||||
}
|
|
||||||
// delete old/unverified key and user ids with the same key id
|
|
||||||
yield this.remove({ keyid:params.keyid });
|
|
||||||
// persist new user ids
|
|
||||||
let userIds = yield this._userid.batch(params);
|
|
||||||
// persist new key
|
|
||||||
let r = yield this._mongo.create({ _id:params.keyid, publicKeyArmored }, DB_TYPE);
|
|
||||||
if (r.insertedCount !== 1) {
|
|
||||||
// rollback user ids
|
|
||||||
yield this.remove({ keyid:params.keyid });
|
|
||||||
util.throw(500, 'Failed to persist key');
|
|
||||||
}
|
}
|
||||||
|
// store key in database
|
||||||
|
let userIds = yield this._persisKey(publicKeyArmored, params);
|
||||||
// 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)
|
||||||
let primaryUserId = userIds.find(uid => uid.email === primaryEmail);
|
yield this._sendVerifyEmail(userIds, primaryEmail, origin);
|
||||||
if (primaryUserId) {
|
|
||||||
userIds = [primaryUserId];
|
|
||||||
}
|
|
||||||
for (let userId of userIds) {
|
|
||||||
yield this._email.send({ template:tpl.verifyKey, userId, origin });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse an ascii armored pgp key block and get its parameters.
|
* Parse an ascii armored pgp key block and get its parameters.
|
||||||
* @param {String} publicKeyArmored The ascii armored pgp key block
|
* @param {String} publicKeyArmored ascii armored pgp key block
|
||||||
* @return {Object} The key's id and user ids
|
* @return {Object} key's id and user ids
|
||||||
*/
|
*/
|
||||||
parseKey(publicKeyArmored) {
|
_parseKey(publicKeyArmored) {
|
||||||
let keys, userIds = [];
|
let keys, userIds = [];
|
||||||
try {
|
try {
|
||||||
keys = this._openpgp.key.readArmored(publicKeyArmored).keys;
|
keys = this._openpgp.key.readArmored(publicKeyArmored).keys;
|
||||||
@ -109,6 +95,45 @@ class PublicKey {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the public key and its user ids in the database.
|
||||||
|
* @param {String} publicKeyArmored ascii armored pgp key block
|
||||||
|
* @param {Object} params public key parameters
|
||||||
|
* @yield {Array} The persisted user id documents
|
||||||
|
*/
|
||||||
|
*_persisKey(publicKeyArmored, params) {
|
||||||
|
// delete old/unverified key and user ids with the same key id
|
||||||
|
yield this.remove({ keyid:params.keyid });
|
||||||
|
// persist new user ids
|
||||||
|
let userIds = yield this._userId.batch(params);
|
||||||
|
// persist new key
|
||||||
|
let r = yield this._mongo.create({ _id:params.keyid, publicKeyArmored }, DB_TYPE);
|
||||||
|
if (r.insertedCount !== 1) {
|
||||||
|
// rollback user ids
|
||||||
|
yield this.remove({ keyid:params.keyid });
|
||||||
|
util.throw(500, 'Failed to persist key');
|
||||||
|
}
|
||||||
|
return userIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send verification emails to the public keys user ids for verification.
|
||||||
|
* If a primary email address is provided only one email will be sent.
|
||||||
|
* @param {Array} userIds user id documents containg the verification nonces
|
||||||
|
* @param {string} primaryEmail the public key's primary email address
|
||||||
|
* @param {Object} origin the server's origin (required for email links)
|
||||||
|
* @yield {undefined}
|
||||||
|
*/
|
||||||
|
*_sendVerifyEmail(userIds, primaryEmail, origin) {
|
||||||
|
let primaryUserId = userIds.find(uid => uid.email === primaryEmail);
|
||||||
|
if (primaryUserId) {
|
||||||
|
userIds = [primaryUserId];
|
||||||
|
}
|
||||||
|
for (let userId of userIds) {
|
||||||
|
yield this._email.send({ template:tpl.verifyKey, userId, origin });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a verified public key from the database. Either the key id or the
|
* Fetch a verified public key from the database. Either the key id or the
|
||||||
* email address muss be provided.
|
* email address muss be provided.
|
||||||
@ -118,7 +143,7 @@ class PublicKey {
|
|||||||
*/
|
*/
|
||||||
*get(options) {
|
*get(options) {
|
||||||
let keyid = options.keyid, email = options.email;
|
let keyid = options.keyid, email = options.email;
|
||||||
let verified = yield this._userid.getVerfied({
|
let verified = yield this._userId.getVerfied({
|
||||||
keyid: keyid ? keyid.toUpperCase() : undefined,
|
keyid: keyid ? keyid.toUpperCase() : undefined,
|
||||||
userIds: email ? [{ email:email.toLowerCase() }] : undefined
|
userIds: email ? [{ email:email.toLowerCase() }] : undefined
|
||||||
});
|
});
|
||||||
@ -140,7 +165,10 @@ class PublicKey {
|
|||||||
*/
|
*/
|
||||||
*requestRemove(options) {
|
*requestRemove(options) {
|
||||||
let keyid = options.keyid, email = options.email, origin = options.origin;
|
let keyid = options.keyid, email = options.email, origin = options.origin;
|
||||||
let userIds = yield this._userid.flagForRemove({ keyid, email }, DB_TYPE);
|
let userIds = yield this._userId.flagForRemove({ keyid, email }, DB_TYPE);
|
||||||
|
if (!userIds.length) {
|
||||||
|
util.throw(404, 'User id not found');
|
||||||
|
}
|
||||||
for (let userId of userIds) {
|
for (let userId of userIds) {
|
||||||
yield this._email.send({ template:tpl.verifyRemove, userId, origin });
|
yield this._email.send({ template:tpl.verifyRemove, userId, origin });
|
||||||
}
|
}
|
||||||
@ -172,7 +200,7 @@ class PublicKey {
|
|||||||
// remove key document
|
// remove key document
|
||||||
yield this._mongo.remove({ _id:keyid }, DB_TYPE);
|
yield this._mongo.remove({ _id:keyid }, DB_TYPE);
|
||||||
// remove matching user id documents
|
// remove matching user id documents
|
||||||
yield this._userid.remove({ keyid });
|
yield this._userId.remove({ keyid });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
213
test/integration/public-key-test.js
Normal file
213
test/integration/public-key-test.js
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('co-mocha')(require('mocha')); // monkey patch mocha for generators
|
||||||
|
|
||||||
|
const log = require('npmlog');
|
||||||
|
const openpgp = require('openpgp');
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
const Email = require('../../src/email/email');
|
||||||
|
const Mongo = require('../../src/dao/mongo');
|
||||||
|
const UserId = require('../../src/service/user-id');
|
||||||
|
const PublicKey = require('../../src/service/public-key');
|
||||||
|
const expect = require('chai').expect;
|
||||||
|
const sinon = require('sinon');
|
||||||
|
|
||||||
|
describe('Public Key Integration Tests', function() {
|
||||||
|
this.timeout(20000);
|
||||||
|
|
||||||
|
let publicKey, email, mongo, userId,
|
||||||
|
sendEmailStub, publicKeyArmored, emailParams;
|
||||||
|
|
||||||
|
const DB_TYPE_PUB_KEY = 'publickey';
|
||||||
|
const DB_TYPE_USER_ID = 'userid';
|
||||||
|
const primaryEmail = 'safewithme.testuser@gmail.com';
|
||||||
|
const origin = { host:'localhost', protocol:'http' };
|
||||||
|
|
||||||
|
before(function *() {
|
||||||
|
publicKeyArmored = require('fs').readFileSync(__dirname + '/../key1.asc', 'utf8');
|
||||||
|
let credentials;
|
||||||
|
try {
|
||||||
|
credentials = require('../../credentials.json');
|
||||||
|
} catch(e) {
|
||||||
|
log.info('mongo-test', 'No credentials.json found ... using environment vars.');
|
||||||
|
}
|
||||||
|
mongo = new Mongo({
|
||||||
|
uri: process.env.MONGO_URI || credentials.mongo.uri,
|
||||||
|
user: process.env.MONGO_USER || credentials.mongo.user,
|
||||||
|
password: process.env.MONGO_PASS || credentials.mongo.pass
|
||||||
|
});
|
||||||
|
yield mongo.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function *() {
|
||||||
|
yield mongo.clear(DB_TYPE_PUB_KEY);
|
||||||
|
yield mongo.clear(DB_TYPE_USER_ID);
|
||||||
|
emailParams = null;
|
||||||
|
sendEmailStub = sinon.stub().returns(Promise.resolve({ response:'250' }));
|
||||||
|
sendEmailStub.withArgs(sinon.match(recipient => {
|
||||||
|
return recipient.to.address === primaryEmail;
|
||||||
|
}), sinon.match(params => {
|
||||||
|
emailParams = params;
|
||||||
|
return !!params.nonce;
|
||||||
|
}));
|
||||||
|
sinon.stub(nodemailer, 'createTransport').returns({
|
||||||
|
templateSender: () => { return sendEmailStub; }
|
||||||
|
});
|
||||||
|
email = new Email(nodemailer);
|
||||||
|
email.init({
|
||||||
|
host: 'localhost',
|
||||||
|
auth: { user:'user', pass:'pass' },
|
||||||
|
sender: { name:'Foo Bar', email:'foo@bar.com' }
|
||||||
|
});
|
||||||
|
userId = new UserId(mongo);
|
||||||
|
publicKey = new PublicKey(openpgp, mongo, email, userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
nodemailer.createTransport.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function *() {
|
||||||
|
yield mongo.clear(DB_TYPE_PUB_KEY);
|
||||||
|
yield mongo.clear(DB_TYPE_USER_ID);
|
||||||
|
yield mongo.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('put', () => {
|
||||||
|
it('should persist key and send verification email', function *() {
|
||||||
|
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||||
|
expect(emailParams.nonce).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work twice if not yet verified', function *() {
|
||||||
|
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||||
|
expect(emailParams.nonce).to.exist;
|
||||||
|
emailParams = null;
|
||||||
|
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||||
|
expect(emailParams.nonce).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw 304 if key already exists', function *() {
|
||||||
|
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||||
|
yield userId.verify(emailParams);
|
||||||
|
try {
|
||||||
|
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||||
|
expect(false).to.be.true;
|
||||||
|
} catch(e) {
|
||||||
|
expect(e.status).to.equal(304);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get', () => {
|
||||||
|
beforeEach(function *() {
|
||||||
|
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return verified key by key id', function *() {
|
||||||
|
yield userId.verify(emailParams);
|
||||||
|
let key = yield publicKey.get({ keyid:emailParams.keyid });
|
||||||
|
expect(key.publicKeyArmored).to.equal(publicKeyArmored);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return verified key by email address', function *() {
|
||||||
|
yield userId.verify(emailParams);
|
||||||
|
let key = yield publicKey.get({ email:primaryEmail });
|
||||||
|
expect(key.publicKeyArmored).to.equal(publicKeyArmored);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw 404 for unverified key', function *() {
|
||||||
|
try {
|
||||||
|
yield publicKey.get({ keyid:emailParams.keyid });
|
||||||
|
expect(false).to.be.true;
|
||||||
|
} catch(e) {
|
||||||
|
expect(e.status).to.equal(404);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('requestRemove', () => {
|
||||||
|
let keyid;
|
||||||
|
|
||||||
|
beforeEach(function *() {
|
||||||
|
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||||
|
keyid = emailParams.keyid;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for verified key', function *() {
|
||||||
|
yield userId.verify(emailParams);
|
||||||
|
emailParams = null;
|
||||||
|
yield publicKey.requestRemove({ keyid, origin });
|
||||||
|
expect(emailParams.nonce).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for unverified key', function *() {
|
||||||
|
emailParams = null;
|
||||||
|
yield publicKey.requestRemove({ keyid, origin });
|
||||||
|
expect(emailParams.nonce).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work by email address', function *() {
|
||||||
|
emailParams = null;
|
||||||
|
yield publicKey.requestRemove({ email:primaryEmail, origin });
|
||||||
|
expect(emailParams.nonce).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw 404 for no key', function *() {
|
||||||
|
yield publicKey.remove({ keyid });
|
||||||
|
try {
|
||||||
|
yield publicKey.requestRemove({ keyid, origin });
|
||||||
|
expect(false).to.be.true;
|
||||||
|
} catch(e) {
|
||||||
|
expect(e.status).to.equal(404);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('verifyRemove', () => {
|
||||||
|
let keyid;
|
||||||
|
|
||||||
|
beforeEach(function *() {
|
||||||
|
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||||
|
keyid = emailParams.keyid;
|
||||||
|
emailParams = null;
|
||||||
|
yield publicKey.requestRemove({ keyid, origin });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove key', function *() {
|
||||||
|
yield publicKey.verifyRemove(emailParams);
|
||||||
|
let uid = yield mongo.get({ keyid }, DB_TYPE_USER_ID);
|
||||||
|
expect(uid).to.not.exist;
|
||||||
|
let key = yield mongo.get({ _id:keyid }, DB_TYPE_PUB_KEY);
|
||||||
|
expect(key).to.not.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw 404 for no key', function *() {
|
||||||
|
yield publicKey.remove({ keyid });
|
||||||
|
try {
|
||||||
|
yield publicKey.verifyRemove(emailParams);
|
||||||
|
expect(false).to.be.true;
|
||||||
|
} catch(e) {
|
||||||
|
expect(e.status).to.equal(404);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remove', () => {
|
||||||
|
let keyid;
|
||||||
|
|
||||||
|
beforeEach(function *() {
|
||||||
|
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
|
||||||
|
keyid = emailParams.keyid;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove key', function *() {
|
||||||
|
yield publicKey.remove({ keyid });
|
||||||
|
let uid = yield mongo.get({ keyid }, DB_TYPE_USER_ID);
|
||||||
|
expect(uid).to.not.exist;
|
||||||
|
let key = yield mongo.get({ _id:keyid }, DB_TYPE_PUB_KEY);
|
||||||
|
expect(key).to.not.exist;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user