Merge pull request #41 from mailvelope/dev/app-refactor
Cleanup app/init/koa-middlewares
This commit is contained in:
commit
7f5ad65c61
8
index.js
8
index.js
@ -22,7 +22,7 @@ const numCPUs = require('os').cpus().length;
|
|||||||
const config = require('config');
|
const config = require('config');
|
||||||
const log = require('npmlog');
|
const log = require('npmlog');
|
||||||
|
|
||||||
log.level = config.log.level; // set log level depending on process.env.NODE_ENV
|
log.level = config.log.level;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Start worker cluster depending on number of CPUs
|
// Start worker cluster depending on number of CPUs
|
||||||
@ -32,13 +32,13 @@ if (cluster.isMaster) {
|
|||||||
for (let i = 0; i < numCPUs; i++) {
|
for (let i = 0; i < numCPUs; i++) {
|
||||||
cluster.fork();
|
cluster.fork();
|
||||||
}
|
}
|
||||||
cluster.on('fork', worker => log.info('cluster', 'Forked worker #%s [pid:%s]', worker.id, worker.process.pid));
|
cluster.on('fork', worker => log.info('cluster', `Forked worker #${worker.id} [pid:${worker.process.pid}]`));
|
||||||
cluster.on('exit', worker => {
|
cluster.on('exit', worker => {
|
||||||
log.warn('cluster', 'Worker #%s [pid:%s] died', worker.id, worker.process.pid);
|
log.warn('cluster', `Worker #${worker.id} [pid:${worker.process.pid}] died`);
|
||||||
setTimeout(() => cluster.fork(), 5000);
|
setTimeout(() => cluster.fork(), 5000);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
require('./src/app');
|
require('./src');
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
144
src/app.js
144
src/app.js
@ -1,144 +0,0 @@
|
|||||||
/**
|
|
||||||
* Mailvelope - secure email with OpenPGP encryption for Webmail
|
|
||||||
* Copyright (C) 2016 Mailvelope GmbH
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License version 3
|
|
||||||
* as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const Koa = require('koa');
|
|
||||||
const koaBody = require('koa-body');
|
|
||||||
const log = require('npmlog');
|
|
||||||
const config = require('config');
|
|
||||||
const serve = require('koa-static');
|
|
||||||
const router = require('koa-router')();
|
|
||||||
const util = require('./service/util');
|
|
||||||
const Mongo = require('./dao/mongo');
|
|
||||||
const Email = require('./email/email');
|
|
||||||
const PGP = require('./service/pgp');
|
|
||||||
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;
|
|
||||||
let publicKey;
|
|
||||||
let hkp;
|
|
||||||
let rest;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Configure koa HTTP server
|
|
||||||
//
|
|
||||||
|
|
||||||
// HKP routes
|
|
||||||
router.post('/pks/add', ctx => hkp.add(ctx));
|
|
||||||
router.get('/pks/lookup', ctx => hkp.lookup(ctx));
|
|
||||||
|
|
||||||
// REST api routes
|
|
||||||
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(async (ctx, next) => {
|
|
||||||
if (util.isTrue(config.server.httpsUpgrade) && util.checkHTTP(ctx)) {
|
|
||||||
ctx.redirect(`https://${ctx.hostname}${ctx.url}`);
|
|
||||||
} else {
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set HTTP response headers
|
|
||||||
app.use(async (ctx, next) => {
|
|
||||||
// HSTS
|
|
||||||
if (util.isTrue(config.server.httpsUpgrade)) {
|
|
||||||
ctx.set('Strict-Transport-Security', 'max-age=16070400');
|
|
||||||
}
|
|
||||||
// HPKP
|
|
||||||
if (config.server.httpsKeyPin && config.server.httpsKeyPinBackup) {
|
|
||||||
ctx.set('Public-Key-Pins', `pin-sha256="${config.server.httpsKeyPin}"; pin-sha256="${config.server.httpsKeyPinBackup}"; max-age=16070400`);
|
|
||||||
}
|
|
||||||
// CSP
|
|
||||||
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)
|
|
||||||
ctx.set('X-Frame-Options', 'DENY');
|
|
||||||
// CORS
|
|
||||||
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());
|
|
||||||
|
|
||||||
// serve static files
|
|
||||||
app.use(serve(`${__dirname}/static`));
|
|
||||||
|
|
||||||
app.on('error', (error, ctx) => {
|
|
||||||
if (error.status) {
|
|
||||||
log.verbose('app', 'Request faild: %s, %s', error.status, error.message);
|
|
||||||
} else {
|
|
||||||
log.error('app', 'Unknown error', error, ctx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
|
||||||
// Module initialization
|
|
||||||
//
|
|
||||||
|
|
||||||
function injectDependencies() {
|
|
||||||
mongo = new Mongo();
|
|
||||||
email = new Email();
|
|
||||||
pgp = new PGP();
|
|
||||||
publicKey = new PublicKey(pgp, mongo, email);
|
|
||||||
hkp = new HKP(publicKey);
|
|
||||||
rest = new REST(publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Start app ... connect to the database and start listening
|
|
||||||
//
|
|
||||||
|
|
||||||
if (!global.testing) { // don't automatically start server in tests
|
|
||||||
(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);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ...');
|
|
||||||
await mongo.init(config.mongo);
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = init;
|
|
67
src/app/index.js
Normal file
67
src/app/index.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Mailvelope - secure email with OpenPGP encryption for Webmail
|
||||||
|
* Copyright (C) 2016 Mailvelope GmbH
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License version 3
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Koa = require('koa');
|
||||||
|
const config = require('config');
|
||||||
|
const serve = require('koa-static');
|
||||||
|
const router = require('koa-router')();
|
||||||
|
const middleware = require('./middleware');
|
||||||
|
const Mongo = require('../dao/mongo');
|
||||||
|
const Email = require('../email/email');
|
||||||
|
const PGP = require('../service/pgp');
|
||||||
|
const PublicKey = require('../service/public-key');
|
||||||
|
const HKP = require('../route/hkp');
|
||||||
|
const REST = require('../route/rest');
|
||||||
|
|
||||||
|
const app = new Koa();
|
||||||
|
|
||||||
|
let hkp;
|
||||||
|
let rest;
|
||||||
|
|
||||||
|
// HKP and REST api routes
|
||||||
|
router.post('/pks/add', ctx => hkp.add(ctx));
|
||||||
|
router.get('/pks/lookup', ctx => hkp.lookup(ctx));
|
||||||
|
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));
|
||||||
|
|
||||||
|
// setup koa middlewares
|
||||||
|
app.on('error', middleware.logUnknownError);
|
||||||
|
app.use(middleware.upgradeToHTTPS);
|
||||||
|
app.use(middleware.setHTTPResponseHeaders);
|
||||||
|
app.use(middleware.parseBody());
|
||||||
|
app.use(router.routes());
|
||||||
|
app.use(router.allowedMethods());
|
||||||
|
app.use(serve(`${__dirname}/../static`));
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
// inject dependencies
|
||||||
|
const mongo = new Mongo();
|
||||||
|
const email = new Email();
|
||||||
|
const pgp = new PGP();
|
||||||
|
const publicKey = new PublicKey(pgp, mongo, email);
|
||||||
|
hkp = new HKP(publicKey);
|
||||||
|
rest = new REST(publicKey);
|
||||||
|
// init DAOs
|
||||||
|
email.init(config.email);
|
||||||
|
await mongo.init(config.mongo);
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = init;
|
65
src/app/middleware.js
Normal file
65
src/app/middleware.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Mailvelope - secure email with OpenPGP encryption for Webmail
|
||||||
|
* Copyright (C) 2016 Mailvelope GmbH
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License version 3
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const log = require('npmlog');
|
||||||
|
const config = require('config');
|
||||||
|
const koaBody = require('koa-body');
|
||||||
|
const util = require('../service/util');
|
||||||
|
|
||||||
|
exports.upgradeToHTTPS = async function(ctx, next) {
|
||||||
|
if (util.isTrue(config.server.httpsUpgrade) && util.checkHTTP(ctx)) {
|
||||||
|
ctx.redirect(`https://${ctx.hostname}${ctx.url}`);
|
||||||
|
} else {
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.setHTTPResponseHeaders = async function(ctx, next) {
|
||||||
|
// HSTS
|
||||||
|
if (util.isTrue(config.server.httpsUpgrade)) {
|
||||||
|
ctx.set('Strict-Transport-Security', 'max-age=16070400');
|
||||||
|
}
|
||||||
|
// HPKP
|
||||||
|
if (config.server.httpsKeyPin && config.server.httpsKeyPinBackup) {
|
||||||
|
ctx.set('Public-Key-Pins', `pin-sha256="${config.server.httpsKeyPin}"; pin-sha256="${config.server.httpsKeyPinBackup}"; max-age=16070400`);
|
||||||
|
}
|
||||||
|
// CSP
|
||||||
|
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)
|
||||||
|
ctx.set('X-Frame-Options', 'DENY');
|
||||||
|
// CORS
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.parseBody = () => koaBody({
|
||||||
|
multipart: true,
|
||||||
|
formLimit: '1mb'
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.logUnknownError = function(error, ctx) {
|
||||||
|
if (error.status) {
|
||||||
|
log.verbose('middleware', 'Request faild: %s, %s', error.status, error.message);
|
||||||
|
} else {
|
||||||
|
log.error('middleware', 'Unknown error', error, ctx);
|
||||||
|
}
|
||||||
|
};
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const log = require('npmlog');
|
||||||
const MongoClient = require('mongodb').MongoClient;
|
const MongoClient = require('mongodb').MongoClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,6 +32,7 @@ class Mongo {
|
|||||||
* @yield {undefined}
|
* @yield {undefined}
|
||||||
*/
|
*/
|
||||||
async init({uri, user, pass}) {
|
async init({uri, user, pass}) {
|
||||||
|
log.info('mongo', 'Connecting to MongoDB ...');
|
||||||
const url = `mongodb://${user}:${pass}@${uri}`;
|
const url = `mongodb://${user}:${pass}@${uri}`;
|
||||||
this._db = await MongoClient.connect(url);
|
this._db = await MongoClient.connect(url);
|
||||||
}
|
}
|
||||||
|
32
src/index.js
Normal file
32
src/index.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Mailvelope - secure email with OpenPGP encryption for Webmail
|
||||||
|
* Copyright (C) 2016 Mailvelope GmbH
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License version 3
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const log = require('npmlog');
|
||||||
|
const config = require('config');
|
||||||
|
const init = require('./app');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const app = await init();
|
||||||
|
app.listen(config.server.port);
|
||||||
|
log.info('app', `Listening on http://localhost:${config.server.port}`);
|
||||||
|
} catch (err) {
|
||||||
|
log.error('app', 'Initialization failed!', err);
|
||||||
|
}
|
||||||
|
})();
|
@ -25,6 +25,8 @@ describe('Koa App (HTTP Server) Integration Tests', function() {
|
|||||||
before(async () => {
|
before(async () => {
|
||||||
sandbox = sinon.sandbox.create();
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
|
sandbox.stub(log);
|
||||||
|
|
||||||
publicKeyArmored = fs.readFileSync(`${__dirname}/../key1.asc`, 'utf8');
|
publicKeyArmored = fs.readFileSync(`${__dirname}/../key1.asc`, 'utf8');
|
||||||
mongo = new Mongo();
|
mongo = new Mongo();
|
||||||
await mongo.init(config.mongo);
|
await mongo.init(config.mongo);
|
||||||
@ -39,9 +41,6 @@ describe('Koa App (HTTP Server) Integration Tests', function() {
|
|||||||
use() {}
|
use() {}
|
||||||
});
|
});
|
||||||
|
|
||||||
sandbox.stub(log);
|
|
||||||
|
|
||||||
global.testing = true;
|
|
||||||
const init = require('../../src/app');
|
const init = require('../../src/app');
|
||||||
app = await init();
|
app = await init();
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const log = require('npmlog');
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const Mongo = require('../../src/dao/mongo');
|
const Mongo = require('../../src/dao/mongo');
|
||||||
|
|
||||||
@ -7,9 +8,13 @@ describe('Mongo Integration Tests', function() {
|
|||||||
this.timeout(20000);
|
this.timeout(20000);
|
||||||
|
|
||||||
const DB_TYPE = 'apple';
|
const DB_TYPE = 'apple';
|
||||||
|
let sandbox;
|
||||||
let mongo;
|
let mongo;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
|
sandbox.stub(log);
|
||||||
|
|
||||||
mongo = new Mongo();
|
mongo = new Mongo();
|
||||||
await mongo.init(config.mongo);
|
await mongo.init(config.mongo);
|
||||||
});
|
});
|
||||||
@ -19,6 +24,7 @@ describe('Mongo Integration Tests', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
|
sandbox.restore();
|
||||||
await mongo.clear(DB_TYPE);
|
await mongo.clear(DB_TYPE);
|
||||||
await mongo.disconnect();
|
await mongo.disconnect();
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const log = require('npmlog');
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const nodemailer = require('nodemailer');
|
const nodemailer = require('nodemailer');
|
||||||
const Email = require('../../src/email/email');
|
const Email = require('../../src/email/email');
|
||||||
@ -28,6 +29,7 @@ describe('Public Key Integration Tests', function() {
|
|||||||
before(async () => {
|
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');
|
||||||
|
sinon.stub(log, 'info');
|
||||||
mongo = new Mongo();
|
mongo = new Mongo();
|
||||||
await mongo.init(config.mongo);
|
await mongo.init(config.mongo);
|
||||||
});
|
});
|
||||||
@ -67,6 +69,7 @@ describe('Public Key Integration Tests', function() {
|
|||||||
after(async () => {
|
after(async () => {
|
||||||
await mongo.clear(DB_TYPE);
|
await mongo.clear(DB_TYPE);
|
||||||
await mongo.disconnect();
|
await mongo.disconnect();
|
||||||
|
log.info.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('put', () => {
|
describe('put', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user