From d5bd65b4bcb62bf834e0855704da3d4994aefdd6 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Fri, 10 Jun 2016 12:06:08 +0200 Subject: [PATCH] Cleanup url handling --- config/production.js | 6 +++++- src/app.js | 3 ++- src/email/email.js | 2 +- src/route/hkp.js | 2 +- src/route/home.js | 41 +++++++++++++++++++++++++--------------- src/route/rest.js | 4 ++-- src/service/util.js | 43 ++++++++++++++++++++++++++++++++++++++++-- test/unit/util-test.js | 16 ++++++++++++++-- 8 files changed, 92 insertions(+), 25 deletions(-) diff --git a/config/production.js b/config/production.js index a8dba5b..70be3ca 100644 --- a/config/production.js +++ b/config/production.js @@ -2,6 +2,10 @@ module.exports = { log: { level: 'error' - } + }, + + server: { + upgradeHTTP: true + }, }; \ No newline at end of file diff --git a/src/app.js b/src/app.js index d8f289d..0857a0c 100644 --- a/src/app.js +++ b/src/app.js @@ -22,6 +22,7 @@ const app = require('koa')(); const log = require('npmlog'); const config = require('config'); 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'); @@ -85,7 +86,7 @@ app.use(function *(next) { // Redirect all http traffic to https app.use(function *(next) { - if (process.env.NODE_ENV === 'production' && !this.secure && this.get('X-Forwarded-Proto') === 'http') { + if (config.server.upgradeHTTP && util.checkHTTP(this)) { this.redirect('https://' + this.hostname + this.url); } else { yield next; diff --git a/src/email/email.js b/src/email/email.js index c6101d8..0a28dad 100644 --- a/src/email/email.js +++ b/src/email/email.js @@ -69,7 +69,7 @@ class Email { html: template.html, params: { name: userId.name, - baseUrl: origin.protocol + '://' + origin.host, + baseUrl: util.url(origin), keyId: encodeURIComponent(keyId), nonce: encodeURIComponent(userId.nonce) } diff --git a/src/route/hkp.js b/src/route/hkp.js index 78c6364..29ffba1 100644 --- a/src/route/hkp.js +++ b/src/route/hkp.js @@ -44,7 +44,7 @@ class HKP { if (!publicKeyArmored) { ctx.throw(400, 'Invalid request!'); } - let origin = util.getOrigin(ctx); + let origin = util.origin(ctx); yield this._publicKey.put({ publicKeyArmored, origin }); ctx.status = 201; } diff --git a/src/route/home.js b/src/route/home.js index 65e9b89..d8e56ec 100644 --- a/src/route/home.js +++ b/src/route/home.js @@ -1,23 +1,34 @@ 'use strict'; +const util = require('../service/util'); + module.exports = function () { - let tls = this.secure || process.env.NODE_ENV === 'production' && this.get('X-Forwarded-Proto') === 'https'; - let hkp = (tls ? 'hkps://' : 'hkp://') + this.host; - let del = (tls ? 'https://' : 'http://') + this.host + '/api/v1/removeKey?email=user@example.com'; + let hkpLink = util.hkpUrl(this); + let removeLink = util.url(util.origin(this), '/api/v1/removeKey?email=user@example.com'); this.body = ` -

Welcome to the OpenPGP key server

-

This server verifies email address as well as private key ownership by sending an encrypted verification email.

-

Try it out

-
    -
  1. Configure this key server in your HKP compatible OpenPGP client using this url: ${hkp}
  2. -
  3. Now just upload a public key like you always do.
  4. -
  5. Check your inbox and click on the verification link inside the encrypted message.
  6. -
  7. You can delete all your data from the server at any time using this link: ${del}
  8. -
-

Documentation and code

-

Please refer to the documentation to learn more about the api.

-

License AGPL v3.0

+ + + + + OpenPGP key server + + + +

Welcome to the OpenPGP key server

+

This server verifies email address as well as private key ownership by sending an encrypted verification email.

+

Try it out

+
    +
  1. Configure this key server in your HKP compatible OpenPGP client using this url: ${hkpLink}
  2. +
  3. Now just upload a public key like you always do.
  4. +
  5. Check your inbox and click on the verification link inside the encrypted message.
  6. +
  7. You can delete all your data from the server at any time using this link: ${removeLink}
  8. +
