diff --git a/README.md b/README.md index 0ed0f29..bb446ef 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Install one for managing certificate storage and the other for handling ACME challenges. The default storage plugin is [`le-store-certbot`](https://github.com/Daplie/le-store-certbot) -and the default challenger is [`le-challenge-fs`](https://github.com/Daplie/le-challenge-fs). +and the default challenge is [`le-challenge-fs`](https://github.com/Daplie/le-challenge-fs). ```bash npm install --save letsencrypt@2.x @@ -108,7 +108,7 @@ var leStore = require('le-store-certbot').create({ // ACME Challenge Handlers -var leChallenger = require('le-challenge-fs').create({ +var leChallenge = require('le-challenge-fs').create({ webrootPath: '~/letsencrypt/var/' // or template string such as , debug: false // '/srv/www/:hostname/.well-known/acme-challenge' }); @@ -122,7 +122,7 @@ function leAgree(opts, agreeCb) { le = LE.create({ server: LE.stagingServerUrl // or LE.productionServerUrl , store: leStore // handles saving of config, accounts, and certificates -, challenger: leChallenger // handles /.well-known/acme-challege keys and tokens +, challenge: leChallenge // handles /.well-known/acme-challege keys and tokens , agreeToTerms: leAgree // hook to allow user to view and accept LE TOS , debug: false }); diff --git a/index.js b/index.js index 7cc6b7f..a59a8ac 100644 --- a/index.js +++ b/index.js @@ -27,7 +27,7 @@ var u; // undefined LE._undefined = { acme: u , store: u -, challenger: u +, challenge: u , register: u , check: u @@ -40,6 +40,8 @@ LE._undefined = { , server: u , agreeToTerms: u , _ipc: u +, duplicate: u +, _acmeUrls: u }; LE._undefine = function (le) { Object.keys(LE._undefined).forEach(function (key) { @@ -55,7 +57,7 @@ LE.create = function (le) { le.acme = le.acme || ACME.create({ debug: le.debug }); le.store = le.store || require('le-store-certbot').create({ debug: le.debug }); - le.challenger = le.challenger || require('le-store-certbot').create({ debug: le.debug }); + le.challenge = le.challenge || require('le-challenge-certbot').create({ debug: le.debug }); le.core = require('./lib/core'); le = LE._undefine(le); @@ -102,14 +104,14 @@ LE.create = function (le) { } }); - if (le.challenger.create) { - le.challenger = le.challenger.create(le); + if (le.challenge.create) { + le.challenge = le.challenge.create(le); } - le.challenger = PromiseA.promisifyAll(le.challenger); - le._challengerOpts = le.challenger.getOptions(); - Object.keys(le._challengerOpts).forEach(function (key) { + le.challenge = PromiseA.promisifyAll(le.challenge); + le._challengeOpts = le.challenge.getOptions(); + Object.keys(le._challengeOpts).forEach(function (key) { if (!(key in le)) { - le[key] = le._challengerOpts[key]; + le[key] = le._challengeOpts[key]; } }); @@ -126,9 +128,10 @@ LE.create = function (le) { return le.core.certificates.checkAsync(args); }; - le.middleware = function () { - return require('./lib/middleware')(le); - }; + le.middleware = le.middleware || require('./lib/middleware'); + if (le.middleware.create) { + le.middleware = le.middleware.create(le); + } return le; }; diff --git a/lib/middleware.js b/lib/middleware.js index 13af19c..6efe68b 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -1,30 +1,53 @@ 'use strict'; -module.exports = function (le) { +var utils = require('./utils'); + +function log(debug) { + if (debug) { + var args = Array.prototype.slice.call(arguments); + args.shift(); + args.unshift("[le/lib/middleware.js]"); + console.log.apply(console, args); + } +} + +module.exports.create = function (le) { + if (!le.challenge || !le.challenge.get) { + throw new Error("middleware requires challenge plugin with get method"); + } + + log(le.debug, "created middleware"); return function () { var prefix = le.acmeChallengePrefix; // /.well-known/acme-challenge/:token return function (req, res, next) { if (0 !== req.url.indexOf(prefix)) { + log(le.debug, "no match, skipping middleware"); next(); return; } - var key = req.url.slice(prefix.length); + log(le.debug, "this must be tinder, 'cuz it's a match!"); + + var token = req.url.slice(prefix.length); var hostname = req.hostname || (req.headers.host || '').toLowerCase().replace(/:*/, ''); + log(le.debug, "hostname", hostname, "token", token); + + var copy = utils.merge({}, le); + copy = utils.tplCopy(copy); + // TODO tpl copy? - le.challenger.getAsync(le, hostname, key).then(function (token) { - if (!token) { - res.status = 404; - res.send("Error: These aren't the tokens you're looking for. Move along."); + le.challenge.get(copy, hostname, token, function (err, secret) { + if (err || !token) { + res.statusCode = 404; + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.end('{ "error": { "message": "Error: These aren\'t the tokens you\'re looking for. Move along." } }'); return; } - res.send(token); - }, function (/*err*/) { - res.status = 404; - res.send("Error: These aren't the tokens you're looking for. Move along."); + res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + res.end(secret); }); }; }; diff --git a/tests/challenge-middleware.js b/tests/challenge-middleware.js new file mode 100644 index 0000000..625b27f --- /dev/null +++ b/tests/challenge-middleware.js @@ -0,0 +1,102 @@ +'use strict'; + +var PromiseA = require('bluebird'); +var requestAsync = PromiseA.promisify(require('request')); +var LE = require('../').LE; +var le = LE.create({ + server: 'staging' +, acme: require('le-acme-core').ACME.create() +, store: require('le-store-certbot').create({ + configDir: '~/letsencrypt.test/etc' + , webrootPath: '~/letsencrypt.test/var/:hostname' + }) +, challenge: require('le-challenge-fs').create({ + webrootPath: '~/letsencrypt.test/var/:hostname' + }) +, debug: true +}); +var utils = require('../lib/utils'); + +if ('/.well-known/acme-challenge/' !== LE.acmeChallengePrefix) { + throw new Error("Bad constant 'acmeChallengePrefix'"); +} + +var baseUrl; +var domain = 'example.com'; +var token = 'token-id'; +var secret = 'key-secret'; + +var tests = [ + function () { + console.log('Test Url:', baseUrl + token); + return requestAsync({ url: baseUrl + token }).then(function (req) { + if (404 !== req.statusCode) { + console.log(req.statusCode); + throw new Error("Should be status 404"); + } + }); + } + +, function () { + var copy = utils.merge({}, le); + copy = utils.tplCopy(copy); + return PromiseA.promisify(le.challenge.set)(copy, domain, token, secret); + } + +, function () { + return requestAsync(baseUrl + token).then(function (req) { + if (200 !== req.statusCode) { + console.log(req.statusCode, req.body); + throw new Error("Should be status 200"); + } + + if (req.body !== secret) { + console.error(token, secret, req.body); + throw new Error("req.body should be secret"); + } + }); + } + +, function () { + var copy = utils.merge({}, le); + copy = utils.tplCopy(copy); + return PromiseA.promisify(le.challenge.remove)(copy, domain, token); + } + +, function () { + return requestAsync(baseUrl + token).then(function (req) { + if (404 !== req.statusCode) { + console.log(req.statusCode); + throw new Error("Should be status 404"); + } + }); + } +]; + +function run() { + //var express = require(express); + var server = require('http').createServer(le.middleware()); + server.listen(0, function () { + console.log('Server running, proceeding to test.'); + baseUrl = 'http://localhost.daplie.com:' + server.address().port + LE.acmeChallengePrefix; + + function next() { + var test = tests.shift(); + if (!test) { + console.info('All tests passed'); + server.close(); + return; + } + + test().then(next, function (err) { + console.error('ERROR'); + console.error(err.stack); + server.close(); + }); + } + + next(); + }); +} + +run();