diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..76c4a67 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "bracketSpacing": true, + "printWidth": 120, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": true +} diff --git a/README.md b/README.md index 07f685a..aeec137 100644 --- a/README.md +++ b/README.md @@ -37,30 +37,30 @@ and **node.js middleware systems**. # Features - - [x] Automatic HTTPS - - [x] Free SSL - - [x] Free Wildcard SSL - - [x] Multiple domain support (up to 100 altnames per SAN) - - [x] Dynamic Virtual Hosting (vhost) - - [x] Automatical renewal (10 to 14 days before expiration) - - [x] Great ACME support - - [x] ACME draft 11 - - [x] Let's Encrypt v2 - - [x] Let's Encrypt v1 - - [x] Full node.js support - - [x] node v6+ - - [x] core https module - - [x] Express.js - - [x] [Koa](https://git.rootprojects.org/root/greenlock-koa.js) - - [x] [hapi](https://git.rootprojects.org/root/greenlock-hapi.js) - - [x] Extensible Plugin Support - - [x] AWS (S3, Route53) - - [x] Azure - - [x] CloudFlare - - [x] Consul - - [x] Digital Ocean - - [x] etcd - - [x] Redis +- [x] Automatic HTTPS + - [x] Free SSL + - [x] Free Wildcard SSL + - [x] Multiple domain support (up to 100 altnames per SAN) + - [x] Dynamic Virtual Hosting (vhost) + - [x] Automatical renewal (10 to 14 days before expiration) +- [x] Great ACME support + - [x] ACME draft 11 + - [x] Let's Encrypt v2 + - [x] Let's Encrypt v1 +- [x] Full node.js support + - [x] node v6+ + - [x] core https module + - [x] Express.js + - [x] [Koa](https://git.rootprojects.org/root/greenlock-koa.js) + - [x] [hapi](https://git.rootprojects.org/root/greenlock-hapi.js) +- [x] Extensible Plugin Support + - [x] AWS (S3, Route53) + - [x] Azure + - [x] CloudFlare + - [x] Consul + - [x] Digital Ocean + - [x] etcd + - [x] Redis # Install @@ -78,18 +78,18 @@ Watch the QuickStart demonstration: [https://youtu.be/e8vaR4CEZ5s](https://youtu YouTube Video Preview -* [0:00](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=0) - Intro -* [2:22](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=142) - Demonstrating QuickStart Example -* [6:37](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk?t=397) - Troubleshooting / Gotchas +- [0:00](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=0) - Intro +- [2:22](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=142) - Demonstrating QuickStart Example +- [6:37](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk?t=397) - Troubleshooting / Gotchas #### Beyond the QuickStart (Part 2) -* [1:00](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=60) - Bringing Greenlock into an Existing Express Project -* [2:26](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=146) - The `approveDomains` callback +- [1:00](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=60) - Bringing Greenlock into an Existing Express Project +- [2:26](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=146) - The `approveDomains` callback #### Security Concerns (Part 3) -* [0:00](https://www.youtube.com/watch?v=aZgVqPzoZTY&index=3&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk) - Potential Attacks, and Mitigation +- [0:00](https://www.youtube.com/watch?v=aZgVqPzoZTY&index=3&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk) - Potential Attacks, and Mitigation ### Working Example Code @@ -110,35 +110,39 @@ node greenlock-express.js/examples/simple.js All you have to do is start the webserver and then visit it at its domain name. `server.js`: + ```javascript -'use strict'; +"use strict"; -require('greenlock-express').create({ - email: 'john.doe@example.com' // The email address of the ACME user / hosting provider -, agreeTos: true // You must accept the ToS as the host which handles the certs -, configDir: '~/.config/acme/' // Writable directory where certs will be saved -, communityMember: true // Join the community to get notified of important updates -, telemetry: true // Contribute telemetry data to the project +require("greenlock-express") + .create({ + email: "john.doe@example.com", // The email address of the ACME user / hosting provider + agreeTos: true, // You must accept the ToS as the host which handles the certs + configDir: "~/.config/acme/", // Writable directory where certs will be saved + communityMember: true, // Join the community to get notified of important updates + telemetry: true, // Contribute telemetry data to the project - // Using your express app: - // simply export it as-is, then include it here -, app: require('./app.js') + // Using your express app: + // simply export it as-is, then include it here + app: require("./app.js") -//, debug: true -}).listen(80, 443); + //, debug: true + }) + .listen(80, 443); ``` `app.js`: -```js -'use strict'; -var express = require('express'); +```js +"use strict"; + +var express = require("express"); var app = express(); -app.use('/', function (req, res) { - res.setHeader('Content-Type', 'text/html; charset=utf-8') - res.end('Hello, World!\n\nšŸ’š šŸ”’.js'); -}) +app.use("/", function(req, res) { + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.end("Hello, World!\n\nšŸ’š šŸ”’.js"); +}); // Don't do this: // app.listen(3000) @@ -166,29 +170,29 @@ You can see our full privacy policy at Be sure to tell me ([@solderjs](https://twitter.com/@solderjs)) / us ([@GreenlockHTTPS](https://twitter.com/@GreenlockHTTPS)) about it. :) | -| Full List | Check out the [examples/](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples) directory | +| Example | Location + Description | +| :-------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| **QuickStart** | [examples/quickstart.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/quickstart.js) uses the fewest options and accepts all default settings. It's guaranteed to work for you. | +| Production | [examples/production.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/production.js) shows how to require an express app (or other middleware system), expand the `approveDomains` callback, provides an example database shim, and exposes the server instance. | +| Virtual Hosting | [examples/vhost.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/vhost.js) shows how to dynamically secure and serve domains based on their existance on the file system. | +| Wildcard Domains | [examples/wildcard.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/wildcard.js) shows how to use the `acme-dns-01-cli` and wildcard cetificates. | +| HTTPS (raw) | [examples/spdy.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/spdy.js) demonstrates how to manually configure a node web server using the node's built-in `http` and `https` modules. | +| HTTP2 (spdy) | Presently spdy is incompatible with node v11, but [examples/spdy.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/spdy.js) demonstrates how to manually configure a node web server with spdy-compatible versions of node and Greenlock. | +| HTTP2 (node) | [examples/http2.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/http2.js) uses node's new HTTP2 module, which is NOT compatible with the existing middleware systems (and is not "stable" as of v10.0). | +| WebSockets (ws) | [examples/websockets.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/websockets.js) demonstrates how to use Greenlock express with a websocket server. | +| socket.io | [examples/socket.io.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/socket.io.js) demonstrates how to use Greenlock express with socket.io (even though `ws` is far simpler, faster, and better and every way). | +| - | Build Your Own
Be sure to tell me ([@solderjs](https://twitter.com/@solderjs)) / us ([@GreenlockHTTPS](https://twitter.com/@GreenlockHTTPS)) about it. :) | +| Full List | Check out the [examples/](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples) directory | # Plugins @@ -230,51 +234,49 @@ https://acme-staging-v02.api.letsencrypt.org/directory ## HTTP-01 Challenges -| | Plugin | -|:--------------:|:---------:| -| **Default (fs)** | [acme-http-01-fs](https://git.rootprojects.org/root/acme-http-01-webroot.js) | -| **Manual (cli)** | [acme-http-01-cli](https://git.rootprojects.org/root/acme-http-01-cli.js) | -| AWS S3 | [acme-http-01-s3](https://git.rootprojects.org/root/acme-http-01-s3.js) | -| Azure | [kolarcz/node-le-challenge-azure-storage](https://github.com/kolarcz/node-le-challenge-azure-storage) | -| - | Build Your Own
[acme-http-01-challenge-test](https://git.rootprojects.org/root/acme-challenge-test.js) | -| Full List | Search [acme-http-01-](https://www.npmjs.com/search?q=acme-http-01-) on npm (or [le-challenge-](https://www.npmjs.com/search?q=le-challenge-) for older versions) | - +| | Plugin | +| :--------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| **Default (fs)** | [acme-http-01-fs](https://git.rootprojects.org/root/acme-http-01-webroot.js) | +| **Manual (cli)** | [acme-http-01-cli](https://git.rootprojects.org/root/acme-http-01-cli.js) | +| AWS S3 | [acme-http-01-s3](https://git.rootprojects.org/root/acme-http-01-s3.js) | +| Azure | [kolarcz/node-le-challenge-azure-storage](https://github.com/kolarcz/node-le-challenge-azure-storage) | +| - | Build Your Own
[acme-http-01-challenge-test](https://git.rootprojects.org/root/acme-challenge-test.js) | +| Full List | Search [acme-http-01-](https://www.npmjs.com/search?q=acme-http-01-) on npm (or [le-challenge-](https://www.npmjs.com/search?q=le-challenge-) for older versions) | ## DNS-01 Challenges -| | Plugin | -|:--------------:|:---------:| -| **Manual (cli)** | [acme-dns-01-cli](https://git.rootprojects.org/root/acme-dns-01-cli.js) | -| AWS Route 53 | [thadeetrompetter/le-challenge-route53](https://github.com/thadeetrompetter/le-challenge-route53) | -| CloudFlare | [buschtoens/le-challenge-cloudflare](https://github.com/buschtoens/le-challenge-cloudflare) | -| CloudFlare | [llun/le-challenge-cloudflare](https://github.com/llun/le-challenge-cloudflare) | -| Digital Ocean | [bmv437/le-challenge-digitalocean](https://github.com/bmv437/le-challenge-digitalocean) | -| etcd | [ceecko/le-challenge-etcd](https://github.com/ceecko/le-challenge-etcd) | -| - | Build Your Own
[acme-challenge-test](https://git.rootprojects.org/root/acme-challenge-test.js) | -| Full List | Search [acme-dns-01-](https://www.npmjs.com/search?q=acme-dns-01-) or [le-challenge-](https://www.npmjs.com/search?q=le-challenge-) on npm | +| | Plugin | +| :--------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | +| **Manual (cli)** | [acme-dns-01-cli](https://git.rootprojects.org/root/acme-dns-01-cli.js) | +| AWS Route 53 | [thadeetrompetter/le-challenge-route53](https://github.com/thadeetrompetter/le-challenge-route53) | +| CloudFlare | [buschtoens/le-challenge-cloudflare](https://github.com/buschtoens/le-challenge-cloudflare) | +| CloudFlare | [llun/le-challenge-cloudflare](https://github.com/llun/le-challenge-cloudflare) | +| Digital Ocean | [bmv437/le-challenge-digitalocean](https://github.com/bmv437/le-challenge-digitalocean) | +| etcd | [ceecko/le-challenge-etcd](https://github.com/ceecko/le-challenge-etcd) | +| - | Build Your Own
[acme-challenge-test](https://git.rootprojects.org/root/acme-challenge-test.js) | +| Full List | Search [acme-dns-01-](https://www.npmjs.com/search?q=acme-dns-01-) or [le-challenge-](https://www.npmjs.com/search?q=le-challenge-) on npm | ## Account & Certificate Storage -| | Plugin | -|:--------------:|:---------:| -| **Simplest** | [greenlock-store-fs](https://git.rootprojects.org/root/greenlock-store-fs.js) | -| certbot (v2 default) | [le-store-certbot](https://git.coolaj86.com/coolaj86/le-store-certbot.js) | -| AWS S3 | [gl-store-s3](https://git.rootprojects.org/root/gl-store-s3.js) | -| Consul | [sebastian-software/le-store-consul](https://github.com/sebastian-software/le-store-consul) | -| json (fs) | [paulgrove/le-store-simple-fs](https://github.com/paulgrove/le-store-simple-fs) -| Redis | [digitalbazaar/le-store-redis](https://github.com/digitalbazaar/le-store-redis) | -| - | Build Your Own
[greenlock-store-test](https://git.rootprojects.org/root/greenlock-store-test.js) | -| Full List | Search [le-store-](https://www.npmjs.com/search?q=le-store-) on npm | +| | Plugin | +| :------------------: | :---------------------------------------------------------------------------------------------------: | +| **Simplest** | [greenlock-store-fs](https://git.rootprojects.org/root/greenlock-store-fs.js) | +| certbot (v2 default) | [le-store-certbot](https://git.coolaj86.com/coolaj86/le-store-certbot.js) | +| AWS S3 | [gl-store-s3](https://git.rootprojects.org/root/gl-store-s3.js) | +| Consul | [sebastian-software/le-store-consul](https://github.com/sebastian-software/le-store-consul) | +| json (fs) | [paulgrove/le-store-simple-fs](https://github.com/paulgrove/le-store-simple-fs) | +| Redis | [digitalbazaar/le-store-redis](https://github.com/digitalbazaar/le-store-redis) | +| - | Build Your Own
[greenlock-store-test](https://git.rootprojects.org/root/greenlock-store-test.js) | +| Full List | Search [le-store-](https://www.npmjs.com/search?q=le-store-) on npm | ## Auto-SNI -| | Plugin | -|:-----------:|:---------:| +| | Plugin | +| :---------: | :-------------------------------------------------------------: | | **Default** | [le-sni-auto](https://git.coolaj86.com/coolaj86/le-sni-auto.js) | (you probably wouldn't need or want to replace this) - **Bugs**: Please report bugs with the community plugins to the appropriate owner first, then here if you don't get a response. # Usage @@ -300,34 +302,35 @@ node greenlock-express.js/examples/normal.js It looks a little more like this: `serve.js`: + ```javascript -'use strict'; +"use strict"; // returns an instance of greenlock.js with additional helper methods -var glx = require('greenlock-express').create({ - server: 'https://acme-v02.api.letsencrypt.org/directory' - // Note: If at first you don't succeed, stop and switch to staging: - // https://acme-staging-v02.api.letsencrypt.org/directory -, version: 'draft-11' // Let's Encrypt v2 (ACME v2) +var glx = require("greenlock-express").create({ + server: "https://acme-v02.api.letsencrypt.org/directory", + // Note: If at first you don't succeed, stop and switch to staging: + // https://acme-staging-v02.api.letsencrypt.org/directory + version: "draft-11", // Let's Encrypt v2 (ACME v2) - // If you wish to replace the default account and domain key storage plugin -, store: require('le-store-certbot').create({ - configDir: require('path').join(require('os').homedir(), 'acme', 'etc') - , webrootPath: '/tmp/acme-challenges' - }) + // If you wish to replace the default account and domain key storage plugin + store: require("le-store-certbot").create({ + configDir: require("path").join(require("os").homedir(), "acme", "etc"), + webrootPath: "/tmp/acme-challenges" + }), - // Contribute telemetry data to the project -, telemetry: true + // Contribute telemetry data to the project + telemetry: true, - // the default servername to use when the client doesn't specify - // (because some IoT devices don't support servername indication) -, servername: 'example.com' + // the default servername to use when the client doesn't specify + // (because some IoT devices don't support servername indication) + servername: "example.com", -, approveDomains: approveDomains + approveDomains: approveDomains }); -var server = glx.listen(80, 443, function () { - console.log("Listening on port 80 for ACME challenges and 443 for express app."); +var server = glx.listen(80, 443, function() { + console.log("Listening on port 80 for ACME challenges and 443 for express app."); }); ``` @@ -341,53 +344,54 @@ plainServer.on('error', function (err) { ... }); ``` The Automatic Certificate Issuance is initiated via SNI (`httpsOptions.SNICallback`). -For security, domain validation MUST have an approval callback in *production*. +For security, domain validation MUST have an approval callback in _production_. ```javascript -var http01 = require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' }); +var http01 = require("le-challenge-fs").create({ webrootPath: "/tmp/acme-challenges" }); function approveDomains(opts, certs, cb) { - // This is where you check your database and associated - // email addresses with domains and agreements and such - // if (!isAllowed(opts.domains)) { return cb(new Error("not allowed")); } + // This is where you check your database and associated + // email addresses with domains and agreements and such + // if (!isAllowed(opts.domains)) { return cb(new Error("not allowed")); } - // The domains being approved for the first time are listed in opts.domains - // Certs being renewed are listed in certs.altnames (if that's useful) + // The domains being approved for the first time are listed in opts.domains + // Certs being renewed are listed in certs.altnames (if that's useful) - // Opt-in to submit stats and get important updates - opts.communityMember = true; + // Opt-in to submit stats and get important updates + opts.communityMember = true; - // If you wish to replace the default challenge plugin, you may do so here - opts.challenges = { 'http-01': http01 }; + // If you wish to replace the default challenge plugin, you may do so here + opts.challenges = { "http-01": http01 }; - opts.email = 'john.doe@example.com'; - opts.agreeTos = true; + opts.email = "john.doe@example.com"; + opts.agreeTos = true; - // NOTE: you can also change other options such as `challengeType` and `challenge` - // opts.challengeType = 'http-01'; - // opts.challenge = require('le-challenge-fs').create({}); + // NOTE: you can also change other options such as `challengeType` and `challenge` + // opts.challengeType = 'http-01'; + // opts.challenge = require('le-challenge-fs').create({}); - cb(null, { options: opts, certs: certs }); + cb(null, { options: opts, certs: certs }); } ``` - ```javascript // handles acme-challenge and redirects to https -require('http').createServer(glx.middleware(require('redirect-https')())).listen(80, function () { - console.log("Listening for ACME http-01 challenges on", this.address()); -}); +require("http") + .createServer(glx.middleware(require("redirect-https")())) + .listen(80, function() { + console.log("Listening for ACME http-01 challenges on", this.address()); + }); - - -var app = require('express')(); -app.use('/', function (req, res) { - res.end('Hello, World!'); +var app = require("express")(); +app.use("/", function(req, res) { + res.end("Hello, World!"); }); // handles your app -require('https').createServer(glx.httpsOptions, app).listen(443, function () { - console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); -}); +require("https") + .createServer(glx.httpsOptions, app) + .listen(443, function() { + console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); + }); ``` **Security**: @@ -395,7 +399,6 @@ require('https').createServer(glx.httpsOptions, app).listen(443, function () { Greenlock will do a self-check on all domain registrations to prevent you from hitting rate limits. - # API This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO). @@ -405,35 +408,35 @@ The API is actually located at [greenlock.js options](https://git.rootprojects.o The only "API" consists of two options, the rest is just a wrapper around `greenlock.js` to take LOC from 15 to 5: -* `opts.app` An express app in the format `function (req, res) { ... }` (no `next`). -* `server = glx.listen(plainAddr, tlsAddr, onListen)` Accepts port numbers (or arrays of port numbers) to listen on, returns secure server. - * `listen(80, 443)` - * `listen(80, 443, onListenSecure)` - * `listen(80, 443, onListenPlain, onListenSecure)` - * `listen('localhost:80', '0.0.0.0:443')` - * `listen('[::1]:80', '[::]:443')` - * `listen('/tmp/glx.plain.sock', '/tmp/glx.secure.sock')` +- `opts.app` An express app in the format `function (req, res) { ... }` (no `next`). +- `server = glx.listen(plainAddr, tlsAddr, onListen)` Accepts port numbers (or arrays of port numbers) to listen on, returns secure server. + - `listen(80, 443)` + - `listen(80, 443, onListenSecure)` + - `listen(80, 443, onListenPlain, onListenSecure)` + - `listen('localhost:80', '0.0.0.0:443')` + - `listen('[::1]:80', '[::]:443')` + - `listen('/tmp/glx.plain.sock', '/tmp/glx.secure.sock')` Brief overview of some simple options for `greenlock.js`: -* `opts.server` set to https://acme-v02.api.letsencrypt.org/directory in production -* `opts.version` set to `v01` for Let's Encrypt v1 or `draft-11` for Let's Encrypt v2 (mistakenly called ACME v2) -* `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.communityMember` Join the community to get notified of important updates and help make greenlock better -* `opts.approveDomains` can be either of: - * An explicit array of allowed domains such as `[ 'example.com', 'www.example.com' ]` - * A callback `function (opts, certs, cb) { cb(null, { options: opts, certs: certs }); }` for setting `email`, `agreeTos`, `domains`, etc (as shown in usage example above) -* `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. +- `opts.server` set to https://acme-v02.api.letsencrypt.org/directory in production +- `opts.version` set to `v01` for Let's Encrypt v1 or `draft-11` for Let's Encrypt v2 (mistakenly called ACME v2) +- `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.communityMember` Join the community to get notified of important updates and help make greenlock better +- `opts.approveDomains` can be either of: + - An explicit array of allowed domains such as `[ 'example.com', 'www.example.com' ]` + - A callback `function (opts, certs, cb) { cb(null, { options: opts, certs: certs }); }` for setting `email`, `agreeTos`, `domains`, etc (as shown in usage example above) +- `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. ## Supported ACME versions -* Let's Encrypt v1 (aka v01) -* Let's Encrypt v2 (aka v02 or ACME draft 11) -* ACME draft 11 (ACME v2 is a misnomer) -* Wildcard domains (via dns-01 challenges) - * `*.example.com` +- Let's Encrypt v1 (aka v01) +- Let's Encrypt v2 (aka v02 or ACME draft 11) +- ACME draft 11 (ACME v2 is a misnomer) +- Wildcard domains (via dns-01 challenges) + - `*.example.com` tags: letsencrypt acme free ssl automated https node express.js diff --git a/config.js b/config.js index 7017760..478f8cd 100644 --- a/config.js +++ b/config.js @@ -1,9 +1,9 @@ -'use strict'; +"use strict"; -var path = require('path'); +var path = require("path"); module.exports = { - email: 'jon.doe@example.com' -, configDir: path.join(__dirname, 'acme') -, srv: '/srv/www/' -, api: '/srv/api/' + email: "jon.doe@example.com", + configDir: path.join(__dirname, "acme"), + srv: "/srv/www/", + api: "/srv/api/" }; diff --git a/examples/demo.js b/examples/demo.js index d9bee69..d204bea 100644 --- a/examples/demo.js +++ b/examples/demo.js @@ -1,59 +1,56 @@ -'use strict'; +"use strict"; // npm install spdy@3.x //var Greenlock = require('greenlock-express') -var Greenlock = require('../'); +var Greenlock = require("../"); var greenlock = Greenlock.create({ + // Let's Encrypt v2 is ACME draft 11 + version: "draft-11", - // Let's Encrypt v2 is ACME draft 11 - version: 'draft-11' + server: "https://acme-v02.api.letsencrypt.org/directory", + // Note: If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory -, server: 'https://acme-v02.api.letsencrypt.org/directory' - // Note: If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory + // You MUST change this to a valid email address + email: "jon@example.com", - // You MUST change this to a valid email address -, email: 'jon@example.com' + // You MUST NOT build clients that accept the ToS without asking the user + agreeTos: true, - // You MUST NOT build clients that accept the ToS without asking the user -, agreeTos: true + // You MUST change these to valid domains + // NOTE: all domains will validated and listed on the certificate + approvedDomains: ["example.com", "www.example.com"], - // You MUST change these to valid domains - // NOTE: all domains will validated and listed on the certificate -, approvedDomains: [ 'example.com', 'www.example.com' ] + // You MUST have access to write to directory where certs are saved + // ex: /home/foouser/acme/etc + configDir: "~/.config/acme/", - // You MUST have access to write to directory where certs are saved - // ex: /home/foouser/acme/etc -, configDir: '~/.config/acme/' - - // Get notified of important updates and help me make greenlock better -, communityMember: true - -//, debug: true + // Get notified of important updates and help me make greenlock better + communityMember: true + //, debug: true }); - - //////////////////////// // http-01 Challenges // //////////////////////// // http-01 challenge happens over http/1.1, not http2 -var redirectHttps = require('redirect-https')(); -var acmeChallengeHandler = greenlock.middleware(function (req, res) { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.end('

Hello, āš ļø Insecure World!

Visit Secure Site' - + '' - ); +var redirectHttps = require("redirect-https")(); +var acmeChallengeHandler = greenlock.middleware(function(req, res) { + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.end( + "

Hello, āš ļø Insecure World!

Visit Secure Site" + + '' + ); }); -require('http').createServer(acmeChallengeHandler).listen(80, function () { - console.log("Listening for ACME http-01 challenges on", this.address()); -}); - - +require("http") + .createServer(acmeChallengeHandler) + .listen(80, function() { + console.log("Listening for ACME http-01 challenges on", this.address()); + }); //////////////////////// // http2 via SPDY h2 // @@ -61,15 +58,18 @@ require('http').createServer(acmeChallengeHandler).listen(80, function () { // spdy is a drop-in replacement for the https API var spdyOptions = Object.assign({}, greenlock.tlsOptions); -spdyOptions.spdy = { protocols: [ 'h2', 'http/1.1' ], plain: false }; -var server = require('spdy').createServer(spdyOptions, require('express')().use('/', function (req, res) { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.end('

Hello, šŸ” Secure World!

'); -})); -server.on('error', function (err) { - console.error(err); +spdyOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false }; +var server = require("spdy").createServer( + spdyOptions, + require("express")().use("/", function(req, res) { + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.end("

Hello, šŸ” Secure World!

"); + }) +); +server.on("error", function(err) { + console.error(err); }); -server.on('listening', function () { - console.log("Listening for SPDY/http2/https requests on", this.address()); +server.on("listening", function() { + console.log("Listening for SPDY/http2/https requests on", this.address()); }); server.listen(443); diff --git a/examples/force-renew.js b/examples/force-renew.js index 8c8f463..6224758 100644 --- a/examples/force-renew.js +++ b/examples/force-renew.js @@ -1,29 +1,30 @@ -'use strict'; +"use strict"; //require('greenlock-express') -require('../').create({ +require("../") + .create({ + // Let's Encrypt v2 is ACME draft 11 + version: "draft-11", - // Let's Encrypt v2 is ACME draft 11 - version: 'draft-11' + server: "https://acme-v02.api.letsencrypt.org/directory", + // Note: If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory -, server: 'https://acme-v02.api.letsencrypt.org/directory' - // Note: If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory + email: "john.doe@example.com", -, email: 'john.doe@example.com' + agreeTos: true, -, agreeTos: true + approvedDomains: ["example.com", "www.example.com"], -, approvedDomains: [ 'example.com', 'www.example.com' ] + app: require("express")().use("/", function(req, res) { + res.end("Hello, World!"); + }), -, app: require('express')().use('/', function (req, res) { - res.end('Hello, World!'); - }) + renewWithin: 91 * 24 * 60 * 60 * 1000, + renewBy: 90 * 24 * 60 * 60 * 1000, -, renewWithin: (91 * 24 * 60 * 60 * 1000) -, renewBy: (90 * 24 * 60 * 60 * 1000) - - // Get notified of important updates and help me make greenlock better -, communityMember: true -, debug: true -}).listen(80, 443); + // Get notified of important updates and help me make greenlock better + communityMember: true, + debug: true + }) + .listen(80, 443); diff --git a/examples/http2.js b/examples/http2.js index 5b30c65..a890715 100644 --- a/examples/http2.js +++ b/examples/http2.js @@ -1,74 +1,70 @@ -'use strict'; +"use strict"; //var Greenlock = require('greenlock-express') -var Greenlock = require('../'); +var Greenlock = require("../"); var greenlock = Greenlock.create({ + // Let's Encrypt v2 is ACME draft 11 + version: "draft-11", - // Let's Encrypt v2 is ACME draft 11 - version: 'draft-11' + server: "https://acme-v02.api.letsencrypt.org/directory", + // Note: If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory -, server: 'https://acme-v02.api.letsencrypt.org/directory' - // Note: If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory + // You MUST change this to a valid email address + email: "jon@example.com", - // You MUST change this to a valid email address -, email: 'jon@example.com' + // You MUST NOT build clients that accept the ToS without asking the user + agreeTos: true, - // You MUST NOT build clients that accept the ToS without asking the user -, agreeTos: true + // You MUST change these to valid domains + // NOTE: all domains will validated and listed on the certificate + approvedDomains: ["example.com", "www.example.com"], - // You MUST change these to valid domains - // NOTE: all domains will validated and listed on the certificate -, approvedDomains: [ 'example.com', 'www.example.com' ] + // You MUST have access to write to directory where certs are saved + // ex: /home/foouser/acme/etc + configDir: "~/.config/acme/", - // You MUST have access to write to directory where certs are saved - // ex: /home/foouser/acme/etc -, configDir: '~/.config/acme/' - - // Get notified of important updates and help me make greenlock better -, communityMember: true - -//, debug: true + // Get notified of important updates and help me make greenlock better + communityMember: true + //, debug: true }); - - //////////////////////// // http-01 Challenges // //////////////////////// // http-01 challenge happens over http/1.1, not http2 -var redirectHttps = require('redirect-https')(); +var redirectHttps = require("redirect-https")(); var acmeChallengeHandler = greenlock.middleware(redirectHttps); -require('http').createServer(acmeChallengeHandler).listen(80, function () { - console.log("Listening for ACME http-01 challenges on", this.address()); -}); - - +require("http") + .createServer(acmeChallengeHandler) + .listen(80, function() { + console.log("Listening for ACME http-01 challenges on", this.address()); + }); //////////////////////// // node.js' http2 api // //////////////////////// // http2 is a new API with which you would use hapi or koa, not express -var server = require('http2').createSecureServer(greenlock.tlsOptions); -server.on('error', function (err) { - console.error(err); +var server = require("http2").createSecureServer(greenlock.tlsOptions); +server.on("error", function(err) { + console.error(err); }); // WARNING: Because the middleware don't handle this API style, // the Host headers are unmodified and potentially dangerous // (ex: Host: Robert'); DROP TABLE Students;) -server.on('stream', function (stream, headers) { - console.log(headers); - stream.respond({ - 'content-type': 'text/html' - , ':status': 200 - }); - stream.end('Hello, HTTP2 World!'); +server.on("stream", function(stream, headers) { + console.log(headers); + stream.respond({ + "content-type": "text/html", + ":status": 200 + }); + stream.end("Hello, HTTP2 World!"); }); -server.on('listening', function () { - console.log("Listening for http2 requests on", this.address()); +server.on("listening", function() { + console.log("Listening for http2 requests on", this.address()); }); server.listen(443); diff --git a/examples/my-express-app.js b/examples/my-express-app.js index 04da098..fada117 100644 --- a/examples/my-express-app.js +++ b/examples/my-express-app.js @@ -1,15 +1,17 @@ -'use strict'; +"use strict"; -var express = require('express'); +var express = require("express"); var app = express(); -app.use('/', function (req, res) { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.end('Hello, World!\n\nšŸ’š šŸ”’.js'); +app.use("/", function(req, res) { + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.end("Hello, World!\n\nšŸ’š šŸ”’.js"); }); // DO NOT DO app.listen() unless we're testing this directly -if (require.main === module) { app.listen(3000); } +if (require.main === module) { + app.listen(3000); +} // Instead do export the app: module.exports = app; diff --git a/examples/production.js b/examples/production.js index 08ee7ef..2bcfda9 100644 --- a/examples/production.js +++ b/examples/production.js @@ -1,90 +1,88 @@ -'use strict'; +"use strict"; // // My Secure Server // //var greenlock = require('greenlock-express') -var greenlock = require('../').create({ +var greenlock = require("../").create({ + // Let's Encrypt v2 is ACME draft 11 + // Note: If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory + server: "https://acme-v02.api.letsencrypt.org/directory", + version: "draft-11", + // You MUST have write access to save certs + configDir: "~/.config/acme/", - // Let's Encrypt v2 is ACME draft 11 - // Note: If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory - server: 'https://acme-v02.api.letsencrypt.org/directory' -, version: 'draft-11' - // You MUST have write access to save certs -, configDir: '~/.config/acme/' + // The previous 'simple' example set these values statically, + // but this example uses approveDomains() to set them dynamically + //, email: 'none@see.note.above' + //, agreeTos: false -// The previous 'simple' example set these values statically, -// but this example uses approveDomains() to set them dynamically -//, email: 'none@see.note.above' -//, agreeTos: false + // approveDomains is the right place to check a database for + // email addresses with domains and agreements and such + approveDomains: approveDomains, - // approveDomains is the right place to check a database for - // email addresses with domains and agreements and such -, approveDomains: approveDomains + app: require("./my-express-app.js"), -, app: require('./my-express-app.js') - - // Get notified of important updates and help me make greenlock better -, communityMember: true - -//, debug: true + // Get notified of important updates and help me make greenlock better + communityMember: true + //, debug: true }); var server = greenlock.listen(80, 443); - // // My Secure Database Check // function approveDomains(opts, certs, cb) { + // Only one domain is listed with *automatic* registration via SNI + // (it's an array because managed registration allows for multiple domains, + // which was the case in the simple example) + console.log(opts.domains); - // Only one domain is listed with *automatic* registration via SNI - // (it's an array because managed registration allows for multiple domains, - // which was the case in the simple example) - console.log(opts.domains); + // The domains being approved for the first time are listed in opts.domains + // Certs being renewed are listed in certs.altnames + if (certs) { + opts.domains = [certs.subject].concat(certs.altnames); + } - // The domains being approved for the first time are listed in opts.domains - // Certs being renewed are listed in certs.altnames - if (certs) { - opts.domains = [certs.subject].concat(certs.altnames); - } + fooCheckDb(opts.domains, function(err, agree, email) { + if (err) { + cb(err); + return; + } - fooCheckDb(opts.domains, function (err, agree, email) { - if (err) { cb(err); return; } + // Services SHOULD automatically accept the ToS and use YOUR email + // Clients MUST NOT accept the ToS without asking the user + opts.agreeTos = agree; + opts.email = email; - // Services SHOULD automatically accept the ToS and use YOUR email - // Clients MUST NOT accept the ToS without asking the user - opts.agreeTos = agree; - opts.email = email; + // NOTE: you can also change other options such as `challengeType` and `challenge` + // (this would be helpful if you decided you wanted wildcard support as a domain altname) + // opts.challengeType = 'http-01'; + // opts.challenge = require('le-challenge-fs').create({}); - // NOTE: you can also change other options such as `challengeType` and `challenge` - // (this would be helpful if you decided you wanted wildcard support as a domain altname) - // opts.challengeType = 'http-01'; - // opts.challenge = require('le-challenge-fs').create({}); - - cb(null, { options: opts, certs: certs }); - }); + cb(null, { options: opts, certs: certs }); + }); } - // // My User / Domain Database // function fooCheckDb(domains, cb) { - // This is an oversimplified example of how we might implement a check in - // our database if we have different rules for different users and domains - var domains = [ 'example.com', 'www.example.com' ]; - var userEmail = 'john.doe@example.com'; - var userAgrees = true; - var passCheck = opts.domains.every(function (domain) { - return -1 !== domains.indexOf(domain); - }); + // This is an oversimplified example of how we might implement a check in + // our database if we have different rules for different users and domains + var domains = ["example.com", "www.example.com"]; + var userEmail = "john.doe@example.com"; + var userAgrees = true; + var passCheck = opts.domains.every(function(domain) { + return -1 !== domains.indexOf(domain); + }); - if (!passCheck) { - cb(new Error('domain not allowed')); - } else { - cb(null, userAgrees, userEmail); - } + if (!passCheck) { + cb(new Error("domain not allowed")); + } else { + cb(null, userAgrees, userEmail); + } } diff --git a/examples/quickstart.js b/examples/quickstart.js index e2c6ce0..f13ecc9 100644 --- a/examples/quickstart.js +++ b/examples/quickstart.js @@ -1,37 +1,38 @@ -'use strict'; +"use strict"; //require('greenlock-express') -require('../').create({ +require("../") + .create({ + // Let's Encrypt v2 is ACME draft 11 + version: "draft-11", - // Let's Encrypt v2 is ACME draft 11 - version: 'draft-11' + server: "https://acme-v02.api.letsencrypt.org/directory", + // Note: If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory -, server: 'https://acme-v02.api.letsencrypt.org/directory' - // Note: If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory + // You MUST change this to a valid email address + email: "john.doe@example.com", - // You MUST change this to a valid email address -, email: 'john.doe@example.com' + // You MUST NOT build clients that accept the ToS without asking the user + agreeTos: true, - // You MUST NOT build clients that accept the ToS without asking the user -, agreeTos: true + // You MUST change these to valid domains + // NOTE: all domains will validated and listed on the certificate + approvedDomains: ["example.com", "www.example.com"], - // You MUST change these to valid domains - // NOTE: all domains will validated and listed on the certificate -, approvedDomains: [ 'example.com', 'www.example.com' ] + // You MUST have access to write to directory where certs are saved + // ex: /home/foouser/acme/etc + configDir: "~/.config/acme/", + store: require("greenlock-store-fs"), - // You MUST have access to write to directory where certs are saved - // ex: /home/foouser/acme/etc -, configDir: '~/.config/acme/' + app: require("express")().use("/", function(req, res) { + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.end("Hello, World!\n\nšŸ’š šŸ”’.js"); + }), -, app: require('express')().use('/', function (req, res) { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.end('Hello, World!\n\nšŸ’š šŸ”’.js'); - }) + // Get notified of important updates and help me make greenlock better + communityMember: true - // Get notified of important updates and help me make greenlock better -, communityMember: true - -//, debug: true - -}).listen(80, 443); + //, debug: true + }) + .listen(80, 443); diff --git a/examples/remote-access.js b/examples/remote-access.js index 87676d6..4e66cef 100644 --- a/examples/remote-access.js +++ b/examples/remote-access.js @@ -1,4 +1,4 @@ -'use strict'; +"use strict"; // // WARNING: Not for noobs @@ -9,87 +9,96 @@ // This demo is used with tunnel-server.js and tunnel-client.js // -var email = 'john.doe@gmail.com'; -var domains = [ 'example.com' ]; +var email = "john.doe@gmail.com"; +var domains = ["example.com"]; var agreeLeTos = true; //var secret = "My Little Brony"; -var secret = require('crypto').randomBytes(16).toString('hex'); +var secret = require("crypto") + .randomBytes(16) + .toString("hex"); -require('../').create({ - version: 'draft-11' +require("../") + .create({ + version: "draft-11", -, server: 'https://acme-v02.api.letsencrypt.org/directory' - // Note: If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory - -, email: email -, agreeTos: agreeLeTos -, approveDomains: domains -, configDir: '~/.config/acme/' -, app: remoteAccess(secret) - // Get notified of important updates and help me make greenlock better -, communityMember: true -//, debug: true -}).listen(3000, 8443); + server: "https://acme-v02.api.letsencrypt.org/directory", + // Note: If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory + email: email, + agreeTos: agreeLeTos, + approveDomains: domains, + configDir: "~/.config/acme/", + app: remoteAccess(secret), + // Get notified of important updates and help me make greenlock better + communityMember: true + //, debug: true + }) + .listen(3000, 8443); function remoteAccess(secret) { - var express = require('express'); - var basicAuth = require('express-basic-auth'); - var serveIndex = require('serve-index'); + var express = require("express"); + var basicAuth = require("express-basic-auth"); + var serveIndex = require("serve-index"); - var rootIndex = serveIndex('/', { hidden: true, icons: true, view: 'details' }); - var rootFs = express.static('/', { dotfiles: 'allow', redirect: true, index: false }); + var rootIndex = serveIndex("/", { hidden: true, icons: true, view: "details" }); + var rootFs = express.static("/", { dotfiles: "allow", redirect: true, index: false }); - var userIndex = serveIndex(require('os').homedir(), { hidden: true, icons: true, view: 'details' }); - var userFs = express.static(require('os').homedir(), { dotfiles: 'allow', redirect: true, index: false }); + var userIndex = serveIndex(require("os").homedir(), { hidden: true, icons: true, view: "details" }); + var userFs = express.static(require("os").homedir(), { dotfiles: "allow", redirect: true, index: false }); - var app = express(); - var realm = 'Login Required'; + var app = express(); + var realm = "Login Required"; - var myAuth = basicAuth({ - users: { 'root': secret, 'user': secret } - , challenge: true - , realm: realm - , unauthorizedResponse: function (/*req*/) { - return 'Unauthorized Home'; - } - }); + var myAuth = basicAuth({ + users: { root: secret, user: secret }, + challenge: true, + realm: realm, + unauthorizedResponse: function(/*req*/) { + return 'Unauthorized Home'; + } + }); - app.get('/', function (req, res) { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.end( - 'View Files' - + '  |  ' - + 'Logout' - ); - }); - app.use('/logout', function (req, res) { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"'); - res.statusCode = 401; - //res.setHeader('Location', '/'); - res.end('Logged out   |   Home'); - }); - app.use('/browse', myAuth); - app.use('/browse', function (req, res, next) { - if ('root' === req.auth.user) { rootFs(req, res, function () { rootIndex(req, res, next); }); return; } - if ('user' === req.auth.user) { userFs(req, res, function () { userIndex(req, res, next); }); return; } - res.end('Sad Panda'); - }); + app.get("/", function(req, res) { + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.end('View Files' + "  |  " + 'Logout'); + }); + app.use("/logout", function(req, res) { + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.setHeader("WWW-Authenticate", 'Basic realm="' + realm + '"'); + res.statusCode = 401; + //res.setHeader('Location', '/'); + res.end('Logged out   |   Home'); + }); + app.use("/browse", myAuth); + app.use("/browse", function(req, res, next) { + if ("root" === req.auth.user) { + rootFs(req, res, function() { + rootIndex(req, res, next); + }); + return; + } + if ("user" === req.auth.user) { + userFs(req, res, function() { + userIndex(req, res, next); + }); + return; + } + res.end("Sad Panda"); + }); - console.log(''); - console.log(''); - console.log('Usernames are\n'); - console.log('\troot'); - console.log('\tuser'); - console.log(''); - console.log('Password (for both) is\n'); - console.log('\t' + secret); - console.log(''); - console.log("Shhhh... It's a secret to everybody!"); - console.log(''); - console.log(''); + console.log(""); + console.log(""); + console.log("Usernames are\n"); + console.log("\troot"); + console.log("\tuser"); + console.log(""); + console.log("Password (for both) is\n"); + console.log("\t" + secret); + console.log(""); + console.log("Shhhh... It's a secret to everybody!"); + console.log(""); + console.log(""); - return app; + return app; } diff --git a/examples/socket.io.js b/examples/socket.io.js index 6d4cb0e..b626025 100644 --- a/examples/socket.io.js +++ b/examples/socket.io.js @@ -2,19 +2,19 @@ // I'm not a fan of `socket.io` because it's huge and complex. // I much prefer `ws` because it's very simple and easy. // That said, it's popular....... -'use strict'; +"use strict"; //var greenlock = require('greenlock-express'); -var greenlock = require('../'); -var options = require('./greenlock-options.js'); -var socketio = require('socket.io'); +var greenlock = require("../"); +var options = require("./greenlock-options.js"); +var socketio = require("socket.io"); var server; var io; // Any node http app will do - whether express, raw http or whatever -options.app = require('express')().use('/', function (req, res) { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.end('Hello, World!\n\nšŸ’š šŸ”’.js'); +options.app = require("express")().use("/", function(req, res) { + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.end("Hello, World!\n\nšŸ’š šŸ”’.js"); }); // The server that's handed back from `listen` is a raw https server @@ -22,11 +22,11 @@ server = greenlock.create(options).listen(80, 443); io = socketio(server); // Then you do your socket.io stuff -io.on('connection', function (socket) { - console.log('a user connected'); - socket.emit('Welcome'); +io.on("connection", function(socket) { + console.log("a user connected"); + socket.emit("Welcome"); - socket.on('chat message', function (msg) { - socket.broadcast.emit('chat message', msg); - }); + socket.on("chat message", function(msg) { + socket.broadcast.emit("chat message", msg); + }); }); diff --git a/examples/spdy.js b/examples/spdy.js index 0af2470..6cb87dd 100644 --- a/examples/spdy.js +++ b/examples/spdy.js @@ -1,54 +1,50 @@ -'use strict'; +"use strict"; // npm install spdy@3.x //var Greenlock = require('greenlock-express') -var Greenlock = require('../'); +var Greenlock = require("../"); var greenlock = Greenlock.create({ + // Let's Encrypt v2 is ACME draft 11 + version: "draft-11", - // Let's Encrypt v2 is ACME draft 11 - version: 'draft-11' + server: "https://acme-v02.api.letsencrypt.org/directory", + // Note: If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory -, server: 'https://acme-v02.api.letsencrypt.org/directory' - // Note: If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory + // You MUST change this to a valid email address + email: "jon@example.com", - // You MUST change this to a valid email address -, email: 'jon@example.com' + // You MUST NOT build clients that accept the ToS without asking the user + agreeTos: true, - // You MUST NOT build clients that accept the ToS without asking the user -, agreeTos: true + // You MUST change these to valid domains + // NOTE: all domains will validated and listed on the certificate + approvedDomains: ["example.com", "www.example.com"], - // You MUST change these to valid domains - // NOTE: all domains will validated and listed on the certificate -, approvedDomains: [ 'example.com', 'www.example.com' ] + // You MUST have access to write to directory where certs are saved + // ex: /home/foouser/acme/etc + configDir: "~/.config/acme/", // MUST have write access - // You MUST have access to write to directory where certs are saved - // ex: /home/foouser/acme/etc -, configDir: '~/.config/acme/' // MUST have write access - - // Get notified of important updates and help me make greenlock better -, communityMember: true - -//, debug: true + // Get notified of important updates and help me make greenlock better + communityMember: true + //, debug: true }); - - //////////////////////// // http-01 Challenges // //////////////////////// // http-01 challenge happens over http/1.1, not http2 -var redirectHttps = require('redirect-https')(); +var redirectHttps = require("redirect-https")(); var acmeChallengeHandler = greenlock.middleware(redirectHttps); -require('http').createServer(acmeChallengeHandler).listen(80, function () { - console.log("Listening for ACME http-01 challenges on", this.address()); -}); - - +require("http") + .createServer(acmeChallengeHandler) + .listen(80, function() { + console.log("Listening for ACME http-01 challenges on", this.address()); + }); //////////////////////// // http2 via SPDY h2 // @@ -56,13 +52,13 @@ require('http').createServer(acmeChallengeHandler).listen(80, function () { // spdy is a drop-in replacement for the https API var spdyOptions = Object.assign({}, greenlock.tlsOptions); -spdyOptions.spdy = { protocols: [ 'h2', 'http/1.1' ], plain: false }; -var myApp = require('./my-express-app.js'); -var server = require('spdy').createServer(spdyOptions, myApp); -server.on('error', function (err) { - console.error(err); +spdyOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false }; +var myApp = require("./my-express-app.js"); +var server = require("spdy").createServer(spdyOptions, myApp); +server.on("error", function(err) { + console.error(err); }); -server.on('listening', function () { - console.log("Listening for SPDY/http2/https requests on", this.address()); +server.on("listening", function() { + console.log("Listening for SPDY/http2/https requests on", this.address()); }); server.listen(443); diff --git a/examples/vhost.js b/examples/vhost.js index 93460d1..618bf35 100644 --- a/examples/vhost.js +++ b/examples/vhost.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -'use strict'; +"use strict"; /////////////////// // vhost example // @@ -11,118 +11,124 @@ // The prefix where sites go by name. // For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path -var srv = process.argv[3] || '/srv/www/'; +var srv = process.argv[3] || "/srv/www/"; -var path = require('path'); -var fs = require('fs').promises; -var finalhandler = require('finalhandler'); -var serveStatic = require('serve-static'); +var path = require("path"); +var fs = require("fs").promises; +var finalhandler = require("finalhandler"); +var serveStatic = require("serve-static"); //var glx = require('greenlock-express') -var glx = require('./').create({ +var glx = require("./").create({ + version: "draft-11", // Let's Encrypt v2 is ACME draft 11 - version: 'draft-11' // Let's Encrypt v2 is ACME draft 11 + server: "https://acme-v02.api.letsencrypt.org/directory", // If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory -, server: 'https://acme-v02.api.letsencrypt.org/directory' // If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory + configDir: process.argv[4] || "~/.config/acme/", // You MUST have access to write to directory where certs + // are saved. ex: /home/foouser/.config/acme -, configDir: process.argv[4] || '~/.config/acme/' // You MUST have access to write to directory where certs - // are saved. ex: /home/foouser/.config/acme + approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the + // domain name here and reject invalid ones -, approveDomains: myApproveDomains // Greenlock's wraps around tls.SNICallback. Check the - // domain name here and reject invalid ones + app: myVhostApp, // Any node-style http app (i.e. express, koa, hapi, rill) -, app: myVhostApp // Any node-style http app (i.e. express, koa, hapi, rill) - - /* CHANGE TO A VALID EMAIL */ -, email: process.argv[2] || 'jon.doe@example.com' // Email for Let's Encrypt account and Greenlock Security -, agreeTos: true // Accept Let's Encrypt ToS -//, communityMember: true // Join Greenlock to get important updates, no spam - -//, debug: true + /* CHANGE TO A VALID EMAIL */ + email: process.argv[2] || "jon.doe@example.com", // Email for Let's Encrypt account and Greenlock Security + agreeTos: true // Accept Let's Encrypt ToS + //, communityMember: true // Join Greenlock to get important updates, no spam + //, debug: true }); var server = glx.listen(80, 443); -server.on('listening', function () { - console.info(server.type + " listening on", server.address()); +server.on("listening", function() { + console.info(server.type + " listening on", server.address()); }); function myApproveDomains(opts, certs, cb) { - console.log('sni:', opts.domain); - // In this example the filesystem is our "database". - // We check in /srv/www for whatever.com and if it exists, it's allowed + console.log("sni:", opts.domain); + // In this example the filesystem is our "database". + // We check in /srv/www for whatever.com and if it exists, it's allowed - // SECURITY Greenlock validates opts.domains ahead-of-time so you don't have to - return checkWwws(opts.domains[0]).then(function () { - //opts.email = email; - opts.agreeTos = true; - cb(null, { options: opts, certs: certs }); - }).catch(cb); + // SECURITY Greenlock validates opts.domains ahead-of-time so you don't have to + return checkWwws(opts.domains[0]) + .then(function() { + //opts.email = email; + opts.agreeTos = true; + cb(null, { options: opts, certs: certs }); + }) + .catch(cb); } function checkWwws(_hostname) { - if (!_hostname) { - // SECURITY, don't allow access to the 'srv' root - // (greenlock-express uses middleware to check '..', etc) - return ''; - } - var hostname = _hostname; - var _hostdir = path.join(srv, hostname); - var hostdir = _hostdir; - // TODO could test for www/no-www both in directory - return fs.readdir(hostdir).then(function () { - // TODO check for some sort of htaccess.json and use email in that - // NOTE: you can also change other options such as `challengeType` and `challenge` - // opts.challengeType = 'http-01'; - // opts.challenge = require('le-challenge-fs').create({}); - return hostname; - }).catch(function () { - if ('www.' === hostname.slice(0, 4)) { - // Assume we'll redirect to non-www if it's available. - hostname = hostname.slice(4); - hostdir = path.join(srv, hostname); - return fs.readdir(hostdir).then(function () { - // TODO list both domains? - return hostname; - }); - } else { - // Or check and see if perhaps we should redirect non-www to www - hostname = 'www.' + hostname; - hostdir = path.join(srv, hostname); - return fs.readdir(hostdir).then(function () { - // TODO list both domains? - return hostname; - }); - } - }).catch(function () { - throw new Error("rejecting '" + _hostname + "' because '" + _hostdir + "' could not be read"); - }); + if (!_hostname) { + // SECURITY, don't allow access to the 'srv' root + // (greenlock-express uses middleware to check '..', etc) + return ""; + } + var hostname = _hostname; + var _hostdir = path.join(srv, hostname); + var hostdir = _hostdir; + // TODO could test for www/no-www both in directory + return fs + .readdir(hostdir) + .then(function() { + // TODO check for some sort of htaccess.json and use email in that + // NOTE: you can also change other options such as `challengeType` and `challenge` + // opts.challengeType = 'http-01'; + // opts.challenge = require('le-challenge-fs').create({}); + return hostname; + }) + .catch(function() { + if ("www." === hostname.slice(0, 4)) { + // Assume we'll redirect to non-www if it's available. + hostname = hostname.slice(4); + hostdir = path.join(srv, hostname); + return fs.readdir(hostdir).then(function() { + // TODO list both domains? + return hostname; + }); + } else { + // Or check and see if perhaps we should redirect non-www to www + hostname = "www." + hostname; + hostdir = path.join(srv, hostname); + return fs.readdir(hostdir).then(function() { + // TODO list both domains? + return hostname; + }); + } + }) + .catch(function() { + throw new Error("rejecting '" + _hostname + "' because '" + _hostdir + "' could not be read"); + }); } function myVhostApp(req, res) { - // SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to - // (also: only domains approved above will get here) - console.log('vhost:', req.headers.host); - if (!req.headers.host) { - // SECURITY, don't allow access to the 'srv' root - // (greenlock-express uses middleware to check '..', etc) - return res.end(); - } + // SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to + // (also: only domains approved above will get here) + console.log("vhost:", req.headers.host); + if (!req.headers.host) { + // SECURITY, don't allow access to the 'srv' root + // (greenlock-express uses middleware to check '..', etc) + return res.end(); + } - // We could cache wether or not a host exists for some amount of time - var fin = finalhandler(req, res); - return checkWwws(req.headers.host).then(function (hostname) { - if (hostname !== req.headers.host) { - res.statusCode = 302; - res.setHeader('Location', 'https://' + hostname); - // SECURITY this is safe only because greenlock disallows invalid hostnames - res.end(""); - return; - } - var serve = serveStatic(path.join(srv, hostname), { redirect: true }); - serve(req, res, fin); - }).catch(function () { - fin(); - }); + // We could cache wether or not a host exists for some amount of time + var fin = finalhandler(req, res); + return checkWwws(req.headers.host) + .then(function(hostname) { + if (hostname !== req.headers.host) { + res.statusCode = 302; + res.setHeader("Location", "https://" + hostname); + // SECURITY this is safe only because greenlock disallows invalid hostnames + res.end(""); + return; + } + var serve = serveStatic(path.join(srv, hostname), { redirect: true }); + serve(req, res, fin); + }) + .catch(function() { + fin(); + }); } diff --git a/examples/websockets.js b/examples/websockets.js index 54a69da..26cbc25 100644 --- a/examples/websockets.js +++ b/examples/websockets.js @@ -1,40 +1,46 @@ -'use strict'; - +"use strict"; //////////////////////// // Greenlock Setup // //////////////////////// //var Greenlock = require('greenlock-express'); -var Greenlock = require('../'); +var Greenlock = require("../"); var greenlock = Greenlock.create({ + // Let's Encrypt v2 is ACME draft 11 + // Note: If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory + server: "https://acme-v02.api.letsencrypt.org/directory", + version: "draft-11", + configDir: "~/.config/acme/", + app: require("./my-express-app.js"), - // Let's Encrypt v2 is ACME draft 11 - // Note: If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory - server: 'https://acme-v02.api.letsencrypt.org/directory' -, version: 'draft-11' -, configDir: '~/.config/acme/' -, app: require('./my-express-app.js') + // You MUST change these to a valid email and domains + email: "john.doe@example.com", + approvedDomains: ["example.com", "www.example.com"], + agreeTos: true, - // You MUST change these to a valid email and domains -, email: 'john.doe@example.com' -, approvedDomains: [ 'example.com', 'www.example.com' ] -, agreeTos: true - - // Get notified of important updates and help me make greenlock better -, communityMember: true -, telemetry: true -//, debug: true + // Get notified of important updates and help me make greenlock better + communityMember: true, + telemetry: true + //, debug: true }); var server = greenlock.listen(80, 443); -var WebSocket = require('ws'); +var WebSocket = require("ws"); var ws = new WebSocket.Server({ server: server }); -ws.on('connection', function (ws, req) { - // inspect req.headers.authorization (or cookies) for session info - ws.send("[Secure Echo Server] Hello!\nAuth: '" + (req.headers.authorization || 'none') + "'\n" - + "Cookie: '" + (req.headers.cookie || 'none') + "'\n"); - ws.on('message', function (data) { ws.send(data); }); +ws.on("connection", function(ws, req) { + // inspect req.headers.authorization (or cookies) for session info + ws.send( + "[Secure Echo Server] Hello!\nAuth: '" + + (req.headers.authorization || "none") + + "'\n" + + "Cookie: '" + + (req.headers.cookie || "none") + + "'\n" + ); + ws.on("message", function(data) { + ws.send(data); + }); }); diff --git a/examples/wildcard.js b/examples/wildcard.js index a612fab..5dbadc9 100644 --- a/examples/wildcard.js +++ b/examples/wildcard.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -'use strict'; +"use strict"; /*global Promise*/ /////////////////////// @@ -11,60 +11,67 @@ // //var glx = require('greenlock-express') -var glx = require('../').create({ +var glx = require("../").create({ + version: "draft-11", // Let's Encrypt v2 is ACME draft 11 - version: 'draft-11' // Let's Encrypt v2 is ACME draft 11 + server: "https://acme-staging-v02.api.letsencrypt.org/directory", + //, server: 'https://acme-v02.api.letsencrypt.org/directory' // If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory -, server: 'https://acme-staging-v02.api.letsencrypt.org/directory' -//, server: 'https://acme-v02.api.letsencrypt.org/directory' // If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory + configDir: "~/acme/", // You MUST have access to write to directory where certs + // are saved. ex: /home/foouser/.config/acme -, configDir: '~/acme/' // You MUST have access to write to directory where certs - // are saved. ex: /home/foouser/.config/acme + approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the + // domain name here and reject invalid ones -, approveDomains: myApproveDomains // Greenlock's wraps around tls.SNICallback. Check the - // domain name here and reject invalid ones + app: require("./my-express-app.js"), // Any node-style http app (i.e. express, koa, hapi, rill) -, app: require('./my-express-app.js') // Any node-style http app (i.e. express, koa, hapi, rill) + /* CHANGE TO A VALID EMAIL */ + email: "jon.doe@example.com", // Email for Let's Encrypt account and Greenlock Security + agreeTos: true, // Accept Let's Encrypt ToS + communityMember: true, // Join Greenlock to (very rarely) get important updates - /* CHANGE TO A VALID EMAIL */ -, email: 'jon.doe@example.com' // Email for Let's Encrypt account and Greenlock Security -, agreeTos: true // Accept Let's Encrypt ToS -, communityMember: true // Join Greenlock to (very rarely) get important updates - -//, debug: true -, store: require('le-store-fs') + //, debug: true + store: require("le-store-fs") }); var server = glx.listen(80, 443); -server.on('listening', function () { - console.info(server.type + " listening on", server.address()); +server.on("listening", function() { + console.info(server.type + " listening on", server.address()); }); function myApproveDomains(opts) { - console.log('sni:', opts.domain); + console.log("sni:", opts.domain); - // must be 'example.com' or start with 'example.com' - if ('example.com' !== opts.domain - && 'example.com' !== opts.domain.split('.').slice(1).join('.')) { - return Promise.reject(new Error("we don't serve your kind here: " + opts.domain)); - } + // must be 'example.com' or start with 'example.com' + if ( + "example.com" !== opts.domain && + "example.com" !== + opts.domain + .split(".") + .slice(1) + .join(".") + ) { + return Promise.reject(new Error("we don't serve your kind here: " + opts.domain)); + } - // the primary domain for the cert - opts.subject = 'example.com'; - // the altnames (including the primary) - opts.domains = [ opts.subject, '*.example.com' ]; + // the primary domain for the cert + opts.subject = "example.com"; + // the altnames (including the primary) + opts.domains = [opts.subject, "*.example.com"]; - if (!opts.challenges) { opts.challenges = {}; } - opts.challenges['http-01'] = require('le-challenge-fs').create({}); - // Note: When implementing a dns-01 plugin you should make it check in a loop - // until it can positively confirm that the DNS changes have propagated. - // That could take several seconds to a few minutes. - opts.challenges['dns-01'] = require('le-challenge-dns').create({}); + if (!opts.challenges) { + opts.challenges = {}; + } + opts.challenges["http-01"] = require("le-challenge-fs").create({}); + // Note: When implementing a dns-01 plugin you should make it check in a loop + // until it can positively confirm that the DNS changes have propagated. + // That could take several seconds to a few minutes. + opts.challenges["dns-01"] = require("le-challenge-dns").create({}); - // explicitly set account id and certificate.id - opts.account = { id: opts.email }; - opts.certificate = { id: opts.subject }; + // explicitly set account id and certificate.id + opts.account = { id: opts.email }; + opts.certificate = { id: opts.subject }; - return Promise.resolve(opts); + return Promise.resolve(opts); } diff --git a/index.js b/index.js index a5124ee..bc97e9e 100644 --- a/index.js +++ b/index.js @@ -1,265 +1,321 @@ -'use strict'; +"use strict"; var PromiseA; try { - PromiseA = require('bluebird'); -} catch(e) { - PromiseA = global.Promise; + PromiseA = require("bluebird"); +} catch (e) { + PromiseA = global.Promise; } // opts.approveDomains(options, certs, cb) -module.exports.create = function (opts) { - // accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware - if (!opts._communityPackage) { - opts._communityPackage = 'greenlock-express.js'; - opts._communityPackageVersion = require('./package.json').version; - } +module.exports.create = function(opts) { + // accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware + if (!opts._communityPackage) { + opts._communityPackage = "greenlock-express.js"; + opts._communityPackageVersion = require("./package.json").version; + } - function explainError(e) { - console.error('Error:' + e.message); - if ('EACCES' === e.errno) { - console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'."); - console.error("You probably need to use \"sudo\" or \"sudo setcap 'cap_net_bind_service=+ep' $(which node)\""); - return; - } - if ('EADDRINUSE' === e.errno) { - console.error("'" + e.address + ":" + e.port + "' is already being used by some other program."); - console.error("You probably need to stop that program or restart your computer."); - return; - } - console.error(e.code + ": '" + e.address + ":" + e.port + "'"); - } + function explainError(e) { + console.error("Error:" + e.message); + if ("EACCES" === e.errno) { + console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'."); + console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"'); + return; + } + if ("EADDRINUSE" === e.errno) { + console.error("'" + e.address + ":" + e.port + "' is already being used by some other program."); + console.error("You probably need to stop that program or restart your computer."); + return; + } + console.error(e.code + ": '" + e.address + ":" + e.port + "'"); + } - function _createPlain(plainPort) { - if (!plainPort) { plainPort = 80; } + function _createPlain(plainPort) { + if (!plainPort) { + plainPort = 80; + } - var parts = String(plainPort).split(':'); - var p = parts.pop(); - var addr = parts.join(':').replace(/^\[/, '').replace(/\]$/, ''); - var args = []; - var httpType; - var server; - var validHttpPort = (parseInt(p, 10) >= 0); + var parts = String(plainPort).split(":"); + var p = parts.pop(); + var addr = parts + .join(":") + .replace(/^\[/, "") + .replace(/\]$/, ""); + var args = []; + var httpType; + var server; + var validHttpPort = parseInt(p, 10) >= 0; - if (addr) { args[1] = addr; } - if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { - console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); - } + if (addr) { + args[1] = addr; + } + if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { + console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); + } - server = require('http').createServer( - greenlock.middleware.sanitizeHost(greenlock.middleware(require('redirect-https')())) - ); - httpType = 'http'; + server = require("http").createServer( + greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")())) + ); + httpType = "http"; - return { server: server, listen: function () { return new PromiseA(function (resolve, reject) { - args[0] = p; - args.push(function () { - if (!greenlock.servername) { - if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) { - greenlock.servername = greenlock.approvedDomains[0]; - } - if (Array.isArray(greenlock.approveDomains) && greenlock.approvedDomains.length) { - greenlock.servername = greenlock.approvedDomains[0]; - } - } + return { + server: server, + listen: function() { + return new PromiseA(function(resolve, reject) { + args[0] = p; + args.push(function() { + if (!greenlock.servername) { + if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) { + greenlock.servername = greenlock.approvedDomains[0]; + } + if (Array.isArray(greenlock.approveDomains) && greenlock.approvedDomains.length) { + greenlock.servername = greenlock.approvedDomains[0]; + } + } - if (!greenlock.servername) { - resolve(null); - return; - } + if (!greenlock.servername) { + resolve(null); + return; + } - return greenlock.check({ domains: [ greenlock.servername ] }).then(function (certs) { - if (certs) { - return { - key: Buffer.from(certs.privkey, 'ascii') - , cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii') - }; - } - console.info("Fetching certificate for '%s' to use as default for HTTPS server...", greenlock.servername); - return new PromiseA(function (resolve, reject) { - // using SNICallback because all options will be set - greenlock.tlsOptions.SNICallback(greenlock.servername, function (err/*, secureContext*/) { - if (err) { reject(err); return; } - return greenlock.check({ domains: [ greenlock.servername ] }).then(function (certs) { - resolve({ - key: Buffer.from(certs.privkey, 'ascii') - , cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii') - }); - }).catch(reject); - }); - }); - }).then(resolve).catch(reject); - }); - server.listen.apply(server, args).on('error', function (e) { - if (server.listenerCount('error') < 2) { - console.warn("Did not successfully create http server and bind to port '" + p + "':"); - explainError(e); - process.exit(41); - } - }); - }); } }; - } + return greenlock + .check({ domains: [greenlock.servername] }) + .then(function(certs) { + if (certs) { + return { + key: Buffer.from(certs.privkey, "ascii"), + cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii") + }; + } + console.info( + "Fetching certificate for '%s' to use as default for HTTPS server...", + greenlock.servername + ); + return new PromiseA(function(resolve, reject) { + // using SNICallback because all options will be set + greenlock.tlsOptions.SNICallback(greenlock.servername, function(err /*, secureContext*/) { + if (err) { + reject(err); + return; + } + return greenlock + .check({ domains: [greenlock.servername] }) + .then(function(certs) { + resolve({ + key: Buffer.from(certs.privkey, "ascii"), + cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii") + }); + }) + .catch(reject); + }); + }); + }) + .then(resolve) + .catch(reject); + }); + server.listen.apply(server, args).on("error", function(e) { + if (server.listenerCount("error") < 2) { + console.warn("Did not successfully create http server and bind to port '" + p + "':"); + explainError(e); + process.exit(41); + } + }); + }); + } + }; + } - function _create(port) { - if (!port) { port = 443; } + function _create(port) { + if (!port) { + port = 443; + } - var parts = String(port).split(':'); - var p = parts.pop(); - var addr = parts.join(':').replace(/^\[/, '').replace(/\]$/, ''); - var args = []; - var httpType; - var server; - var validHttpPort = (parseInt(p, 10) >= 0); + var parts = String(port).split(":"); + var p = parts.pop(); + var addr = parts + .join(":") + .replace(/^\[/, "") + .replace(/\]$/, ""); + var args = []; + var httpType; + var server; + var validHttpPort = parseInt(p, 10) >= 0; - if (addr) { args[1] = addr; } - if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { - console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); - } + if (addr) { + args[1] = addr; + } + if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { + console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); + } - var https; - try { - https = require('spdy'); - greenlock.tlsOptions.spdy = { protocols: [ 'h2', 'http/1.1' ], plain: false }; - httpType = 'http2 (spdy/h2)'; - } catch(e) { - https = require('https'); - httpType = 'https'; - } - var sniCallback = greenlock.tlsOptions.SNICallback; - greenlock.tlsOptions.SNICallback = function (domain, cb) { - sniCallback(domain, function (err, context) { - cb(err, context); + var https; + try { + https = require("spdy"); + greenlock.tlsOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false }; + httpType = "http2 (spdy/h2)"; + } catch (e) { + https = require("https"); + httpType = "https"; + } + var sniCallback = greenlock.tlsOptions.SNICallback; + greenlock.tlsOptions.SNICallback = function(domain, cb) { + sniCallback(domain, function(err, context) { + cb(err, context); - if (!context || server._hasDefaultSecureContext) { return; } - if (!domain) { domain = greenlock.servername; } - if (!domain) { return; } + if (!context || server._hasDefaultSecureContext) { + return; + } + if (!domain) { + domain = greenlock.servername; + } + if (!domain) { + return; + } - return greenlock.check({ domains: [ domain ] }).then(function (certs) { - // ignore the case that check doesn't have all the right args here - // to get the same certs that it just got (eventually the right ones will come in) - if (!certs) { return; } - if (server.setSecureContext) { - // only available in node v11.0+ - server.setSecureContext({ - key: Buffer.from(certs.privkey, 'ascii') - , cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii') - }); - console.info("Using '%s' as default certificate", domain); - } else { - console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); - } - server._hasDefaultSecureContext = true; - }).catch(function (/*e*/) { - // this may be that the test.example.com was requested, but it's listed - // on the cert for demo.example.com which is in its own directory, not the other - //console.warn("Unusual error: couldn't get newly authorized certificate:"); - //console.warn(e.message); - }); - }); - }; - if (greenlock.tlsOptions.cert) { - server._hasDefaultSecureContext = true; - if (greenlock.tlsOptions.cert.toString('ascii').split("BEGIN").length < 3) { - console.warn("Invalid certificate file. 'tlsOptions.cert' should contain cert.pem (certificate file) *and* chain.pem (intermediate certificates) seperated by an extra newline (CRLF)"); - } - } - server = https.createServer( - greenlock.tlsOptions - , greenlock.middleware.sanitizeHost(function (req, res) { - try { - greenlock.app(req, res); - } catch(e) { - console.error("[error] [greenlock.app] Your HTTP handler had an uncaught error:"); - console.error(e); - try { - res.statusCode = 500; - res.end("Internal Server Error: [Greenlock] HTTP exception logged for user-provided handler."); - } catch(e) { - // ignore - // (headers may have already been sent, etc) - } - } - }) - ); - server.type = httpType; + return greenlock + .check({ domains: [domain] }) + .then(function(certs) { + // ignore the case that check doesn't have all the right args here + // to get the same certs that it just got (eventually the right ones will come in) + if (!certs) { + return; + } + if (server.setSecureContext) { + // only available in node v11.0+ + server.setSecureContext({ + key: Buffer.from(certs.privkey, "ascii"), + cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii") + }); + console.info("Using '%s' as default certificate", domain); + } else { + console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); + } + server._hasDefaultSecureContext = true; + }) + .catch(function(/*e*/) { + // this may be that the test.example.com was requested, but it's listed + // on the cert for demo.example.com which is in its own directory, not the other + //console.warn("Unusual error: couldn't get newly authorized certificate:"); + //console.warn(e.message); + }); + }); + }; + if (greenlock.tlsOptions.cert) { + server._hasDefaultSecureContext = true; + if (greenlock.tlsOptions.cert.toString("ascii").split("BEGIN").length < 3) { + console.warn( + "Invalid certificate file. 'tlsOptions.cert' should contain cert.pem (certificate file) *and* chain.pem (intermediate certificates) seperated by an extra newline (CRLF)" + ); + } + } + server = https.createServer( + greenlock.tlsOptions, + greenlock.middleware.sanitizeHost(function(req, res) { + try { + greenlock.app(req, res); + } catch (e) { + console.error("[error] [greenlock.app] Your HTTP handler had an uncaught error:"); + console.error(e); + try { + res.statusCode = 500; + res.end("Internal Server Error: [Greenlock] HTTP exception logged for user-provided handler."); + } catch (e) { + // ignore + // (headers may have already been sent, etc) + } + } + }) + ); + server.type = httpType; - return { server: server, listen: function () { return new PromiseA(function (resolve) { - args[0] = p; - args.push(function () { resolve(/*server*/); }); - server.listen.apply(server, args).on('error', function (e) { - if (server.listenerCount('error') < 2) { - console.warn("Did not successfully create http server and bind to port '" + p + "':"); - explainError(e); - process.exit(41); - } - }); - }); } }; - } + return { + server: server, + listen: function() { + return new PromiseA(function(resolve) { + args[0] = p; + args.push(function() { + resolve(/*server*/); + }); + server.listen.apply(server, args).on("error", function(e) { + if (server.listenerCount("error") < 2) { + console.warn("Did not successfully create http server and bind to port '" + p + "':"); + explainError(e); + process.exit(41); + } + }); + }); + } + }; + } - // NOTE: 'greenlock' is just 'opts' renamed - var greenlock = require('greenlock').create(opts); + // NOTE: 'greenlock' is just 'opts' renamed + var greenlock = require("greenlock").create(opts); - if (!opts.app) { - opts.app = function (req, res) { - res.end("Hello, World!\nWith Love,\nGreenlock for Express.js"); - }; - } + if (!opts.app) { + opts.app = function(req, res) { + res.end("Hello, World!\nWith Love,\nGreenlock for Express.js"); + }; + } - opts.listen = function (plainPort, port, fnPlain, fn) { - var server; - var plainServer; + opts.listen = function(plainPort, port, fnPlain, fn) { + var server; + var plainServer; - // If there is only one handler for the `listening` (i.e. TCP bound) event - // then we want to use it as HTTPS (backwards compat) - if (!fn) { - fn = fnPlain; - fnPlain = null; - } + // If there is only one handler for the `listening` (i.e. TCP bound) event + // then we want to use it as HTTPS (backwards compat) + if (!fn) { + fn = fnPlain; + fnPlain = null; + } - var obj1 = _createPlain(plainPort, true); - var obj2 = _create(port, false); + var obj1 = _createPlain(plainPort, true); + var obj2 = _create(port, false); - plainServer = obj1.server; - server = obj2.server; + plainServer = obj1.server; + server = obj2.server; - server.then = obj1.listen().then(function (tlsOptions) { - if (tlsOptions) { - if (server.setSecureContext) { - // only available in node v11.0+ - server.setSecureContext(tlsOptions); - console.info("Using '%s' as default certificate", greenlock.servername); - } else { - console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); - } - server._hasDefaultSecureContext = true; - } - return obj2.listen().then(function () { - // Report plain http status - if ('function' === typeof fnPlain) { - fnPlain.apply(plainServer); - } else if (!fn && !plainServer.listenerCount('listening') && !server.listenerCount('listening')) { - console.info('[:' + (plainServer.address().port || plainServer.address()) - + "] Handling ACME challenges and redirecting to " + server.type); - } + server.then = obj1.listen().then(function(tlsOptions) { + if (tlsOptions) { + if (server.setSecureContext) { + // only available in node v11.0+ + server.setSecureContext(tlsOptions); + console.info("Using '%s' as default certificate", greenlock.servername); + } else { + console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); + } + server._hasDefaultSecureContext = true; + } + return obj2.listen().then(function() { + // Report plain http status + if ("function" === typeof fnPlain) { + fnPlain.apply(plainServer); + } else if (!fn && !plainServer.listenerCount("listening") && !server.listenerCount("listening")) { + console.info( + "[:" + + (plainServer.address().port || plainServer.address()) + + "] Handling ACME challenges and redirecting to " + + server.type + ); + } - // Report h2/https status - if ('function' === typeof fn) { - fn.apply(server); - } else if (!server.listenerCount('listening')) { - console.info('[:' + (server.address().port || server.address()) + "] Serving " + server.type); - } - }); - }).then; + // Report h2/https status + if ("function" === typeof fn) { + fn.apply(server); + } else if (!server.listenerCount("listening")) { + console.info("[:" + (server.address().port || server.address()) + "] Serving " + server.type); + } + }); + }).then; - server.unencrypted = plainServer; - return server; - }; - opts.middleware.acme = function (opts) { - return greenlock.middleware.sanitizeHost(greenlock.middleware(require('redirect-https')(opts))); - }; - opts.middleware.secure = function (app) { - return greenlock.middleware.sanitizeHost(app); - }; + server.unencrypted = plainServer; + return server; + }; + opts.middleware.acme = function(opts) { + return greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")(opts))); + }; + opts.middleware.secure = function(app) { + return greenlock.middleware.sanitizeHost(app); + }; - return greenlock; + return greenlock; }; diff --git a/lib/compat.js b/lib/compat.js index e80b911..3e5e7bc 100644 --- a/lib/compat.js +++ b/lib/compat.js @@ -1,37 +1,37 @@ -'use strict'; +"use strict"; function requireBluebird() { - try { - return require('bluebird'); - } catch(e) { - console.error(""); - console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support."); - console.error("EASY FIX: `npm install --save bluebird`"); - console.error(""); - throw e; - } + try { + return require("bluebird"); + } catch (e) { + console.error(""); + console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support."); + console.error("EASY FIX: `npm install --save bluebird`"); + console.error(""); + throw e; + } } -if ('undefined' === typeof Promise) { - global.Promise = requireBluebird(); +if ("undefined" === typeof Promise) { + global.Promise = requireBluebird(); } -if ('function' !== typeof require('util').promisify) { - require('util').promisify = requireBluebird().promisify; +if ("function" !== typeof require("util").promisify) { + require("util").promisify = requireBluebird().promisify; } if (!console.debug) { - console.debug = console.log; + console.debug = console.log; } -var fs = require('fs'); +var fs = require("fs"); var fsAsync = {}; -Object.keys(fs).forEach(function (key) { - var fn = fs[key]; - if ('function' !== typeof fn || !/[a-z]/.test(key[0])) { - return; - } - fsAsync[key] = require('util').promisify(fn); +Object.keys(fs).forEach(function(key) { + var fn = fs[key]; + if ("function" !== typeof fn || !/[a-z]/.test(key[0])) { + return; + } + fsAsync[key] = require("util").promisify(fn); }); exports.fsAsync = fsAsync; diff --git a/package-lock.json b/package-lock.json index 20791e3..174250f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,593 +1,593 @@ { - "name": "greenlock-express", - "version": "2.7.9", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@coolaj86/urequest": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.7.tgz", - "integrity": "sha512-PPrVYra9aWvZjSCKl/x1pJ9ZpXda1652oJrPBYy5rQumJJMkmTBN3ux+sK2xAUwVvv2wnewDlaQaHLxLwSHnIA==" - }, - "@root/mkdirp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", - "integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "dev": true, - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "acme": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/acme/-/acme-1.3.0.tgz", - "integrity": "sha512-Ny8aXnGdtlEVBZLpzNyVp7gff/3zFYrbfbiY93lx5jdrG4BrraA6P8RkRFP7NbFwh1rBgQkdHEsMxTp3f2r8dA==", - "requires": { - "acme-v2": "^1.6.0" - } - }, - "acme-dns-01-cli": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/acme-dns-01-cli/-/acme-dns-01-cli-3.0.7.tgz", - "integrity": "sha512-Aa4bUpq6ftX1VODiShOetOY5U0tsXY5EV7+fQwme3Q8Y9rjYBArBXHgFCAVKtK1AF+Ev8pIuF6Z42hzMFa73/w==" - }, - "acme-v2": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/acme-v2/-/acme-v2-1.7.7.tgz", - "integrity": "sha512-Pg0EQ45h8N2e4K2goYedutCgWxAmtcruwDHr6hgPBgAWEORVb5SQEdXjtEhCrn+APtr7MyFPryyzXpYpDD5ecA==", - "requires": { - "@coolaj86/urequest": "^1.3.6", - "rsa-compat": "^2.0.6" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true - }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", - "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, - "cert-info": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz", - "integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==" - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "eckles": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz", - "integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA==" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "express": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", - "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.3", - "content-disposition": "0.5.2", - "content-type": "~1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.4", - "qs": "6.5.2", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.2", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - } - }, - "express-basic-auth": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.1.6.tgz", - "integrity": "sha512-fRh/UU2q/YhvY0/Pkzi3VcLyjIExveW2NOOnOGgO6yO0jKXt6zcKPVPWSrL8nlhlh+YEH5LOjz+CGFML5dJQNw==", - "dev": true, - "requires": { - "basic-auth": "^2.0.1" - } - }, - "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "greenlock": { - "version": "2.7.24", - "resolved": "https://registry.npmjs.org/greenlock/-/greenlock-2.7.24.tgz", - "integrity": "sha512-GQb2LMF6IiEzhp01F6eIN7HlPVlUWpWsBZZn7DOIo9upFAWhFpn2w1PStjGb17VmTkg+lgxzcajqcy6AJhCHUQ==", - "requires": { - "acme": "^1.3.0", - "acme-dns-01-cli": "^3.0.0", - "acme-v2": "^1.7.7", - "cert-info": "^1.5.1", - "greenlock-store-fs": "^3.0.2", - "keypairs": "^1.2.14", - "le-challenge-fs": "^2.0.2", - "le-sni-auto": "^2.1.9", - "le-store-certbot": "^2.2.3", - "rsa-compat": "^2.0.8" - } - }, - "greenlock-store-fs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.0.2.tgz", - "integrity": "sha512-t4So75yKs1+7TqmxD5UKdf+zOQU0/4o0lb2auf5zUcAo7fwwNLOAXyWnnZRL3WuFBUiBGh1qXWleuMua0d3LPg==", - "requires": { - "@root/mkdirp": "^1.0.0", - "safe-replace": "^1.1.0" - } - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ipaddr.js": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", - "dev": true - }, - "keypairs": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.14.tgz", - "integrity": "sha512-ZoZfZMygyB0QcjSlz7Rh6wT2CJasYEHBPETtmHZEfxuJd7bnsOG5AdtPZqHZBT+hoHvuWCp/4y8VmvTvH0Y9uA==", - "requires": { - "eckles": "^1.4.1", - "rasha": "^1.2.4" - } - }, - "le-challenge-fs": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/le-challenge-fs/-/le-challenge-fs-2.0.9.tgz", - "integrity": "sha512-stzI6rxd+aXGxBl87QJKKY/i/wl3uz6EoWzX2xSazJvCPSYBQys1RVNgOcf0SfUQPh6TBCFJFSJkiR4mznb4sg==", - "requires": { - "@root/mkdirp": "^1.0.0" - } - }, - "le-sni-auto": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/le-sni-auto/-/le-sni-auto-2.1.9.tgz", - "integrity": "sha512-QmQHNwQDi/56GY8+qczFZ06FZbxaeJQjbjEhwwQHhkJ9IHhIQFkPfCT/OyDfLj4gqLIrg5ZX8CemxxVZnLEYfg==" - }, - "le-store-certbot": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/le-store-certbot/-/le-store-certbot-2.2.3.tgz", - "integrity": "sha512-c4ACR+v+JKMiAOOshLh6gdCKA7wIWR16+mROMLpQjq3rXJ3Vm8FaBHe2H+crT+flP+g7FmciAwUlfOJEJpIuCQ==", - "requires": { - "@root/mkdirp": "^1.0.0", - "pyconf": "^1.1.7", - "safe-replace": "^1.1.0" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true - }, - "mime-db": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", - "dev": true - }, - "mime-types": { - "version": "2.1.22", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", - "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", - "dev": true, - "requires": { - "mime-db": "~1.38.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "proxy-addr": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", - "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", - "dev": true, - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.8.0" - } - }, - "pyconf": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/pyconf/-/pyconf-1.1.7.tgz", - "integrity": "sha512-v4clh33m68sjtMsh8XMpjhGWb/MQODAYZ1y7ORG5Qv58UK25OddoB+oXyexgDkK8ttFui/lZm2sQDgA2Ftjfkw==", - "requires": { - "safe-replace": "^1.0.2" - } - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "rasha": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.5.tgz", - "integrity": "sha512-KxtX+/fBk+wM7O3CNgwjSh5elwFilLvqWajhr6wFr2Hd63JnKTTi43Tw+Jb1hxJQWOwoya+NZWR2xztn3hCrTw==" - }, - "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", - "unpipe": "1.0.0" - } - }, - "redirect-https": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/redirect-https/-/redirect-https-1.3.0.tgz", - "integrity": "sha512-9GzwI/+Cqw3jlSg0CW6TgBQbhiVhkHSDvW8wjgRQ9IK34wtxS71YJiQeazSCSEqbvowHCJuQZgmQFl1xUHKEgg==", - "requires": { - "escape-html": "^1.0.3" - } - }, - "rsa-compat": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.8.tgz", - "integrity": "sha512-BFiiSEbuxzsVdaxpejbxfX07qs+rtous49Y6mL/zw6YHh9cranDvm2BvBmqT3rso84IsxNlP5BXnuNvm1Wn3Tw==", - "requires": { - "keypairs": "^1.2.14" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", - "integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - } - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - } - }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - } - } + "name": "greenlock-express", + "version": "2.7.9", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@coolaj86/urequest": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.7.tgz", + "integrity": "sha512-PPrVYra9aWvZjSCKl/x1pJ9ZpXda1652oJrPBYy5rQumJJMkmTBN3ux+sK2xAUwVvv2wnewDlaQaHLxLwSHnIA==" + }, + "@root/mkdirp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", + "integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "acme": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/acme/-/acme-1.3.0.tgz", + "integrity": "sha512-Ny8aXnGdtlEVBZLpzNyVp7gff/3zFYrbfbiY93lx5jdrG4BrraA6P8RkRFP7NbFwh1rBgQkdHEsMxTp3f2r8dA==", + "requires": { + "acme-v2": "^1.6.0" + } + }, + "acme-dns-01-cli": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/acme-dns-01-cli/-/acme-dns-01-cli-3.0.7.tgz", + "integrity": "sha512-Aa4bUpq6ftX1VODiShOetOY5U0tsXY5EV7+fQwme3Q8Y9rjYBArBXHgFCAVKtK1AF+Ev8pIuF6Z42hzMFa73/w==" + }, + "acme-v2": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/acme-v2/-/acme-v2-1.7.7.tgz", + "integrity": "sha512-Pg0EQ45h8N2e4K2goYedutCgWxAmtcruwDHr6hgPBgAWEORVb5SQEdXjtEhCrn+APtr7MyFPryyzXpYpDD5ecA==", + "requires": { + "@coolaj86/urequest": "^1.3.6", + "rsa-compat": "^2.0.6" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cert-info": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz", + "integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "eckles": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz", + "integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "express-basic-auth": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.1.6.tgz", + "integrity": "sha512-fRh/UU2q/YhvY0/Pkzi3VcLyjIExveW2NOOnOGgO6yO0jKXt6zcKPVPWSrL8nlhlh+YEH5LOjz+CGFML5dJQNw==", + "dev": true, + "requires": { + "basic-auth": "^2.0.1" + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "greenlock": { + "version": "2.7.24", + "resolved": "https://registry.npmjs.org/greenlock/-/greenlock-2.7.24.tgz", + "integrity": "sha512-GQb2LMF6IiEzhp01F6eIN7HlPVlUWpWsBZZn7DOIo9upFAWhFpn2w1PStjGb17VmTkg+lgxzcajqcy6AJhCHUQ==", + "requires": { + "acme": "^1.3.0", + "acme-dns-01-cli": "^3.0.0", + "acme-v2": "^1.7.7", + "cert-info": "^1.5.1", + "greenlock-store-fs": "^3.0.2", + "keypairs": "^1.2.14", + "le-challenge-fs": "^2.0.2", + "le-sni-auto": "^2.1.9", + "le-store-certbot": "^2.2.3", + "rsa-compat": "^2.0.8" + } + }, + "greenlock-store-fs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.0.2.tgz", + "integrity": "sha512-t4So75yKs1+7TqmxD5UKdf+zOQU0/4o0lb2auf5zUcAo7fwwNLOAXyWnnZRL3WuFBUiBGh1qXWleuMua0d3LPg==", + "requires": { + "@root/mkdirp": "^1.0.0", + "safe-replace": "^1.1.0" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", + "dev": true + }, + "keypairs": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.14.tgz", + "integrity": "sha512-ZoZfZMygyB0QcjSlz7Rh6wT2CJasYEHBPETtmHZEfxuJd7bnsOG5AdtPZqHZBT+hoHvuWCp/4y8VmvTvH0Y9uA==", + "requires": { + "eckles": "^1.4.1", + "rasha": "^1.2.4" + } + }, + "le-challenge-fs": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/le-challenge-fs/-/le-challenge-fs-2.0.9.tgz", + "integrity": "sha512-stzI6rxd+aXGxBl87QJKKY/i/wl3uz6EoWzX2xSazJvCPSYBQys1RVNgOcf0SfUQPh6TBCFJFSJkiR4mznb4sg==", + "requires": { + "@root/mkdirp": "^1.0.0" + } + }, + "le-sni-auto": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/le-sni-auto/-/le-sni-auto-2.1.9.tgz", + "integrity": "sha512-QmQHNwQDi/56GY8+qczFZ06FZbxaeJQjbjEhwwQHhkJ9IHhIQFkPfCT/OyDfLj4gqLIrg5ZX8CemxxVZnLEYfg==" + }, + "le-store-certbot": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/le-store-certbot/-/le-store-certbot-2.2.3.tgz", + "integrity": "sha512-c4ACR+v+JKMiAOOshLh6gdCKA7wIWR16+mROMLpQjq3rXJ3Vm8FaBHe2H+crT+flP+g7FmciAwUlfOJEJpIuCQ==", + "requires": { + "@root/mkdirp": "^1.0.0", + "pyconf": "^1.1.7", + "safe-replace": "^1.1.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, + "requires": { + "mime-db": "~1.38.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "pyconf": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pyconf/-/pyconf-1.1.7.tgz", + "integrity": "sha512-v4clh33m68sjtMsh8XMpjhGWb/MQODAYZ1y7ORG5Qv58UK25OddoB+oXyexgDkK8ttFui/lZm2sQDgA2Ftjfkw==", + "requires": { + "safe-replace": "^1.0.2" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "rasha": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.5.tgz", + "integrity": "sha512-KxtX+/fBk+wM7O3CNgwjSh5elwFilLvqWajhr6wFr2Hd63JnKTTi43Tw+Jb1hxJQWOwoya+NZWR2xztn3hCrTw==" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "redirect-https": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/redirect-https/-/redirect-https-1.3.0.tgz", + "integrity": "sha512-9GzwI/+Cqw3jlSg0CW6TgBQbhiVhkHSDvW8wjgRQ9IK34wtxS71YJiQeazSCSEqbvowHCJuQZgmQFl1xUHKEgg==", + "requires": { + "escape-html": "^1.0.3" + } + }, + "rsa-compat": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.8.tgz", + "integrity": "sha512-BFiiSEbuxzsVdaxpejbxfX07qs+rtous49Y6mL/zw6YHh9cranDvm2BvBmqT3rso84IsxNlP5BXnuNvm1Wn3Tw==", + "requires": { + "keypairs": "^1.2.14" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", + "integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } } diff --git a/package.json b/package.json index 9c7a2b6..c13bd79 100644 --- a/package.json +++ b/package.json @@ -1,50 +1,50 @@ { - "name": "greenlock-express", - "version": "2.7.9", - "description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.", - "main": "index.js", - "homepage": "https://greenlock.domains", - "directories": { - "example": "examples" - }, - "dependencies": { - "greenlock": "^2.7.24", - "redirect-https": "^1.1.5" - }, - "files": [ - "lib" - ], - "trulyOptionalDependencies": { - "spdy": "^3.4.7" - }, - "devDependencies": { - "express": "^4.16.3", - "express-basic-auth": "^1.1.5", - "finalhandler": "^1.1.1", - "serve-index": "^1.9.1", - "serve-static": "^1.13.2", - "ws": "^5.2.1" - }, - "scripts": { - "start": "node server.js ./config.js", - "test": "node test/greenlock.js" - }, - "repository": { - "type": "git", - "url": "https://git.rootprojects.org/root/greenlock-express.js.git" - }, - "keywords": [ - "Let's Encrypt", - "ACME", - "greenlock", - "Free SSL", - "Automated HTTPS", - "https", - "tls" - ], - "author": "AJ ONeal (https://solderjs.com/)", - "license": "MPL-2.0", - "bugs": { - "url": "https://git.rootprojects.org/root/greenlock-express.js/issues" - } + "name": "greenlock-express", + "version": "2.7.9", + "description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.", + "main": "index.js", + "homepage": "https://greenlock.domains", + "directories": { + "example": "examples" + }, + "dependencies": { + "greenlock": "^2.7.24", + "redirect-https": "^1.1.5" + }, + "files": [ + "lib" + ], + "trulyOptionalDependencies": { + "spdy": "^3.4.7" + }, + "devDependencies": { + "express": "^4.16.3", + "express-basic-auth": "^1.1.5", + "finalhandler": "^1.1.1", + "serve-index": "^1.9.1", + "serve-static": "^1.13.2", + "ws": "^5.2.1" + }, + "scripts": { + "start": "node server.js ./config.js", + "test": "node test/greenlock.js" + }, + "repository": { + "type": "git", + "url": "https://git.rootprojects.org/root/greenlock-express.js.git" + }, + "keywords": [ + "Let's Encrypt", + "ACME", + "greenlock", + "Free SSL", + "Automated HTTPS", + "https", + "tls" + ], + "author": "AJ ONeal (https://solderjs.com/)", + "license": "MPL-2.0", + "bugs": { + "url": "https://git.rootprojects.org/root/greenlock-express.js/issues" + } } diff --git a/server.js b/server.js index 9752d2a..de5ebc9 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -'use strict'; +"use strict"; /*global Promise*/ ///////////////////////////////// @@ -15,189 +15,219 @@ // ex: /srv/api/api.example.com // -var configpath = process.argv[2] || './config.js'; +var configpath = process.argv[2] || "./config.js"; var config = require(configpath); // The prefix where sites go by name. // For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path -var path = require('path'); -var fs = require('./lib/compat.js').fsAsync; -var finalhandler = require('finalhandler'); -var serveStatic = require('serve-static'); +var path = require("path"); +var fs = require("./lib/compat.js").fsAsync; +var finalhandler = require("finalhandler"); +var serveStatic = require("serve-static"); //var glx = require('greenlock-express') -var glx = require('./').create({ +var glx = require("./").create({ + version: "draft-11", // Let's Encrypt v2 is ACME draft 11 - version: 'draft-11' // Let's Encrypt v2 is ACME draft 11 + //, server: 'https://acme-staging-v02.api.letsencrypt.org/directory' + server: "https://acme-v02.api.letsencrypt.org/directory", // If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory -//, server: 'https://acme-staging-v02.api.letsencrypt.org/directory' -, server: 'https://acme-v02.api.letsencrypt.org/directory' // If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory + configDir: config.configDir, // You MUST have access to write to directory where certs + // are saved. ex: /home/foouser/.config/acme -, configDir: config.configDir // You MUST have access to write to directory where certs - // are saved. ex: /home/foouser/.config/acme + approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the + // domain name here and reject invalid ones -, approveDomains: myApproveDomains // Greenlock's wraps around tls.SNICallback. Check the - // domain name here and reject invalid ones + app: myVhostApp, // Any node-style http app (i.e. express, koa, hapi, rill) -, app: myVhostApp // Any node-style http app (i.e. express, koa, hapi, rill) - - /* CHANGE TO A VALID EMAIL */ -, email: config.email // Email for Let's Encrypt account and Greenlock Security -, agreeTos: true // Accept Let's Encrypt ToS -//, communityMember: true // Join Greenlock to get important updates, no spam - -//, debug: true -, store: require('greenlock-store-fs') + /* CHANGE TO A VALID EMAIL */ + email: config.email, // Email for Let's Encrypt account and Greenlock Security + agreeTos: true, // Accept Let's Encrypt ToS + //, communityMember: true // Join Greenlock to get important updates, no spam + //, debug: true + store: require("greenlock-store-fs") }); var server = glx.listen(80, 443); -server.on('listening', function () { - console.info(server.type + " listening on", server.address()); +server.on("listening", function() { + console.info(server.type + " listening on", server.address()); }); function myApproveDomains(opts) { - console.info("SNI:", opts.domain); - // In this example the filesystem is our "database". - // We check in /srv/www for whatever.com and if it exists, it's allowed - // SECURITY Greenlock validates opts.domains ahead-of-time so you don't have to + console.info("SNI:", opts.domain); + // In this example the filesystem is our "database". + // We check in /srv/www for whatever.com and if it exists, it's allowed + // SECURITY Greenlock validates opts.domains ahead-of-time so you don't have to - var domains = []; - var domain = opts.domain.replace(/^(www|api)\./, ''); - return checkWwws(domain).then(function (hostname) { - // this is either example.com or www.example.com - domains.push(hostname); - if ('api.' + domain !== opts.domain) { - if (!domains.includes(opts.domain)) { - domains.push(opts.domain) - } - } - }).catch(function () { - // ignore error - return null; - }).then(function () { - // check for api prefix - var apiname = domain; - if (domains.length) { - apiname = 'api.' + domain; - } - return checkApi(apiname).then(function (app) { - if (!app) { return null; } - domains.push(apiname); - }).catch(function () { - return null; - }); - }).then(function () { - if (0 === domains.length) { - return Promise.reject(new Error("no bare, www., or api. domain matching '" + opts.domain + "'")); - } + var domains = []; + var domain = opts.domain.replace(/^(www|api)\./, ""); + return checkWwws(domain) + .then(function(hostname) { + // this is either example.com or www.example.com + domains.push(hostname); + if ("api." + domain !== opts.domain) { + if (!domains.includes(opts.domain)) { + domains.push(opts.domain); + } + } + }) + .catch(function() { + // ignore error + return null; + }) + .then(function() { + // check for api prefix + var apiname = domain; + if (domains.length) { + apiname = "api." + domain; + } + return checkApi(apiname) + .then(function(app) { + if (!app) { + return null; + } + domains.push(apiname); + }) + .catch(function() { + return null; + }); + }) + .then(function() { + if (0 === domains.length) { + return Promise.reject(new Error("no bare, www., or api. domain matching '" + opts.domain + "'")); + } - console.info('Approved domains:', domains); - opts.domains = domains; - //opts.email = email; - opts.agreeTos = true; - // pick the shortest (bare) or latest (www. instead of api.) to be the subject - opts.subject = opts.domains.sort(function (a, b) { - var len = a.length - b.length; - if (0 !== len) { return len; } - if (a < b) { return 1; } else { return -1; } - })[0]; + console.info("Approved domains:", domains); + opts.domains = domains; + //opts.email = email; + opts.agreeTos = true; + // pick the shortest (bare) or latest (www. instead of api.) to be the subject + opts.subject = opts.domains.sort(function(a, b) { + var len = a.length - b.length; + if (0 !== len) { + return len; + } + if (a < b) { + return 1; + } else { + return -1; + } + })[0]; - if (!opts.challenges) { opts.challenges = {}; } - opts.challenges['http-01'] = require('le-challenge-fs'); - //opts.challenges['dns-01'] = require('le-challenge-dns'); + if (!opts.challenges) { + opts.challenges = {}; + } + opts.challenges["http-01"] = require("le-challenge-fs"); + //opts.challenges['dns-01'] = require('le-challenge-dns'); - // explicitly set account id and certificate.id - opts.account = { id: opts.email }; - opts.certificate = { id: opts.subject }; + // explicitly set account id and certificate.id + opts.account = { id: opts.email }; + opts.certificate = { id: opts.subject }; - return Promise.resolve(opts); - }); + return Promise.resolve(opts); + }); } function checkApi(hostname) { - var apipath = path.join(config.api, hostname); - var link = ''; - return fs.stat(apipath).then(function (stats) { - if (stats.isDirectory()) { - return require(apipath); - } - return fs.readFile(apipath, 'utf8').then(function (txt) { - var linkpath = txt.split('\n')[0]; - link = (' => ' + linkpath + ' '); - return require(linkpath); - }); - }).catch(function (e) { - if ('ENOENT' === e.code) { return null; } - console.error(e); - throw new Error("rejecting '" + hostname + "' because '" + apipath + link + "' failed at require()"); - }); + var apipath = path.join(config.api, hostname); + var link = ""; + return fs + .stat(apipath) + .then(function(stats) { + if (stats.isDirectory()) { + return require(apipath); + } + return fs.readFile(apipath, "utf8").then(function(txt) { + var linkpath = txt.split("\n")[0]; + link = " => " + linkpath + " "; + return require(linkpath); + }); + }) + .catch(function(e) { + if ("ENOENT" === e.code) { + return null; + } + console.error(e); + throw new Error("rejecting '" + hostname + "' because '" + apipath + link + "' failed at require()"); + }); } function checkWwws(_hostname) { - if (!_hostname) { - // SECURITY don't serve the whole config.srv - return Promise.reject(new Error("missing hostname")); - } - var hostname = _hostname; - var hostdir = path.join(config.srv, hostname); - // TODO could test for www/no-www both in directory - return fs.readdir(hostdir).then(function () { - // TODO check for some sort of htaccess.json and use email in that - // NOTE: you can also change other options such as `challengeType` and `challenge` - // opts.challengeType = 'http-01'; - // opts.challenge = require('le-challenge-fs').create({}); - return hostname; - }).catch(function () { - if ('www.' === hostname.slice(0, 4)) { - // Assume we'll redirect to non-www if it's available. - hostname = hostname.slice(4); - hostdir = path.join(config.srv, hostname); - return fs.readdir(hostdir).then(function () { - return hostname; - }); - } else { - // Or check and see if perhaps we should redirect non-www to www - hostname = 'www.' + hostname; - hostdir = path.join(config.srv, hostname); - return fs.readdir(hostdir).then(function () { - return hostname; - }); - } - }).catch(function () { - throw new Error("rejecting '" + _hostname + "' because '" + hostdir + "' could not be read"); - }); + if (!_hostname) { + // SECURITY don't serve the whole config.srv + return Promise.reject(new Error("missing hostname")); + } + var hostname = _hostname; + var hostdir = path.join(config.srv, hostname); + // TODO could test for www/no-www both in directory + return fs + .readdir(hostdir) + .then(function() { + // TODO check for some sort of htaccess.json and use email in that + // NOTE: you can also change other options such as `challengeType` and `challenge` + // opts.challengeType = 'http-01'; + // opts.challenge = require('le-challenge-fs').create({}); + return hostname; + }) + .catch(function() { + if ("www." === hostname.slice(0, 4)) { + // Assume we'll redirect to non-www if it's available. + hostname = hostname.slice(4); + hostdir = path.join(config.srv, hostname); + return fs.readdir(hostdir).then(function() { + return hostname; + }); + } else { + // Or check and see if perhaps we should redirect non-www to www + hostname = "www." + hostname; + hostdir = path.join(config.srv, hostname); + return fs.readdir(hostdir).then(function() { + return hostname; + }); + } + }) + .catch(function() { + throw new Error("rejecting '" + _hostname + "' because '" + hostdir + "' could not be read"); + }); } function myVhostApp(req, res) { - // SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to - // (also: only domains approved above will get here) - console.info(req.method, (req.headers.host||'') + req.url); - Object.keys(req.headers).forEach(function (key) { - console.info(key, req.headers[key]) - }); + // SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to + // (also: only domains approved above will get here) + console.info(req.method, (req.headers.host || "") + req.url); + Object.keys(req.headers).forEach(function(key) { + console.info(key, req.headers[key]); + }); - // We could cache wether or not a host exists for some amount of time - var fin = finalhandler(req, res); - return checkWwws(req.headers.host).then(function (hostname) { - if (hostname !== req.headers.host) { - res.statusCode = 302; - res.setHeader('Location', 'https://' + hostname); - // SECURITY this is safe only because greenlock disallows invalid hostnames - res.end(""); - return; - } - var serve = serveStatic(path.join(config.srv, hostname), { redirect: true }); - serve(req, res, fin); - }).catch(function (err) { - return checkApi(req.headers.host).then(function (app) { - if (app) { app(req, res); return; } - console.error("none found", err); - fin(); - }).catch(function (err) { - console.error("api crashed error", err); - fin(err); - }); - }); + // We could cache wether or not a host exists for some amount of time + var fin = finalhandler(req, res); + return checkWwws(req.headers.host) + .then(function(hostname) { + if (hostname !== req.headers.host) { + res.statusCode = 302; + res.setHeader("Location", "https://" + hostname); + // SECURITY this is safe only because greenlock disallows invalid hostnames + res.end(""); + return; + } + var serve = serveStatic(path.join(config.srv, hostname), { redirect: true }); + serve(req, res, fin); + }) + .catch(function(err) { + return checkApi(req.headers.host) + .then(function(app) { + if (app) { + app(req, res); + return; + } + console.error("none found", err); + fin(); + }) + .catch(function(err) { + console.error("api crashed error", err); + fin(err); + }); + }); } diff --git a/test/greenlock.js b/test/greenlock.js index c10e603..b2908ee 100644 --- a/test/greenlock.js +++ b/test/greenlock.js @@ -1,75 +1,85 @@ #!/usr/bin/env node -var Greenlock = require('../'); +var Greenlock = require("../"); var greenlock = Greenlock.create({ - version: 'draft-11' -, server: 'https://acme-staging-v02.api.letsencrypt.org/directory' -, agreeTos: true -, approvedDomains: [ 'example.com', 'www.example.com' ] -, configDir: require('path').join(require('os').tmpdir(), 'acme') + version: "draft-11", + server: "https://acme-staging-v02.api.letsencrypt.org/directory", + agreeTos: true, + approvedDomains: ["example.com", "www.example.com"], + configDir: require("path").join(require("os").tmpdir(), "acme"), -, app: require('express')().use('/', function (req, res) { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.end('Hello, World!\n\nšŸ’š šŸ”’.js'); - }) + app: require("express")().use("/", function(req, res) { + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.end("Hello, World!\n\nšŸ’š šŸ”’.js"); + }) }); var server1 = greenlock.listen(5080, 5443); -server1.on('listening', function () { - console.log("### THREE 3333 - All is well server1", this.address()); - setTimeout(function () { - // so that the address() object doesn't disappear - server1.close(); - server1.unencrypted.close(); - }, 10); +server1.on("listening", function() { + console.log("### THREE 3333 - All is well server1", this.address()); + setTimeout(function() { + // so that the address() object doesn't disappear + server1.close(); + server1.unencrypted.close(); + }, 10); }); -setTimeout(function () { - var server2 = greenlock.listen(6080, 6443, function () { - console.log("### FIVE 55555 - Started server 2!"); - setTimeout(function () { - server2.close(); - server2.unencrypted.close(); - server6.close(); - server6.unencrypted.close(); - server7.close(); - server7.unencrypted.close(); - setTimeout(function () { - // TODO greenlock needs a close event (and to listen to its server's close event) - process.exit(0); - }, 1000); - }, 1000); - }); - server2.on('listening', function () { - console.log("### FOUR 44444 - All is well server2", server2.address()); - }); +setTimeout(function() { + var server2 = greenlock.listen(6080, 6443, function() { + console.log("### FIVE 55555 - Started server 2!"); + setTimeout(function() { + server2.close(); + server2.unencrypted.close(); + server6.close(); + server6.unencrypted.close(); + server7.close(); + server7.unencrypted.close(); + setTimeout(function() { + // TODO greenlock needs a close event (and to listen to its server's close event) + process.exit(0); + }, 1000); + }, 1000); + }); + server2.on("listening", function() { + console.log("### FOUR 44444 - All is well server2", server2.address()); + }); }, 1000); -var server3 = greenlock.listen(22, 22, function () { - console.error("Error: expected to get an error when launching plain server on port 22"); -}, function () { - console.error("Error: expected to get an error when launching " + server3.type + " server on port 22"); +var server3 = greenlock.listen( + 22, + 22, + function() { + console.error("Error: expected to get an error when launching plain server on port 22"); + }, + function() { + console.error("Error: expected to get an error when launching " + server3.type + " server on port 22"); + } +); +server3.unencrypted.on("error", function() { + console.log("Success: caught expected (plain) error"); }); -server3.unencrypted.on('error', function () { - console.log("Success: caught expected (plain) error"); -}); -server3.on('error', function () { - console.log("Success: caught expected " + server3.type + " error"); - //server3.close(); +server3.on("error", function() { + console.log("Success: caught expected " + server3.type + " error"); + //server3.close(); }); -var server4 = greenlock.listen(7080, 7443, function () { - console.log('Success: server4: plain'); - server4.unencrypted.close(); -}, function () { - console.log('Success: server4: ' + server4.type); - server4.close(); +var server4 = greenlock.listen( + 7080, + 7443, + function() { + console.log("Success: server4: plain"); + server4.unencrypted.close(); + }, + function() { + console.log("Success: server4: " + server4.type); + server4.close(); + } +); + +var server5 = greenlock.listen(10080, 10443, function() { + console.log("Server 5 with one fn", this.address()); + server5.close(); + server5.unencrypted.close(); }); -var server5 = greenlock.listen(10080, 10443, function () { - console.log("Server 5 with one fn", this.address()); - server5.close(); - server5.unencrypted.close(); -}); +var server6 = greenlock.listen("[::]:11080", "[::1]:11443"); -var server6 = greenlock.listen('[::]:11080', '[::1]:11443'); - -var server7 = greenlock.listen('/tmp/gl.plain.sock', '/tmp/gl.sec.sock'); +var server7 = greenlock.listen("/tmp/gl.plain.sock", "/tmp/gl.sec.sock");