Migrate to koa 2
Refactor rest api to async/await
This commit is contained in:
parent
3dfa447fcf
commit
49b24a5cb4
73
src/app.js
73
src/app.js
@ -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 {
|
||||||
|
const app = await init();
|
||||||
app.listen(config.server.port);
|
app.listen(config.server.port);
|
||||||
log.info('app', `Ready to rock! Listening on http://localhost:${config.server.port}`);
|
log.info('app', `Ready to rock! Listening on http://localhost:${config.server.port}`);
|
||||||
}).catch(err => log.error('app', 'Initialization failed!', err));
|
} 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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!';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user