From 82fcb819e95d4c4229c0141aad081735ff3f7965 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Thu, 26 May 2016 13:45:32 +0200 Subject: [PATCH] Design class architecture and HTTP api --- src/ctrl/public-key.js | 42 +++++++++++++++++++++++++ src/dao/email.js | 29 +++++++++++++++++ src/routes/hkp.js | 70 ++++++++++++++++++++---------------------- src/routes/rest.js | 56 +++++++++++++++++++++++++++++++++ src/worker.js | 46 ++++++++++++++++++++++++--- 5 files changed, 201 insertions(+), 42 deletions(-) create mode 100644 src/dao/email.js create mode 100644 src/routes/rest.js diff --git a/src/ctrl/public-key.js b/src/ctrl/public-key.js index 704f025..2c2b6d4 100644 --- a/src/ctrl/public-key.js +++ b/src/ctrl/public-key.js @@ -17,6 +17,16 @@ 'use strict'; +/** + * Database documents have the format: + * { + * _id: "02C134D079701934", // the 16 byte key id + * email: "jon@example.com", // the primary and verified email address + * publicKeyArmored: "-----BEGIN PGP PUBLIC KEY BLOCK----- ... -----END PGP PUBLIC KEY BLOCK-----" + * } + */ +const DB_TYPE = 'publickey'; + /** * A controller that handlers PGP public keys queries to the database */ @@ -30,6 +40,38 @@ class PublicKey { this._mongo = mongo; } + // + // Create/Update + // + + put(options) { + + } + + verify(options) { + + } + + // + // Read + // + + get(options) { + + } + + // + // Delete + // + + remove(options) { + + } + + verifyRemove(options) { + + } + } module.exports = PublicKey; \ No newline at end of file diff --git a/src/dao/email.js b/src/dao/email.js new file mode 100644 index 0000000..eb1e30d --- /dev/null +++ b/src/dao/email.js @@ -0,0 +1,29 @@ +/** + * 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'; + +/** + * A simple wrapper around Nodemailer to send verification emails + */ +class Email { + + send(options) { + + } + +} \ No newline at end of file diff --git a/src/routes/hkp.js b/src/routes/hkp.js index e8cdf00..e3bae3e 100644 --- a/src/routes/hkp.js +++ b/src/routes/hkp.js @@ -31,30 +31,35 @@ class HKP { this._publicKey = publicKey; } - /** - * Public key lookup via http GET - * @param {Object} ctx The koa request/response context - */ - *lookup(ctx) { - var params = this.parseQueryString(ctx); - if (!params) { - return; // invalid request - } - - this.setHeaders(ctx); - if (params.mr) { - this.setGetMRHEaders(ctx); - } - ctx.body = yield Promise.resolve('----- BEGIN PUBLIC PGP KEY -----'); - } - /** * Public key upload via http POST * @param {Object} ctx The koa request/response context */ *add(ctx) { ctx.throw(501, 'Not implemented!'); - return yield Promise.resolve(); + yield; + } + + /** + * Public key lookup via http GET + * @param {Object} ctx The koa request/response context + */ + *lookup(ctx) { + let params = this.parseQueryString(ctx); + if (!params) { + return; // invalid request + } + + let key = yield this._publicKey.get(params); + if (key) { + ctx.body = key.publicKeyArmored; + if (params.mr) { + this.setGetMRHEaders(ctx); + } + } else { + ctx.status = 404; + ctx.body = 'Not found!'; + } } /** @@ -64,20 +69,22 @@ class HKP { * @return {Object} The query parameters or undefined for an invalid request */ parseQueryString(ctx) { - let q = ctx.query; let params = { - op: q.op, // operation ... only 'get' is supported - mr: q.options === 'mr', // machine readable - keyid: this.checkId(q.search) ? q.search.replace('0x', '') : null, - email: this.checkEmail(q.search) ? q.search : null, + op: ctx.query.op, // operation ... only 'get' is supported + mr: ctx.query.options === 'mr' // machine readable }; + if (this.checkId(ctx.query.search)) { + params._id = ctx.query.search.replace(/^0x/, ''); + } else if(this.checkEmail(ctx.query.search)) { + params.email = ctx.query.search; + } if (params.op !== 'get') { ctx.status = 501; ctx.body = 'Not implemented!'; return; - } else if (!params.keyid && !params.email) { - ctx.status = 404; + } else if (!params._id && !params.email) { + ctx.status = 400; ctx.body = 'Invalid request!'; return; } @@ -101,18 +108,7 @@ class HKP { * @return {Boolean} If the key id is valid */ checkId(keyid) { - return /^0x[a-fA-F0-9]{8,40}/.test(keyid); - } - - /** - * Set HTTP headers for the HKP requests. - * @param {Object} ctx The koa request/response context - */ - setHeaders(ctx) { - ctx.set('Access-Control-Allow-Origin', '*'); - ctx.set('Cache-Control', 'no-cache'); - ctx.set('Pragma', 'no-cache'); - ctx.set('Connection', 'keep-alive'); + return /^0x[a-fA-F0-9]{8,40}$/.test(keyid); } /** diff --git a/src/routes/rest.js b/src/routes/rest.js new file mode 100644 index 0000000..d42299b --- /dev/null +++ b/src/routes/rest.js @@ -0,0 +1,56 @@ +/** + * 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'; + +/** + * The REST api to provide additional functionality on top of HKP + */ +class REST { + + constructor(publicKey) { + this._publicKey = publicKey; + } + + *create(ctx) { + ctx.throw(501, 'Not implemented!'); + yield; + } + + *verify(ctx) { + ctx.throw(501, 'Not implemented!'); + yield; + } + + *read(ctx) { + ctx.throw(501, 'Not implemented!'); + yield; + } + + *remove(ctx) { + ctx.throw(501, 'Not implemented!'); + yield; + } + + *verifyRemove(ctx) { + ctx.throw(501, 'Not implemented!'); + yield; + } + +} + +module.exports = REST; \ No newline at end of file diff --git a/src/worker.js b/src/worker.js index 45fe4e1..52e70bf 100644 --- a/src/worker.js +++ b/src/worker.js @@ -25,18 +25,53 @@ const router = require('koa-router')(); const Mongo = require('./dao/mongo'); const PublicKey = require('./ctrl/public-key'); const HKP = require('./routes/hkp'); +const REST = require('./routes/rest'); -let mongo, publicKey, hkp; +let mongo, publicKey, hkp, rest; // -// Configure koa router +// Configure koa HTTP server // -router.get('/pks/lookup', function *() { +// HKP routes +router.post('/pks/add', function *() { // no query params + yield hkp.add(this); +}); +router.get('/pks/lookup', function *() { // ?op=get&search=0x1234567890123456 yield hkp.lookup(this); }); -router.post('/pks/add', function *() { - yield hkp.add(this); + +// REST api routes +router.post('/api/key', function *() { // no query params + yield rest.create(this); +}); +router.get('/api/key', function *() { // ?id=keyid OR ?email=email + yield rest.read(this); +}); +router.del('/api/key', function *() { // ?id=keyid OR ?email=email + yield rest.remove(this); +}); + +// links for verification and sharing +router.get('/api/verify', function *() { // ?id=keyid&nonce=nonce + yield rest.verify(this); +}); +router.get('/api/verifyRemove', function *() { // ?id=keyid&nonce=nonce + yield rest.verifyRemove(this); +}); +router.get('/:email', function *() { // shorthand link for sharing + yield rest.read(this); +}); + +// Set HTTP response headers +app.use(function *(next) { + 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('Cache-Control', 'no-cache'); + this.set('Pragma', 'no-cache'); + this.set('Connection', 'keep-alive'); + yield next; }); app.use(router.routes()); @@ -56,6 +91,7 @@ function injectDependencies() { }); publicKey = new PublicKey(mongo); hkp = new HKP(publicKey); + rest = new REST(publicKey); } function readCredentials() {