diff --git a/index.js b/index.js
index d42c166..fe6da9c 100644
--- a/index.js
+++ b/index.js
@@ -22,7 +22,7 @@ const numCPUs = require('os').cpus().length;
const config = require('config');
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
@@ -32,13 +32,13 @@ if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
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 => {
- 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);
});
} else {
- require('./src/app');
+ require('./src');
}
//
diff --git a/src/app.js b/src/app.js
deleted file mode 100644
index b4f7eb5..0000000
--- a/src/app.js
+++ /dev/null
@@ -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 .
- */
-
-'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;
diff --git a/src/app/index.js b/src/app/index.js
new file mode 100644
index 0000000..85ab831
--- /dev/null
+++ b/src/app/index.js
@@ -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 .
+ */
+
+'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;
diff --git a/src/app/middleware.js b/src/app/middleware.js
new file mode 100644
index 0000000..533ca1b
--- /dev/null
+++ b/src/app/middleware.js
@@ -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 .
+ */
+
+'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);
+ }
+};
diff --git a/src/dao/mongo.js b/src/dao/mongo.js
index d61d572..12f5170 100644
--- a/src/dao/mongo.js
+++ b/src/dao/mongo.js
@@ -17,6 +17,7 @@
'use strict';
+const log = require('npmlog');
const MongoClient = require('mongodb').MongoClient;
/**
@@ -31,6 +32,7 @@ class Mongo {
* @yield {undefined}
*/
async init({uri, user, pass}) {
+ log.info('mongo', 'Connecting to MongoDB ...');
const url = `mongodb://${user}:${pass}@${uri}`;
this._db = await MongoClient.connect(url);
}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..a5986bd
--- /dev/null
+++ b/src/index.js
@@ -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 .
+ */
+
+'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);
+ }
+})();
diff --git a/test/integration/app-test.js b/test/integration/app-test.js
index ae5445e..bd8a09e 100644
--- a/test/integration/app-test.js
+++ b/test/integration/app-test.js
@@ -25,6 +25,8 @@ describe('Koa App (HTTP Server) Integration Tests', function() {
before(async () => {
sandbox = sinon.sandbox.create();
+ sandbox.stub(log);
+
publicKeyArmored = fs.readFileSync(`${__dirname}/../key1.asc`, 'utf8');
mongo = new Mongo();
await mongo.init(config.mongo);
@@ -39,9 +41,6 @@ describe('Koa App (HTTP Server) Integration Tests', function() {
use() {}
});
- sandbox.stub(log);
-
- global.testing = true;
const init = require('../../src/app');
app = await init();
});
diff --git a/test/integration/mongo-test.js b/test/integration/mongo-test.js
index 9a9f9bb..ef20411 100644
--- a/test/integration/mongo-test.js
+++ b/test/integration/mongo-test.js
@@ -1,5 +1,6 @@
'use strict';
+const log = require('npmlog');
const config = require('config');
const Mongo = require('../../src/dao/mongo');
@@ -7,9 +8,13 @@ describe('Mongo Integration Tests', function() {
this.timeout(20000);
const DB_TYPE = 'apple';
+ let sandbox;
let mongo;
before(async () => {
+ sandbox = sinon.sandbox.create();
+ sandbox.stub(log);
+
mongo = new Mongo();
await mongo.init(config.mongo);
});
@@ -19,6 +24,7 @@ describe('Mongo Integration Tests', function() {
});
after(async () => {
+ sandbox.restore();
await mongo.clear(DB_TYPE);
await mongo.disconnect();
});
diff --git a/test/integration/public-key-test.js b/test/integration/public-key-test.js
index 24a26a7..cf5f8a0 100644
--- a/test/integration/public-key-test.js
+++ b/test/integration/public-key-test.js
@@ -1,5 +1,6 @@
'use strict';
+const log = require('npmlog');
const config = require('config');
const nodemailer = require('nodemailer');
const Email = require('../../src/email/email');
@@ -28,6 +29,7 @@ describe('Public Key Integration Tests', function() {
before(async () => {
publicKeyArmored = require('fs').readFileSync(`${__dirname}/../key3.asc`, 'utf8');
publicKeyArmored2 = require('fs').readFileSync(`${__dirname}/../key4.asc`, 'utf8');
+ sinon.stub(log, 'info');
mongo = new Mongo();
await mongo.init(config.mongo);
});
@@ -67,6 +69,7 @@ describe('Public Key Integration Tests', function() {
after(async () => {
await mongo.clear(DB_TYPE);
await mongo.disconnect();
+ log.info.restore();
});
describe('put', () => {