From 26eb38fb256f7119c5f85f6cd6e2b827ae3d99db Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 15 Aug 2016 21:15:16 -0400 Subject: [PATCH] lex v2.x --- README.md | 36 +++++++++-------- index.js | 12 ------ master.js | 118 +++++++++++++++++++----------------------------------- worker.js | 87 ---------------------------------------- 4 files changed, 60 insertions(+), 193 deletions(-) delete mode 100644 index.js delete mode 100644 worker.js diff --git a/README.md b/README.md index fe3945d..6a1486f 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ require('letsencrypt-express').create({ , agreeTos: true +, approvedDomains: [ 'example.com' ] + , app: require('express')().use('/', function (req, res) { res.end('Hello, World!'); }) @@ -134,7 +136,7 @@ var lex = require('letsencrypt-express').create({ // handles acme-challenge and redirects to https -require('http').createServer(lex.middleware()).listen(80, function () { +require('http').createServer(le.middleware()).listen(80, function () { console.log("Listening for ACME http-01 challenges on", this.address()); }); @@ -146,7 +148,7 @@ app.use('/', function (req, res) { }); // handles your app -require('https').createServer(lex.httpsOptions, lex.middleware(app)).listen(443, function () { +require('https').createServer(le.httpsOptions, le.middleware(app)).listen(443, function () { console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); }); ``` @@ -154,22 +156,22 @@ require('https').createServer(lex.httpsOptions, lex.middleware(app)).listen(443, API === -All options are passed directly to `node-letsencrypt`, -so `lex` is an instance of `letsencrypt`, but has a few -extra helper methods and options. +This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO). -See [node-letsencrypt options](https://github.com/Daplie/node-letsencrypt) +The API is actually located at [node-letsencrypt options](https://github.com/Daplie/node-letsencrypt) +(because all options are simply passed through to `node-letsencrypt` proper without modification). -* `lexOptions.approveDomains(options, certs, cb)` is special for `letsencrypt-express`, but will probably be included in `node-letsencrypt` in the future (no API change). +The only "API" consists of two options, the rest is just a wrapper around `node-letsencrypt` to take LOC from 15 to 5: -* `lexOptions.app` is just an elaborate ruse used for the Quickstart. It's sole purpose is to trim out 5 lines of code for setting http and https servers so that whiners won't whine. Real programmers don't use this. -* `leOptions.server` set to https://acme-v01.api.letsencrypt.org/directory in production -* `leOptions.email` useful for simple sites where there is only one owner. Leave this `null` and use `approveDomains` otherwise. -* `leOptions.agreeTos` useful for simple sites where there is only one owner. Leave this `null` and use `approveDomains` otherwise. -* `leOptions.renewWithin` is shared so that the worker knows how earlier to request a new cert -* `leOptions.renewBy` is passed to `le-sni-auto` so that it staggers renewals between `renewWithin` (latest) and `renewBy` (earlier) -* `lex.middleware(nextApp)` uses `letsencrypt/middleware` for GET-ing `http-01`, hence `sharedOptions.webrootPath` -* `lex.httpsOptions` has a default localhost certificate and the `SNICallback`. +* `opts.app` An express app in the format `function (req, res) { ... }` (no `next`). +* `lex.listen(plainPort, tlsPort)` Accepts port numbers (or arrays of port numbers) to listen on. -There are a few options that aren't shown in these examples, so if you need to change something -that isn't shown here, look at the code (it's not that much) or open an issue. +Brief overview of some simple options for `node-letsencrypt`: + +* `opts.server` set to https://acme-v01.api.letsencrypt.org/directory in production +* `opts.email` The default email to use to accept agreements. +* `opts.agreeTos` When set to `true`, this always accepts the LetsEncrypt TOS. When a string it checks the agreement url first. +* `opts.approvedDomains` An explicit array of The allowed domains (can be used instead of `approveDomains`). +* `opts.approveDomains` A callback for checking your database before allowing a domain `function (opts, certs, cb) { }` +* `opts.renewWithin` is the **maximum** number of days (in ms) before expiration to renew a certificate. +* `opts.renewBy` is the **minimum** number of days (in ms) before expiration to renew a certificate. diff --git a/index.js b/index.js deleted file mode 100644 index b5af499..0000000 --- a/index.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -console.error(""); -console.error("One does not simply require('letsencrypt-cluster');"); -console.error(""); -console.error("Usage:"); -console.error("\trequire('letsencrypt-cluster/master').create({ ... });"); -console.error("\trequire('letsencrypt-cluster/worker').create({ ... });"); -console.error(""); -console.error(""); - -process.exit(1); diff --git a/master.js b/master.js index 05f594b..6804003 100644 --- a/master.js +++ b/master.js @@ -1,91 +1,55 @@ 'use strict'; -// opts.addWorker(worker) // opts.approveDomains(options, certs, cb) module.exports.create = function (opts) { - opts = opts || { }; - opts._workers = []; - opts.webrootPath = opts.webrootPath || require('os').tmpdir() + require('path').sep + 'acme-challenge'; - if (!opts.letsencrypt) { opts.letsencrypt = require('letsencrypt').create(opts); } - if ('function' !== typeof opts.approveDomains) { - throw new Error("You must provide opts.approveDomains(domain, certs, callback) to approve certificates"); - } + // accept all defaults for le.challenges, le.store, le.middleware + var le = require('letsencrypt').create(opts); - function log(debug) { - if (!debug) { - return; + opts.app = opts.app || require('express')().use('/', function (req, res) { + res.end("Hello, World!\nWith Love,\nLet's Encrypt Express"); + }); + + opts.listen = function (plainPort, port) { + var PromiseA = require('bluebird'); + var promises = []; + var plainPorts = plainPort; + var ports = port; + var servers = []; + + if (!plainPorts || !ports) { + plainPorts = 80; + ports = 443; } - var args = Array.prototype.slice.call(arguments); - args.shift(); - args.unshift("[le/lib/core.js]"); - console.log.apply(console, args); - } + if (!Array.isArray(plainPorts)) { + plainPorts = [ plainPorts ]; + ports = [ ports ]; + } - opts.addWorker = function (worker) { - opts._workers.push(worker); - - worker.on('online', function () { - log(opts.debug, 'worker is up'); + plainPorts.forEach(function (p) { + promises.push(new PromiseA(function (resolve, reject) { + require('http').createServer(le.middleware(require('https-redirect').create())).listen(p, function () { + resolve(); + }).on('error', reject); + })); }); - worker.on('message', function (msg) { - log(opts.debug, 'Message from worker ' + worker.id); - if ('LE_REQUEST' !== (msg && msg.type)) { - log(opts.debug, 'Ignoring irrelevant message'); - log(opts.debug, msg); - return; - } - - log(opts.debug, 'about to approveDomains'); - opts.approveDomains(msg.options, msg.certs, function (err, results) { - if (err) { - log(opts.debug, 'Approval got ERROR', err.stack || err); - worker.send({ - type: 'LE_RESPONSE' - , domain: msg.domain - , error: { message: err.message, code: err.code, stack: err.stack } - }); - return; - } - - var promise; - - // - /* - var certs = require('localhost.daplie.com-certificates').merge({ - subject: msg.domain - , altnames: [ msg.domain ] - , issuedAt: Date.now() - , expiresAt: Date.now() + (90 * 24 * 60 * 60 * 1000) - }); - certs.privkey = certs.key.toString('ascii'); - certs.cert = certs.cert.toString('ascii'); - certs.chain = ''; - worker.send({ type: 'LE_RESPONSE', domain: msg.domain, certs: certs }); - return; - // */ - - if (results.certs) { - promise = opts.letsencrypt.renew(results.options, results.certs); - } - else { - promise = opts.letsencrypt.register(results.options); - } - - promise.then(function (certs) { - log(opts.debug, 'Approval got certs', certs); - // certs = { subject, domains, issuedAt, expiresAt, privkey, cert, chain }; - opts._workers.forEach(function (w) { - w.send({ type: 'LE_RESPONSE', domain: msg.domain, certs: certs }); - }); - }, function (err) { - log(opts.debug, 'Approval got ERROR', err.stack || err); - worker.send({ type: 'LE_RESPONSE', domain: msg.domain, error: err }); - }); - }); + ports.forEach(function (p) { + promises.push(new PromiseA(function (resolve, reject) { + var server = require('https').createServer(le.httpsOptions, le.middleware(le.app)).listen(p, function () { + resolve(); + }).on('error', reject); + servers.push(server); + })); }); + + if (!Array.isArray(port)) { + servers = servers[0]; + } + + return servers; }; - return opts; + + return le; }; diff --git a/worker.js b/worker.js deleted file mode 100644 index 73b9732..0000000 --- a/worker.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -function log(debug) { - if (!debug) { - return; - } - - var args = Array.prototype.slice.call(arguments); - args.shift(); - args.unshift("[le/lib/core.js]"); - console.log.apply(console, args); -} - - - -module.exports.create = function (opts) { - - // if another worker updates the certs, - // receive a copy from master here as well - // and update the sni cache manually - process.on('message', function (msg) { - if ('LE_RESPONSE' === msg.type && msg.certs) { - opts.sni.cacheCerts(msg.certs); - } - }); - - opts.sni = require('le-sni-auto').create({ - renewWithin: opts.renewWithin || (10 * 24 * 60 * 60 * 1000) - , renewBy: opts.renewBy || (5 * 24 * 60 * 60 * 1000) - , getCertificates: function (domain, certs, cb) { - var workerOptions = { domains: [ domain ] }; - opts.approveDomains(workerOptions, certs, function (_err, results) { - if (_err) { - cb(_err); - return; - } - - var err = new Error("___MESSAGE___"); - process.send({ type: 'LE_REQUEST', domain: domain, options: results.options, certs: results.certs }); - - process.on('message', function (msg) { - log(opts.debug, 'Message from master'); - log(opts.debug, msg); - - if (msg.domain !== domain) { - return; - } - - if (msg.error) { - err.message = msg.error.message || "unknown error sent from cluster master to worker"; - err.stack.replace("___MESSAGE___", err.message); - err = { - message: err.message - , stack: err.stack - , data: { options: workerOptions, certs: certs } - }; - } else { - err = null; - } - - cb(err, msg.certs); - }); - }); - } - }); - - - - opts.httpsOptions = require('localhost.daplie.com-certificates').merge({ SNICallback: opts.sni.sniCallback }); - - - - opts.challenge = { - get: opts.getChallenge - || (opts.challenge && opts.challenge.get) - || require('le-challenge-fs').create({ webrootPath: opts.webrootPath }).get - }; - - - - // opts.challenge.get, opts.acmeChallengePrefix - opts.middleware = require('letsencrypt/lib/middleware').create(opts); - - - - return opts; -};