diff --git a/README.md b/README.md index efd2fbe..741faa7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # letsencrypt-express + Free SSL and Automatic HTTPS for node.js with Express, Connect, and other middleware systems ## Coming Soon @@ -9,6 +10,78 @@ We're working on it See [examples/express-minimal.js](https://github.com/Daplie/node-letsencrypt/blob/master/examples/express-minimal.js) +## Install + +``` +npm install --save letsencrypt-express +``` + +## Examples + +**Minimal** + +```javascript +'use strict'; + +var le = require('letsencrypt-express'); +var express = require('express'); +var app = express(); + +app.use('/', function (req, res) { + res.send({ success: true }); +}); + +le.create('/etc/letsencrypt', app).listen([80], [443, 5001], function () { + console.log("ENCRYPT **ALL** THE DOMAINS!"); +}); +``` + +### More Options Exposed + +```javascript +'use strict'; + +var le = require('letsencrypt-express'); +var express = require('express'); +var app = express(); + +app.use('/', function (req, res) { + res.send({ success: true }); +}); + +var results = le.create({ + configDir: '/etc/letsencrypt' +, onRequest: app +}).listen( + + // you can give just the port, or expand out to the full options + [80, { port: 8080, address: 'localhost', onListening: function () { console.log('http://localhost'); } }] + + // you can give just the port, or expand out to the full options +, [443, 5001, { port: 8443, address: 'localhost' }] + + // this is pretty much the default onListening handler +, function onListening() { + var server = this; + var protocol = ('requestCert' in server) ? 'https': 'http'; + console.log("Listening at " + protocol + '://localhost:' + this.address().port); + } +); + +// In case you need access to the raw servers (i.e. using websockets) +console.log(results.plainServers); +console.log(results.tlsServers); +``` + +### WebSockets with Let's Encrypt + +Note: you don't need to create websockets for the plain ports. + +``` +results.tlsServers.forEach(function (server) { +}); +``` + ## Options If any of these values are `undefined` or `null` the will assume use reasonable defaults. @@ -18,6 +91,9 @@ Partially defined values will be merged with the defaults. Setting the value to `false` will, in many cases (as documented), disable the defaults. ``` +configDir: string // + + webrootPath: string // string a path to a folder where temporary challenge files will be stored and read // default os.tmpdir() + path.sep + 'acme-challenge' @@ -45,4 +121,15 @@ httpsOptions: object // object will be merged with internal defau sniCallback: func // func replace the default sniCallback handler (which manages certificates) with your own + + +letsencrypt: object // object configure the letsencrypt object yourself and pass it in directly + // + // default we create the letsencrypt object using parameters you specify ``` + +## Heroku? + +This doesn't work on heroku because heroku uses a proxy with built-in https +(which is a smart thing to do) and besides, they want you to pay big bucks +for https. (hopefully not for long?...) diff --git a/lib/challenge-handlers.js b/lib/challenge-handlers.js new file mode 100644 index 0000000..1196c1f --- /dev/null +++ b/lib/challenge-handlers.js @@ -0,0 +1,31 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +// TODO handle templating :hostname in letsencrypt proper + +// Note: we're explicitly doing this on the filesystem +// rather than in-memory to support node cluster + +module.exports = { + set: function setChallenge(args, hostname, key, value, cb) { + var keyfile = path.join((args.webrootPath || args.webrootTpl).replace(':hostname', hostname), key); + + fs.writeFile(keyfile, value, 'utf8', cb); + } + +, get: function getChallenge(args, hostname, key, cb) { + var keyfile = path.join((args.webrootPath || args.webrootTpl).replace(':hostname', hostname), key); + + fs.readFile(keyfile, 'utf8', cb); + } + +, remove: function removeChallenge(args, hostname, key, cb) { + var keyfile = path.join((args.webrootPath || args.webrootTpl).replace(':hostname', hostname), key); + + // Note: it's not actually terribly important that we wait for the unlink callback + // but it's a polite thing to do - and we're polite people! + fs.unlink(keyfile, cb); + } +}; diff --git a/lib/standalone.js b/lib/standalone.js index c05c61b..bc5a65b 100644 --- a/lib/standalone.js +++ b/lib/standalone.js @@ -1,22 +1,10 @@ 'use strict'; var path = require('path'); +var challengeStore = require('./lib/challange-handlers'); -function getChallenge(args, hostname, key, cb) { - var fs = require('fs'); - var keyfile = path.join((args.webrootPath || args.webrootTpl).replace(':hostname', hostname), key); - - fs.readFile(keyfile, 'utf8', function (err, text) { - if (err) { - cb(err); - return; - } - - cb(null, text); - }); -} - -function create(obj) { +function create(obj, app) { + var LE = require('letsencrypt'); var https = require('https'); var http = require('http'); @@ -26,12 +14,23 @@ function create(obj) { if (!obj) { obj = {}; } - else if ('function' === typeof obj) { + + if ('string' === typeof obj) { + obj = { + configDir: obj + }; + } + + if ('function' === typeof obj) { obj = { onRequest: obj }; } + if ('function' === typeof app) { + obj.onRequest = obj.onRequest || app; + } + if (!obj.getChallenge) { if (false !== obj.getChallenge) { obj.getChallenge = getChallenge; @@ -41,11 +40,18 @@ function create(obj) { } } - if (!obj.onRequest) { - console.warn("You did not specify args.onRequest, using 'Hello, World!'"); - obj.onRequest = function (req, res) { - res.end('Hello, World!'); - }; + if (!obj.onRequest && false !== obj.onRequest) { + console.warn("You should either do args.onRequest = app or server.on('request', app)," + + " otherwise only acme-challenge requests will be handled (and the rest will hang)"); + console.warn("You can silence this warning by setting args.onRequest = false"); + } + + if (!obj.letsencrypt) { + //LE.merge(obj, ); + obj.letsencrypt = LE.create(obj, { + setChallenge: setChallenge + , removeChallenge: removeChallenge + }); } function acmeResponder(req, res) { @@ -142,7 +148,7 @@ function create(obj) { // deleting creates a "slow object", but that's okay (we only use it once) return results; - }; + } return { listen: listen @@ -151,4 +157,6 @@ function create(obj) { module.exports = create; module.exports.create = create; -module.exports.getChallenge = getChallenge; +module.exports.setChallenge = challengeStore.set; +module.exports.getChallenge = challengeStore.get; +module.exports.removeChallenge = challengeStore.remove;