+

Documentation and code

+

Please refer to the documentation to learn more about the api.

+

License AGPL v3.0

+ + `; this.set('Content-Type', 'text/html; charset=utf-8'); diff --git a/src/route/rest.js b/src/route/rest.js index 35b8c98..dbb094d 100644 --- a/src/route/rest.js +++ b/src/route/rest.js @@ -44,7 +44,7 @@ class REST { if (!publicKeyArmored || (primaryEmail && !util.isEmail(primaryEmail))) { ctx.throw(400, 'Invalid request!'); } - let origin = util.getOrigin(ctx); + let origin = util.origin(ctx); yield this._publicKey.put({ publicKeyArmored, primaryEmail, origin }); ctx.status = 201; } @@ -91,7 +91,7 @@ class REST { * @param {Object} ctx The koa request/response context */ *remove(ctx) { - let q = { keyId:ctx.query.keyId, email:ctx.query.email, origin:util.getOrigin(ctx) }; + let q = { keyId:ctx.query.keyId, email:ctx.query.email, origin:util.origin(ctx) }; if (!util.isKeyId(q.keyId) && !util.isEmail(q.email)) { ctx.throw(400, 'Invalid request!'); } diff --git a/src/service/util.js b/src/service/util.js index 7de9519..e15ee20 100644 --- a/src/service/util.js +++ b/src/service/util.js @@ -102,6 +102,25 @@ exports.random = function(bytes) { return crypto.randomBytes(bytes).toString('hex'); }; +/** + * Check if the user is connecting over a plaintext http connection. + * This can be used as an indicator to upgrade their connection to https. + * @param {Object} ctx The koa request/repsonse context + * @return {boolean} If http is used + */ +exports.checkHTTP = function(ctx) { + return !ctx.secure && ctx.get('X-Forwarded-Proto') === 'http'; +}; + +/** + * Check if the user is connecting over a https connection. + * @param {Object} ctx The koa request/repsonse context + * @return {boolean} If https is used + */ +exports.checkHTTPS = function(ctx) { + return ctx.secure || ctx.get('X-Forwarded-Proto') === 'https'; +}; + /** * Get the server's own origin host and protocol. Required for sending * verification links via email. If the PORT environmane variable @@ -110,9 +129,29 @@ exports.random = function(bytes) { * @param {Object} ctx The koa request/repsonse context * @return {Object} The server origin */ -exports.getOrigin = function(ctx) { +exports.origin = function(ctx) { return { - protocol: process.env.PORT ? 'https' : ctx.protocol, + protocol: this.checkHTTPS(ctx) ? 'https' : ctx.protocol, host: ctx.host }; +}; + +/** + * Helper to create urls pointing to this server + * @param {Object} origin The server's origin + * @param {string} resource (optional) The resource to point to + * @return {string} The complete url + */ +exports.url = function(origin, resource) { + return origin.protocol + '://' + origin.host + (resource || ''); +}; + +/** + * Helper to create a url for hkp clients to connect to this server via + * the hkp protocol. + * @param {Object} ctx The koa request/repsonse context + * @return {string} The complete url + */ +exports.hkpUrl = function(ctx) { + return (this.checkHTTPS(ctx) ? 'hkps://' : 'hkp://') + ctx.host; }; \ No newline at end of file diff --git a/test/unit/util-test.js b/test/unit/util-test.js index e8f9cf7..ec7a5ee 100644 --- a/test/unit/util-test.js +++ b/test/unit/util-test.js @@ -135,9 +135,21 @@ describe('Util Unit Tests', () => { }); }); - describe('getOrigin', () => { + describe('origin', () => { it('should work', () => { - expect(util.getOrigin({host:'h', protocol:'p'})).to.exist; + expect(util.origin({ secure:true, host:'h', protocol:'p' })).to.exist; + }); + }); + + describe('url', () => { + it('should work with resource', () => { + let url = util.url({ host:'localhost', protocol:'http'}, '/foo'); + expect(url).to.equal('http://localhost/foo'); + }); + + it('should work without resource', () => { + let url = util.url({ host:'localhost', protocol:'http'}); + expect(url).to.equal('http://localhost'); }); });