make Prettier

This commit is contained in:
AJ ONeal 2019-06-03 03:47:07 -06:00
parent a49ccb7398
commit c73ad565a3
21 changed files with 1905 additions and 1777 deletions

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"bracketSpacing": true,
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": true
}

381
README.md
View File

@ -37,30 +37,30 @@ and **node.js middleware systems**.
# Features # Features
- [x] Automatic HTTPS - [x] Automatic HTTPS
- [x] Free SSL - [x] Free SSL
- [x] Free Wildcard SSL - [x] Free Wildcard SSL
- [x] Multiple domain support (up to 100 altnames per SAN) - [x] Multiple domain support (up to 100 altnames per SAN)
- [x] Dynamic Virtual Hosting (vhost) - [x] Dynamic Virtual Hosting (vhost)
- [x] Automatical renewal (10 to 14 days before expiration) - [x] Automatical renewal (10 to 14 days before expiration)
- [x] Great ACME support - [x] Great ACME support
- [x] ACME draft 11 - [x] ACME draft 11
- [x] Let's Encrypt v2 - [x] Let's Encrypt v2
- [x] Let's Encrypt v1 - [x] Let's Encrypt v1
- [x] Full node.js support - [x] Full node.js support
- [x] node v6+ - [x] node v6+
- [x] core https module - [x] core https module
- [x] Express.js - [x] Express.js
- [x] [Koa](https://git.rootprojects.org/root/greenlock-koa.js) - [x] [Koa](https://git.rootprojects.org/root/greenlock-koa.js)
- [x] [hapi](https://git.rootprojects.org/root/greenlock-hapi.js) - [x] [hapi](https://git.rootprojects.org/root/greenlock-hapi.js)
- [x] Extensible Plugin Support - [x] Extensible Plugin Support
- [x] AWS (S3, Route53) - [x] AWS (S3, Route53)
- [x] Azure - [x] Azure
- [x] CloudFlare - [x] CloudFlare
- [x] Consul - [x] Consul
- [x] Digital Ocean - [x] Digital Ocean
- [x] etcd - [x] etcd
- [x] Redis - [x] Redis
# Install # Install
@ -78,18 +78,18 @@ Watch the QuickStart demonstration: [https://youtu.be/e8vaR4CEZ5s](https://youtu
<a href="https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk"><img src="https://i.imgur.com/Y8ix6Ts.png" title="QuickStart Video" alt="YouTube Video Preview" /></a> <a href="https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk"><img src="https://i.imgur.com/Y8ix6Ts.png" title="QuickStart Video" alt="YouTube Video Preview" /></a>
* [0:00](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=0) - Intro - [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 - [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 - [6:37](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk?t=397) - Troubleshooting / Gotchas
#### Beyond the QuickStart (Part 2) #### 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 - [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 - [2:26](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=146) - The `approveDomains` callback
#### Security Concerns (Part 3) #### 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 ### 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. All you have to do is start the webserver and then visit it at its domain name.
`server.js`: `server.js`:
```javascript ```javascript
'use strict'; "use strict";
require('greenlock-express').create({ require("greenlock-express")
email: 'john.doe@example.com' // The email address of the ACME user / hosting provider .create({
, agreeTos: true // You must accept the ToS as the host which handles the certs email: "john.doe@example.com", // The email address of the ACME user / hosting provider
, configDir: '~/.config/acme/' // Writable directory where certs will be saved agreeTos: true, // You must accept the ToS as the host which handles the certs
, communityMember: true // Join the community to get notified of important updates configDir: "~/.config/acme/", // Writable directory where certs will be saved
, telemetry: true // Contribute telemetry data to the project communityMember: true, // Join the community to get notified of important updates
telemetry: true, // Contribute telemetry data to the project
// Using your express app: // Using your express app:
// simply export it as-is, then include it here // simply export it as-is, then include it here
, app: require('./app.js') app: require("./app.js")
//, debug: true //, debug: true
}).listen(80, 443); })
.listen(80, 443);
``` ```
`app.js`: `app.js`:
```js
'use strict';
var express = require('express'); ```js
"use strict";
var express = require("express");
var app = express(); var app = express();
app.use('/', function (req, res) { app.use("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8') res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('Hello, World!\n\n💚 🔒.js'); res.end("Hello, World!\n\n💚 🔒.js");
}) });
// Don't do this: // Don't do this:
// app.listen(3000) // app.listen(3000)
@ -166,29 +170,29 @@ You can see our full privacy policy at <https://greenlock.domains/legal/#privacy
Double check the following: Double check the following:
* **Public Facing IP** for `http-01` challenges - **Public Facing IP** for `http-01` challenges
* Are you running this *as* a public-facing webserver (good)? or localhost (bad)? - Are you running this _as_ a public-facing webserver (good)? or localhost (bad)?
* Does `ifconfig` show a public address (good)? or a private one - 10.x, 192.168.x, etc (bad)? - Does `ifconfig` show a public address (good)? or a private one - 10.x, 192.168.x, etc (bad)?
* If you're on a non-public server, are you using the `dns-01` challenge? - If you're on a non-public server, are you using the `dns-01` challenge?
* **correct ACME version** - **correct ACME version**
* Let's Encrypt **v2** (ACME v2) must use `version: 'draft-11'` - Let's Encrypt **v2** (ACME v2) must use `version: 'draft-11'`
* Let's Encrypt v1 must use `version: 'v01'` - Let's Encrypt v1 must use `version: 'v01'`
* **valid email** - **valid email**
* You MUST set `email` to a **valid address** - You MUST set `email` to a **valid address**
* MX records must validate (`dig MX example.com` for `'john@example.com'`) - MX records must validate (`dig MX example.com` for `'john@example.com'`)
* **valid DNS records** - **valid DNS records**
* Must have public DNS records (test with `dig +trace A example.com; dig +trace www.example.com` for `[ 'example.com', 'www.example.com' ]`) - Must have public DNS records (test with `dig +trace A example.com; dig +trace www.example.com` for `[ 'example.com', 'www.example.com' ]`)
* **write access** - **write access**
* You MUST set `configDir` to a writeable location (test with `touch ~/acme/etc/tmp.tmp`) - You MUST set `configDir` to a writeable location (test with `touch ~/acme/etc/tmp.tmp`)
* **port binding privileges** - **port binding privileges**
* You MUST be able to bind to ports 80 and 443 - You MUST be able to bind to ports 80 and 443
* You can do this via `sudo` or [`setcap`](https://gist.github.com/firstdoit/6389682) - You can do this via `sudo` or [`setcap`](https://gist.github.com/firstdoit/6389682)
* **API limits** - **API limits**
* You MUST NOT exceed the API [**usage limits**](https://letsencrypt.org/docs/staging-environment/) per domain, certificate, IP address, etc - You MUST NOT exceed the API [**usage limits**](https://letsencrypt.org/docs/staging-environment/) per domain, certificate, IP address, etc
* **Red Lock, Untrusted** - **Red Lock, Untrusted**
* You MUST use the **production** server url, not staging - You MUST use the **production** server url, not staging
* The API URL should not have 'acme-staging-v02', but should have 'acme-v02' - The API URL should not have 'acme-staging-v02', but should have 'acme-v02'
* Delete the `configDir` used for getting certificates in staging - Delete the `configDir` used for getting certificates in staging
### Production vs Staging ### Production vs Staging
@ -210,19 +214,19 @@ https://acme-staging-v02.api.letsencrypt.org/directory
## Working Examples ## Working Examples
| Example | Location + Description | | 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. | | **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. | | 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&nbsp;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. | | Virtual&nbsp;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&nbsp;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. | | Wildcard&nbsp;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&nbsp;(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. | | HTTPS&nbsp;(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&nbsp;(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&nbsp;(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&nbsp;(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). | | HTTP2&nbsp;(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&nbsp;(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. | | WebSockets&nbsp;(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). | | 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 <br> Be sure to tell me ([@solderjs](https://twitter.com/@solderjs)) / us ([@GreenlockHTTPS](https://twitter.com/@GreenlockHTTPS)) about it. :) | | - | Build Your Own <br> Be sure to tell me ([@solderjs](https://twitter.com/@solderjs)) / us ([@GreenlockHTTPS](https://twitter.com/@GreenlockHTTPS)) about it. :) |
| Full&nbsp;List | Check out the [examples/](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples) directory | | Full&nbsp;List | Check out the [examples/](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples) directory |
# Plugins # Plugins
@ -230,51 +234,49 @@ https://acme-staging-v02.api.letsencrypt.org/directory
## HTTP-01 Challenges ## HTTP-01 Challenges
| | Plugin | | | Plugin |
|:--------------:|:---------:| | :--------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| **Default (fs)** | [acme-http-01-fs](https://git.rootprojects.org/root/acme-http-01-webroot.js) | | **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) | | **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) | | 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) | | Azure | [kolarcz/node-le-challenge-azure-storage](https://github.com/kolarcz/node-le-challenge-azure-storage) |
| - | Build Your Own <br> [acme-http-01-challenge-test](https://git.rootprojects.org/root/acme-challenge-test.js) | | - | Build Your Own <br> [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) | | 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 ## DNS-01 Challenges
| | Plugin | | | Plugin |
|:--------------:|:---------:| | :--------------: | :----------------------------------------------------------------------------------------------------------------------------------------: |
| **Manual (cli)** | [acme-dns-01-cli](https://git.rootprojects.org/root/acme-dns-01-cli.js) | | **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) | | 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 | [buschtoens/le-challenge-cloudflare](https://github.com/buschtoens/le-challenge-cloudflare) |
| CloudFlare | [llun/le-challenge-cloudflare](https://github.com/llun/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) | | 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) | | etcd | [ceecko/le-challenge-etcd](https://github.com/ceecko/le-challenge-etcd) |
| - | Build Your Own <br> [acme-challenge-test](https://git.rootprojects.org/root/acme-challenge-test.js) | | - | Build Your Own <br> [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 | | 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 ## Account & Certificate Storage
| | Plugin | | | Plugin |
|:--------------:|:---------:| | :------------------: | :---------------------------------------------------------------------------------------------------: |
| **Simplest** | [greenlock-store-fs](https://git.rootprojects.org/root/greenlock-store-fs.js) | | **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) | | 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) | | 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) | | 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) | 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) | | Redis | [digitalbazaar/le-store-redis](https://github.com/digitalbazaar/le-store-redis) |
| - | Build Your Own <br> [greenlock-store-test](https://git.rootprojects.org/root/greenlock-store-test.js) | | - | Build Your Own <br> [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 | | Full List | Search [le-store-](https://www.npmjs.com/search?q=le-store-) on npm |
## Auto-SNI ## Auto-SNI
| | Plugin | | | Plugin |
|:-----------:|:---------:| | :---------: | :-------------------------------------------------------------: |
| **Default** | [le-sni-auto](https://git.coolaj86.com/coolaj86/le-sni-auto.js) | | **Default** | [le-sni-auto](https://git.coolaj86.com/coolaj86/le-sni-auto.js) |
(you probably wouldn't need or want to replace this) (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. **Bugs**: Please report bugs with the community plugins to the appropriate owner first, then here if you don't get a response.
# Usage # Usage
@ -300,34 +302,35 @@ node greenlock-express.js/examples/normal.js
It looks a little more like this: It looks a little more like this:
`serve.js`: `serve.js`:
```javascript ```javascript
'use strict'; "use strict";
// returns an instance of greenlock.js with additional helper methods // returns an instance of greenlock.js with additional helper methods
var glx = require('greenlock-express').create({ var glx = require("greenlock-express").create({
server: 'https://acme-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: // Note: If at first you don't succeed, stop and switch to staging:
// https://acme-staging-v02.api.letsencrypt.org/directory // https://acme-staging-v02.api.letsencrypt.org/directory
, version: 'draft-11' // Let's Encrypt v2 (ACME v2) version: "draft-11", // Let's Encrypt v2 (ACME v2)
// If you wish to replace the default account and domain key storage plugin // If you wish to replace the default account and domain key storage plugin
, store: require('le-store-certbot').create({ store: require("le-store-certbot").create({
configDir: require('path').join(require('os').homedir(), 'acme', 'etc') configDir: require("path").join(require("os").homedir(), "acme", "etc"),
, webrootPath: '/tmp/acme-challenges' webrootPath: "/tmp/acme-challenges"
}) }),
// Contribute telemetry data to the project // Contribute telemetry data to the project
, telemetry: true telemetry: true,
// the default servername to use when the client doesn't specify // the default servername to use when the client doesn't specify
// (because some IoT devices don't support servername indication) // (because some IoT devices don't support servername indication)
, servername: 'example.com' servername: "example.com",
, approveDomains: approveDomains approveDomains: approveDomains
}); });
var server = glx.listen(80, 443, function () { var server = glx.listen(80, 443, function() {
console.log("Listening on port 80 for ACME challenges and 443 for express app."); 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`). 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 ```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) { function approveDomains(opts, certs, cb) {
// This is where you check your database and associated // This is where you check your database and associated
// email addresses with domains and agreements and such // email addresses with domains and agreements and such
// if (!isAllowed(opts.domains)) { return cb(new Error("not allowed")); } // if (!isAllowed(opts.domains)) { return cb(new Error("not allowed")); }
// The domains being approved for the first time are listed in opts.domains // 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) // Certs being renewed are listed in certs.altnames (if that's useful)
// Opt-in to submit stats and get important updates // Opt-in to submit stats and get important updates
opts.communityMember = true; opts.communityMember = true;
// If you wish to replace the default challenge plugin, you may do so here // If you wish to replace the default challenge plugin, you may do so here
opts.challenges = { 'http-01': http01 }; opts.challenges = { "http-01": http01 };
opts.email = 'john.doe@example.com'; opts.email = "john.doe@example.com";
opts.agreeTos = true; opts.agreeTos = true;
// NOTE: you can also change other options such as `challengeType` and `challenge` // NOTE: you can also change other options such as `challengeType` and `challenge`
// opts.challengeType = 'http-01'; // opts.challengeType = 'http-01';
// opts.challenge = require('le-challenge-fs').create({}); // opts.challenge = require('le-challenge-fs').create({});
cb(null, { options: opts, certs: certs }); cb(null, { options: opts, certs: certs });
} }
``` ```
```javascript ```javascript
// handles acme-challenge and redirects to https // handles acme-challenge and redirects to https
require('http').createServer(glx.middleware(require('redirect-https')())).listen(80, function () { require("http")
console.log("Listening for ACME http-01 challenges on", this.address()); .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) {
var app = require('express')(); res.end("Hello, World!");
app.use('/', function (req, res) {
res.end('Hello, World!');
}); });
// handles your app // handles your app
require('https').createServer(glx.httpsOptions, app).listen(443, function () { require("https")
console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); .createServer(glx.httpsOptions, app)
}); .listen(443, function() {
console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address());
});
``` ```
**Security**: **Security**:
@ -395,7 +399,6 @@ require('https').createServer(glx.httpsOptions, app).listen(443, function () {
Greenlock will do a self-check on all domain registrations Greenlock will do a self-check on all domain registrations
to prevent you from hitting rate limits. to prevent you from hitting rate limits.
# API # API
This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO). 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: 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`). - `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. - `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)`
* `listen(80, 443, onListenSecure)` - `listen(80, 443, onListenSecure)`
* `listen(80, 443, onListenPlain, onListenSecure)` - `listen(80, 443, onListenPlain, onListenSecure)`
* `listen('localhost:80', '0.0.0.0:443')` - `listen('localhost:80', '0.0.0.0:443')`
* `listen('[::1]:80', '[::]:443')` - `listen('[::1]:80', '[::]:443')`
* `listen('/tmp/glx.plain.sock', '/tmp/glx.secure.sock')` - `listen('/tmp/glx.plain.sock', '/tmp/glx.secure.sock')`
Brief overview of some simple options for `greenlock.js`: Brief overview of some simple options for `greenlock.js`:
* `opts.server` set to https://acme-v02.api.letsencrypt.org/directory in production - `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.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.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.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.communityMember` Join the community to get notified of important updates and help make greenlock better
* `opts.approveDomains` can be either of: - `opts.approveDomains` can be either of:
* An explicit array of allowed domains such as `[ 'example.com', 'www.example.com' ]` - 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) - 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.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.renewBy` is the **minimum** number of days (in ms) before expiration to renew a certificate.
## Supported ACME versions ## Supported ACME versions
* Let's Encrypt v1 (aka v01) - Let's Encrypt v1 (aka v01)
* Let's Encrypt v2 (aka v02 or ACME draft 11) - Let's Encrypt v2 (aka v02 or ACME draft 11)
* ACME draft 11 (ACME v2 is a misnomer) - ACME draft 11 (ACME v2 is a misnomer)
* Wildcard domains (via dns-01 challenges) - Wildcard domains (via dns-01 challenges)
* `*.example.com` - `*.example.com`
<small>tags: letsencrypt acme free ssl automated https node express.js</small> <small>tags: letsencrypt acme free ssl automated https node express.js</small>

View File

@ -1,9 +1,9 @@
'use strict'; "use strict";
var path = require('path'); var path = require("path");
module.exports = { module.exports = {
email: 'jon.doe@example.com' email: "jon.doe@example.com",
, configDir: path.join(__dirname, 'acme') configDir: path.join(__dirname, "acme"),
, srv: '/srv/www/' srv: "/srv/www/",
, api: '/srv/api/' api: "/srv/api/"
}; };

View File

@ -1,59 +1,56 @@
'use strict'; "use strict";
// npm install spdy@3.x // npm install spdy@3.x
//var Greenlock = require('greenlock-express') //var Greenlock = require('greenlock-express')
var Greenlock = require('../'); var Greenlock = require("../");
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
// 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",
version: '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' // You MUST change this to a valid email address
// Note: If at first you don't succeed, stop and switch to staging email: "jon@example.com",
// https://acme-staging-v02.api.letsencrypt.org/directory
// You MUST change this to a valid email address // You MUST NOT build clients that accept the ToS without asking the user
, email: 'jon@example.com' agreeTos: true,
// You MUST NOT build clients that accept the ToS without asking the user // You MUST change these to valid domains
, agreeTos: true // NOTE: all domains will validated and listed on the certificate
approvedDomains: ["example.com", "www.example.com"],
// You MUST change these to valid domains // You MUST have access to write to directory where certs are saved
// NOTE: all domains will validated and listed on the certificate // ex: /home/foouser/acme/etc
, approvedDomains: [ 'example.com', 'www.example.com' ] configDir: "~/.config/acme/",
// You MUST have access to write to directory where certs are saved // Get notified of important updates and help me make greenlock better
// ex: /home/foouser/acme/etc communityMember: true
, configDir: '~/.config/acme/'
// Get notified of important updates and help me make greenlock better
, communityMember: true
//, debug: true
//, debug: true
}); });
//////////////////////// ////////////////////////
// http-01 Challenges // // http-01 Challenges //
//////////////////////// ////////////////////////
// http-01 challenge happens over http/1.1, not http2 // http-01 challenge happens over http/1.1, not http2
var redirectHttps = require('redirect-https')(); var redirectHttps = require("redirect-https")();
var acmeChallengeHandler = greenlock.middleware(function (req, res) { var acmeChallengeHandler = greenlock.middleware(function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('<h1>Hello, ⚠️ Insecure World!</h1><a>Visit Secure Site</a>' res.end(
+ '<script>document.querySelector("a").href=window.location.href.replace(/^http/i, "https");</script>' "<h1>Hello, ⚠️ Insecure World!</h1><a>Visit Secure Site</a>" +
); '<script>document.querySelector("a").href=window.location.href.replace(/^http/i, "https");</script>'
);
}); });
require('http').createServer(acmeChallengeHandler).listen(80, function () { require("http")
console.log("Listening for ACME http-01 challenges on", this.address()); .createServer(acmeChallengeHandler)
}); .listen(80, function() {
console.log("Listening for ACME http-01 challenges on", this.address());
});
//////////////////////// ////////////////////////
// http2 via SPDY h2 // // 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 // spdy is a drop-in replacement for the https API
var spdyOptions = Object.assign({}, greenlock.tlsOptions); var spdyOptions = Object.assign({}, greenlock.tlsOptions);
spdyOptions.spdy = { protocols: [ 'h2', 'http/1.1' ], plain: false }; spdyOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false };
var server = require('spdy').createServer(spdyOptions, require('express')().use('/', function (req, res) { var server = require("spdy").createServer(
res.setHeader('Content-Type', 'text/html; charset=utf-8'); spdyOptions,
res.end('<h1>Hello, 🔐 Secure World!</h1>'); require("express")().use("/", function(req, res) {
})); res.setHeader("Content-Type", "text/html; charset=utf-8");
server.on('error', function (err) { res.end("<h1>Hello, 🔐 Secure World!</h1>");
console.error(err); })
);
server.on("error", function(err) {
console.error(err);
}); });
server.on('listening', function () { server.on("listening", function() {
console.log("Listening for SPDY/http2/https requests on", this.address()); console.log("Listening for SPDY/http2/https requests on", this.address());
}); });
server.listen(443); server.listen(443);

View File

@ -1,29 +1,30 @@
'use strict'; "use strict";
//require('greenlock-express') //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 server: "https://acme-v02.api.letsencrypt.org/directory",
version: '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' email: "john.doe@example.com",
// 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' 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) { renewWithin: 91 * 24 * 60 * 60 * 1000,
res.end('Hello, World!'); renewBy: 90 * 24 * 60 * 60 * 1000,
})
, renewWithin: (91 * 24 * 60 * 60 * 1000) // Get notified of important updates and help me make greenlock better
, renewBy: (90 * 24 * 60 * 60 * 1000) communityMember: true,
debug: true
// Get notified of important updates and help me make greenlock better })
, communityMember: true .listen(80, 443);
, debug: true
}).listen(80, 443);

View File

@ -1,74 +1,70 @@
'use strict'; "use strict";
//var Greenlock = require('greenlock-express') //var Greenlock = require('greenlock-express')
var Greenlock = require('../'); var Greenlock = require("../");
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
// 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",
version: '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' // You MUST change this to a valid email address
// Note: If at first you don't succeed, stop and switch to staging email: "jon@example.com",
// https://acme-staging-v02.api.letsencrypt.org/directory
// You MUST change this to a valid email address // You MUST NOT build clients that accept the ToS without asking the user
, email: 'jon@example.com' agreeTos: true,
// You MUST NOT build clients that accept the ToS without asking the user // You MUST change these to valid domains
, agreeTos: true // NOTE: all domains will validated and listed on the certificate
approvedDomains: ["example.com", "www.example.com"],
// You MUST change these to valid domains // You MUST have access to write to directory where certs are saved
// NOTE: all domains will validated and listed on the certificate // ex: /home/foouser/acme/etc
, approvedDomains: [ 'example.com', 'www.example.com' ] configDir: "~/.config/acme/",
// You MUST have access to write to directory where certs are saved // Get notified of important updates and help me make greenlock better
// ex: /home/foouser/acme/etc communityMember: true
, configDir: '~/.config/acme/'
// Get notified of important updates and help me make greenlock better
, communityMember: true
//, debug: true
//, debug: true
}); });
//////////////////////// ////////////////////////
// http-01 Challenges // // http-01 Challenges //
//////////////////////// ////////////////////////
// http-01 challenge happens over http/1.1, not http2 // 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); var acmeChallengeHandler = greenlock.middleware(redirectHttps);
require('http').createServer(acmeChallengeHandler).listen(80, function () { require("http")
console.log("Listening for ACME http-01 challenges on", this.address()); .createServer(acmeChallengeHandler)
}); .listen(80, function() {
console.log("Listening for ACME http-01 challenges on", this.address());
});
//////////////////////// ////////////////////////
// node.js' http2 api // // node.js' http2 api //
//////////////////////// ////////////////////////
// http2 is a new API with which you would use hapi or koa, not express // http2 is a new API with which you would use hapi or koa, not express
var server = require('http2').createSecureServer(greenlock.tlsOptions); var server = require("http2").createSecureServer(greenlock.tlsOptions);
server.on('error', function (err) { server.on("error", function(err) {
console.error(err); console.error(err);
}); });
// WARNING: Because the middleware don't handle this API style, // WARNING: Because the middleware don't handle this API style,
// the Host headers are unmodified and potentially dangerous // the Host headers are unmodified and potentially dangerous
// (ex: Host: Robert'); DROP TABLE Students;) // (ex: Host: Robert'); DROP TABLE Students;)
server.on('stream', function (stream, headers) { server.on("stream", function(stream, headers) {
console.log(headers); console.log(headers);
stream.respond({ stream.respond({
'content-type': 'text/html' "content-type": "text/html",
, ':status': 200 ":status": 200
}); });
stream.end('Hello, HTTP2 World!'); stream.end("Hello, HTTP2 World!");
}); });
server.on('listening', function () { server.on("listening", function() {
console.log("Listening for http2 requests on", this.address()); console.log("Listening for http2 requests on", this.address());
}); });
server.listen(443); server.listen(443);

View File

@ -1,15 +1,17 @@
'use strict'; "use strict";
var express = require('express'); var express = require("express");
var app = express(); var app = express();
app.use('/', function (req, res) { app.use("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('Hello, World!\n\n💚 🔒.js'); res.end("Hello, World!\n\n💚 🔒.js");
}); });
// DO NOT DO app.listen() unless we're testing this directly // 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: // Instead do export the app:
module.exports = app; module.exports = app;

View File

@ -1,90 +1,88 @@
'use strict'; "use strict";
// //
// My Secure Server // My Secure Server
// //
//var greenlock = require('greenlock-express') //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 // The previous 'simple' example set these values statically,
// Note: If at first you don't succeed, stop and switch to staging // but this example uses approveDomains() to set them dynamically
// https://acme-staging-v02.api.letsencrypt.org/directory //, email: 'none@see.note.above'
server: 'https://acme-v02.api.letsencrypt.org/directory' //, agreeTos: false
, version: 'draft-11'
// You MUST have write access to save certs
, configDir: '~/.config/acme/'
// The previous 'simple' example set these values statically, // approveDomains is the right place to check a database for
// but this example uses approveDomains() to set them dynamically // email addresses with domains and agreements and such
//, email: 'none@see.note.above' approveDomains: approveDomains,
//, agreeTos: false
// approveDomains is the right place to check a database for app: require("./my-express-app.js"),
// email addresses with domains and agreements and such
, approveDomains: approveDomains
, app: require('./my-express-app.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
//, debug: true
}); });
var server = greenlock.listen(80, 443); var server = greenlock.listen(80, 443);
// //
// My Secure Database Check // My Secure Database Check
// //
function approveDomains(opts, certs, cb) { 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 // The domains being approved for the first time are listed in opts.domains
// (it's an array because managed registration allows for multiple domains, // Certs being renewed are listed in certs.altnames
// which was the case in the simple example) if (certs) {
console.log(opts.domains); opts.domains = [certs.subject].concat(certs.altnames);
}
// The domains being approved for the first time are listed in opts.domains fooCheckDb(opts.domains, function(err, agree, email) {
// Certs being renewed are listed in certs.altnames if (err) {
if (certs) { cb(err);
opts.domains = [certs.subject].concat(certs.altnames); return;
} }
fooCheckDb(opts.domains, function (err, agree, email) { // Services SHOULD automatically accept the ToS and use YOUR email
if (err) { cb(err); return; } // 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 // NOTE: you can also change other options such as `challengeType` and `challenge`
// Clients MUST NOT accept the ToS without asking the user // (this would be helpful if you decided you wanted wildcard support as a domain altname)
opts.agreeTos = agree; // opts.challengeType = 'http-01';
opts.email = email; // opts.challenge = require('le-challenge-fs').create({});
// NOTE: you can also change other options such as `challengeType` and `challenge` cb(null, { options: opts, certs: certs });
// (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 });
});
} }
// //
// My User / Domain Database // My User / Domain Database
// //
function fooCheckDb(domains, cb) { function fooCheckDb(domains, cb) {
// This is an oversimplified example of how we might implement a check in // 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 // our database if we have different rules for different users and domains
var domains = [ 'example.com', 'www.example.com' ]; var domains = ["example.com", "www.example.com"];
var userEmail = 'john.doe@example.com'; var userEmail = "john.doe@example.com";
var userAgrees = true; var userAgrees = true;
var passCheck = opts.domains.every(function (domain) { var passCheck = opts.domains.every(function(domain) {
return -1 !== domains.indexOf(domain); return -1 !== domains.indexOf(domain);
}); });
if (!passCheck) { if (!passCheck) {
cb(new Error('domain not allowed')); cb(new Error("domain not allowed"));
} else { } else {
cb(null, userAgrees, userEmail); cb(null, userAgrees, userEmail);
} }
} }

View File

@ -1,37 +1,38 @@
'use strict'; "use strict";
//require('greenlock-express') //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 server: "https://acme-v02.api.letsencrypt.org/directory",
version: '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' // You MUST change this to a valid email address
// Note: If at first you don't succeed, stop and switch to staging email: "john.doe@example.com",
// https://acme-staging-v02.api.letsencrypt.org/directory
// You MUST change this to a valid email address // You MUST NOT build clients that accept the ToS without asking the user
, email: 'john.doe@example.com' agreeTos: true,
// You MUST NOT build clients that accept the ToS without asking the user // You MUST change these to valid domains
, agreeTos: true // NOTE: all domains will validated and listed on the certificate
approvedDomains: ["example.com", "www.example.com"],
// You MUST change these to valid domains // You MUST have access to write to directory where certs are saved
// NOTE: all domains will validated and listed on the certificate // ex: /home/foouser/acme/etc
, approvedDomains: [ 'example.com', 'www.example.com' ] configDir: "~/.config/acme/",
store: require("greenlock-store-fs"),
// You MUST have access to write to directory where certs are saved app: require("express")().use("/", function(req, res) {
// ex: /home/foouser/acme/etc res.setHeader("Content-Type", "text/html; charset=utf-8");
, configDir: '~/.config/acme/' res.end("Hello, World!\n\n💚 🔒.js");
}),
, app: require('express')().use('/', function (req, res) { // Get notified of important updates and help me make greenlock better
res.setHeader('Content-Type', 'text/html; charset=utf-8'); communityMember: true
res.end('Hello, World!\n\n💚 🔒.js');
})
// Get notified of important updates and help me make greenlock better //, debug: true
, communityMember: true })
.listen(80, 443);
//, debug: true
}).listen(80, 443);

View File

@ -1,4 +1,4 @@
'use strict'; "use strict";
// //
// WARNING: Not for noobs // WARNING: Not for noobs
@ -9,87 +9,96 @@
// This demo is used with tunnel-server.js and tunnel-client.js // This demo is used with tunnel-server.js and tunnel-client.js
// //
var email = 'john.doe@gmail.com'; var email = "john.doe@gmail.com";
var domains = [ 'example.com' ]; var domains = ["example.com"];
var agreeLeTos = true; var agreeLeTos = true;
//var secret = "My Little Brony"; //var secret = "My Little Brony";
var secret = require('crypto').randomBytes(16).toString('hex'); var secret = require("crypto")
.randomBytes(16)
.toString("hex");
require('../').create({ require("../")
version: 'draft-11' .create({
version: "draft-11",
, server: 'https://acme-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 // Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory // 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);
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) { function remoteAccess(secret) {
var express = require('express'); var express = require("express");
var basicAuth = require('express-basic-auth'); var basicAuth = require("express-basic-auth");
var serveIndex = require('serve-index'); var serveIndex = require("serve-index");
var rootIndex = serveIndex('/', { hidden: true, icons: true, view: 'details' }); var rootIndex = serveIndex("/", { hidden: true, icons: true, view: "details" });
var rootFs = express.static('/', { dotfiles: 'allow', redirect: true, index: false }); var rootFs = express.static("/", { dotfiles: "allow", redirect: true, index: false });
var userIndex = serveIndex(require('os').homedir(), { hidden: true, icons: true, view: 'details' }); 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 userFs = express.static(require("os").homedir(), { dotfiles: "allow", redirect: true, index: false });
var app = express(); var app = express();
var realm = 'Login Required'; var realm = "Login Required";
var myAuth = basicAuth({ var myAuth = basicAuth({
users: { 'root': secret, 'user': secret } users: { root: secret, user: secret },
, challenge: true challenge: true,
, realm: realm realm: realm,
, unauthorizedResponse: function (/*req*/) { unauthorizedResponse: function(/*req*/) {
return 'Unauthorized <a href="/">Home</a>'; return 'Unauthorized <a href="/">Home</a>';
} }
}); });
app.get('/', function (req, res) { app.get("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end( res.end('<a href="/browse/">View Files</a>' + "&nbsp; | &nbsp;" + '<a href="/logout/">Logout</a>');
'<a href="/browse/">View Files</a>' });
+ '&nbsp; | &nbsp;' app.use("/logout", function(req, res) {
+ '<a href="/logout/">Logout</a>' res.setHeader("Content-Type", "text/html; charset=utf-8");
); res.setHeader("WWW-Authenticate", 'Basic realm="' + realm + '"');
}); res.statusCode = 401;
app.use('/logout', function (req, res) { //res.setHeader('Location', '/');
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end('Logged out &nbsp; | &nbsp; <a href="/">Home</a>');
res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"'); });
res.statusCode = 401; app.use("/browse", myAuth);
//res.setHeader('Location', '/'); app.use("/browse", function(req, res, next) {
res.end('Logged out &nbsp; | &nbsp; <a href="/">Home</a>'); if ("root" === req.auth.user) {
}); rootFs(req, res, function() {
app.use('/browse', myAuth); rootIndex(req, res, next);
app.use('/browse', function (req, res, next) { });
if ('root' === req.auth.user) { rootFs(req, res, function () { rootIndex(req, res, next); }); return; } return;
if ('user' === req.auth.user) { userFs(req, res, function () { userIndex(req, res, next); }); return; } }
res.end('Sad Panda'); if ("user" === req.auth.user) {
}); userFs(req, res, function() {
userIndex(req, res, next);
});
return;
}
res.end("Sad Panda");
});
console.log(''); console.log("");
console.log(''); console.log("");
console.log('Usernames are\n'); console.log("Usernames are\n");
console.log('\troot'); console.log("\troot");
console.log('\tuser'); console.log("\tuser");
console.log(''); console.log("");
console.log('Password (for both) is\n'); console.log("Password (for both) is\n");
console.log('\t' + secret); console.log("\t" + secret);
console.log(''); console.log("");
console.log("Shhhh... It's a secret to everybody!"); console.log("Shhhh... It's a secret to everybody!");
console.log(''); console.log("");
console.log(''); console.log("");
return app; return app;
} }

View File

@ -2,19 +2,19 @@
// I'm not a fan of `socket.io` because it's huge and complex. // 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. // I much prefer `ws` because it's very simple and easy.
// That said, it's popular....... // That said, it's popular.......
'use strict'; "use strict";
//var greenlock = require('greenlock-express'); //var greenlock = require('greenlock-express');
var greenlock = require('../'); var greenlock = require("../");
var options = require('./greenlock-options.js'); var options = require("./greenlock-options.js");
var socketio = require('socket.io'); var socketio = require("socket.io");
var server; var server;
var io; var io;
// Any node http app will do - whether express, raw http or whatever // Any node http app will do - whether express, raw http or whatever
options.app = require('express')().use('/', function (req, res) { options.app = require("express")().use("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('Hello, World!\n\n💚 🔒.js'); res.end("Hello, World!\n\n💚 🔒.js");
}); });
// The server that's handed back from `listen` is a raw https server // 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); io = socketio(server);
// Then you do your socket.io stuff // Then you do your socket.io stuff
io.on('connection', function (socket) { io.on("connection", function(socket) {
console.log('a user connected'); console.log("a user connected");
socket.emit('Welcome'); socket.emit("Welcome");
socket.on('chat message', function (msg) { socket.on("chat message", function(msg) {
socket.broadcast.emit('chat message', msg); socket.broadcast.emit("chat message", msg);
}); });
}); });

View File

@ -1,54 +1,50 @@
'use strict'; "use strict";
// npm install spdy@3.x // npm install spdy@3.x
//var Greenlock = require('greenlock-express') //var Greenlock = require('greenlock-express')
var Greenlock = require('../'); var Greenlock = require("../");
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
// 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",
version: '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' // You MUST change this to a valid email address
// Note: If at first you don't succeed, stop and switch to staging email: "jon@example.com",
// https://acme-staging-v02.api.letsencrypt.org/directory
// You MUST change this to a valid email address // You MUST NOT build clients that accept the ToS without asking the user
, email: 'jon@example.com' agreeTos: true,
// You MUST NOT build clients that accept the ToS without asking the user // You MUST change these to valid domains
, agreeTos: true // NOTE: all domains will validated and listed on the certificate
approvedDomains: ["example.com", "www.example.com"],
// You MUST change these to valid domains // You MUST have access to write to directory where certs are saved
// NOTE: all domains will validated and listed on the certificate // ex: /home/foouser/acme/etc
, approvedDomains: [ 'example.com', 'www.example.com' ] configDir: "~/.config/acme/", // MUST have write access
// You MUST have access to write to directory where certs are saved // Get notified of important updates and help me make greenlock better
// ex: /home/foouser/acme/etc communityMember: true
, configDir: '~/.config/acme/' // MUST have write access
// Get notified of important updates and help me make greenlock better
, communityMember: true
//, debug: true
//, debug: true
}); });
//////////////////////// ////////////////////////
// http-01 Challenges // // http-01 Challenges //
//////////////////////// ////////////////////////
// http-01 challenge happens over http/1.1, not http2 // 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); var acmeChallengeHandler = greenlock.middleware(redirectHttps);
require('http').createServer(acmeChallengeHandler).listen(80, function () { require("http")
console.log("Listening for ACME http-01 challenges on", this.address()); .createServer(acmeChallengeHandler)
}); .listen(80, function() {
console.log("Listening for ACME http-01 challenges on", this.address());
});
//////////////////////// ////////////////////////
// http2 via SPDY h2 // // 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 // spdy is a drop-in replacement for the https API
var spdyOptions = Object.assign({}, greenlock.tlsOptions); var spdyOptions = Object.assign({}, greenlock.tlsOptions);
spdyOptions.spdy = { protocols: [ 'h2', 'http/1.1' ], plain: false }; spdyOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false };
var myApp = require('./my-express-app.js'); var myApp = require("./my-express-app.js");
var server = require('spdy').createServer(spdyOptions, myApp); var server = require("spdy").createServer(spdyOptions, myApp);
server.on('error', function (err) { server.on("error", function(err) {
console.error(err); console.error(err);
}); });
server.on('listening', function () { server.on("listening", function() {
console.log("Listening for SPDY/http2/https requests on", this.address()); console.log("Listening for SPDY/http2/https requests on", this.address());
}); });
server.listen(443); server.listen(443);

View File

@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
'use strict'; "use strict";
/////////////////// ///////////////////
// vhost example // // vhost example //
@ -11,118 +11,124 @@
// The prefix where sites go by name. // The prefix where sites go by name.
// For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path // 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 path = require("path");
var fs = require('fs').promises; var fs = require("fs").promises;
var finalhandler = require('finalhandler'); var finalhandler = require("finalhandler");
var serveStatic = require('serve-static'); var serveStatic = require("serve-static");
//var glx = require('greenlock-express') //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 configDir: process.argv[4] || "~/.config/acme/", // You MUST have access to write to directory where certs
// https://acme-staging-v02.api.letsencrypt.org/directory // are saved. ex: /home/foouser/.config/acme
, configDir: process.argv[4] || '~/.config/acme/' // You MUST have access to write to directory where certs approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the
// are saved. ex: /home/foouser/.config/acme // domain name here and reject invalid ones
, approveDomains: myApproveDomains // Greenlock's wraps around tls.SNICallback. Check the app: myVhostApp, // Any node-style http app (i.e. express, koa, hapi, rill)
// domain name here and reject invalid ones
, 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
/* CHANGE TO A VALID EMAIL */ agreeTos: true // Accept Let's Encrypt ToS
, email: process.argv[2] || 'jon.doe@example.com' // Email for Let's Encrypt account and Greenlock Security //, communityMember: true // Join Greenlock to get important updates, no spam
, agreeTos: true // Accept Let's Encrypt ToS
//, communityMember: true // Join Greenlock to get important updates, no spam
//, debug: true
//, debug: true
}); });
var server = glx.listen(80, 443); var server = glx.listen(80, 443);
server.on('listening', function () { server.on("listening", function() {
console.info(server.type + " listening on", server.address()); console.info(server.type + " listening on", server.address());
}); });
function myApproveDomains(opts, certs, cb) { function myApproveDomains(opts, certs, cb) {
console.log('sni:', opts.domain); console.log("sni:", opts.domain);
// In this example the filesystem is our "database". // In this example the filesystem is our "database".
// We check in /srv/www for whatever.com and if it exists, it's allowed // 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 // SECURITY Greenlock validates opts.domains ahead-of-time so you don't have to
return checkWwws(opts.domains[0]).then(function () { return checkWwws(opts.domains[0])
//opts.email = email; .then(function() {
opts.agreeTos = true; //opts.email = email;
cb(null, { options: opts, certs: certs }); opts.agreeTos = true;
}).catch(cb); cb(null, { options: opts, certs: certs });
})
.catch(cb);
} }
function checkWwws(_hostname) { function checkWwws(_hostname) {
if (!_hostname) { if (!_hostname) {
// SECURITY, don't allow access to the 'srv' root // SECURITY, don't allow access to the 'srv' root
// (greenlock-express uses middleware to check '..', etc) // (greenlock-express uses middleware to check '..', etc)
return ''; return "";
} }
var hostname = _hostname; var hostname = _hostname;
var _hostdir = path.join(srv, hostname); var _hostdir = path.join(srv, hostname);
var hostdir = _hostdir; var hostdir = _hostdir;
// TODO could test for www/no-www both in directory // TODO could test for www/no-www both in directory
return fs.readdir(hostdir).then(function () { return fs
// TODO check for some sort of htaccess.json and use email in that .readdir(hostdir)
// NOTE: you can also change other options such as `challengeType` and `challenge` .then(function() {
// opts.challengeType = 'http-01'; // TODO check for some sort of htaccess.json and use email in that
// opts.challenge = require('le-challenge-fs').create({}); // NOTE: you can also change other options such as `challengeType` and `challenge`
return hostname; // opts.challengeType = 'http-01';
}).catch(function () { // opts.challenge = require('le-challenge-fs').create({});
if ('www.' === hostname.slice(0, 4)) { return hostname;
// Assume we'll redirect to non-www if it's available. })
hostname = hostname.slice(4); .catch(function() {
hostdir = path.join(srv, hostname); if ("www." === hostname.slice(0, 4)) {
return fs.readdir(hostdir).then(function () { // Assume we'll redirect to non-www if it's available.
// TODO list both domains? hostname = hostname.slice(4);
return hostname; hostdir = path.join(srv, hostname);
}); return fs.readdir(hostdir).then(function() {
} else { // TODO list both domains?
// Or check and see if perhaps we should redirect non-www to www return hostname;
hostname = 'www.' + hostname; });
hostdir = path.join(srv, hostname); } else {
return fs.readdir(hostdir).then(function () { // Or check and see if perhaps we should redirect non-www to www
// TODO list both domains? hostname = "www." + hostname;
return hostname; hostdir = path.join(srv, hostname);
}); return fs.readdir(hostdir).then(function() {
} // TODO list both domains?
}).catch(function () { return hostname;
throw new Error("rejecting '" + _hostname + "' because '" + _hostdir + "' could not be read"); });
}); }
})
.catch(function() {
throw new Error("rejecting '" + _hostname + "' because '" + _hostdir + "' could not be read");
});
} }
function myVhostApp(req, res) { function myVhostApp(req, res) {
// SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to // SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to
// (also: only domains approved above will get here) // (also: only domains approved above will get here)
console.log('vhost:', req.headers.host); console.log("vhost:", req.headers.host);
if (!req.headers.host) { if (!req.headers.host) {
// SECURITY, don't allow access to the 'srv' root // SECURITY, don't allow access to the 'srv' root
// (greenlock-express uses middleware to check '..', etc) // (greenlock-express uses middleware to check '..', etc)
return res.end(); return res.end();
} }
// We could cache wether or not a host exists for some amount of time // We could cache wether or not a host exists for some amount of time
var fin = finalhandler(req, res); var fin = finalhandler(req, res);
return checkWwws(req.headers.host).then(function (hostname) { return checkWwws(req.headers.host)
if (hostname !== req.headers.host) { .then(function(hostname) {
res.statusCode = 302; if (hostname !== req.headers.host) {
res.setHeader('Location', 'https://' + hostname); res.statusCode = 302;
// SECURITY this is safe only because greenlock disallows invalid hostnames res.setHeader("Location", "https://" + hostname);
res.end("<!-- redirecting to https://" + hostname + "-->"); // SECURITY this is safe only because greenlock disallows invalid hostnames
return; res.end("<!-- redirecting to https://" + hostname + "-->");
} return;
var serve = serveStatic(path.join(srv, hostname), { redirect: true }); }
serve(req, res, fin); var serve = serveStatic(path.join(srv, hostname), { redirect: true });
}).catch(function () { serve(req, res, fin);
fin(); })
}); .catch(function() {
fin();
});
} }

View File

@ -1,40 +1,46 @@
'use strict'; "use strict";
//////////////////////// ////////////////////////
// Greenlock Setup // // Greenlock Setup //
//////////////////////// ////////////////////////
//var Greenlock = require('greenlock-express'); //var Greenlock = require('greenlock-express');
var Greenlock = require('../'); var Greenlock = require("../");
var greenlock = Greenlock.create({ 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 // You MUST change these to a valid email and domains
// Note: If at first you don't succeed, stop and switch to staging email: "john.doe@example.com",
// https://acme-staging-v02.api.letsencrypt.org/directory approvedDomains: ["example.com", "www.example.com"],
server: 'https://acme-v02.api.letsencrypt.org/directory' agreeTos: true,
, version: 'draft-11'
, configDir: '~/.config/acme/'
, app: require('./my-express-app.js')
// You MUST change these to a valid email and domains // Get notified of important updates and help me make greenlock better
, email: 'john.doe@example.com' communityMember: true,
, approvedDomains: [ 'example.com', 'www.example.com' ] telemetry: true
, agreeTos: 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 server = greenlock.listen(80, 443);
var WebSocket = require('ws'); var WebSocket = require("ws");
var ws = new WebSocket.Server({ server: server }); var ws = new WebSocket.Server({ server: server });
ws.on('connection', function (ws, req) { ws.on("connection", function(ws, req) {
// inspect req.headers.authorization (or cookies) for session info // inspect req.headers.authorization (or cookies) for session info
ws.send("[Secure Echo Server] Hello!\nAuth: '" + (req.headers.authorization || 'none') + "'\n" ws.send(
+ "Cookie: '" + (req.headers.cookie || 'none') + "'\n"); "[Secure Echo Server] Hello!\nAuth: '" +
ws.on('message', function (data) { ws.send(data); }); (req.headers.authorization || "none") +
"'\n" +
"Cookie: '" +
(req.headers.cookie || "none") +
"'\n"
);
ws.on("message", function(data) {
ws.send(data);
});
}); });

View File

@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
'use strict'; "use strict";
/*global Promise*/ /*global Promise*/
/////////////////////// ///////////////////////
@ -11,60 +11,67 @@
// //
//var glx = require('greenlock-express') //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' configDir: "~/acme/", // You MUST have access to write to directory where certs
//, server: 'https://acme-v02.api.letsencrypt.org/directory' // If at first you don't succeed, stop and switch to staging // are saved. ex: /home/foouser/.config/acme
// https://acme-staging-v02.api.letsencrypt.org/directory
, configDir: '~/acme/' // You MUST have access to write to directory where certs approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the
// are saved. ex: /home/foouser/.config/acme // domain name here and reject invalid ones
, approveDomains: myApproveDomains // Greenlock's wraps around tls.SNICallback. Check the app: require("./my-express-app.js"), // Any node-style http app (i.e. express, koa, hapi, rill)
// domain name here and reject invalid ones
, 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 */ //, debug: true
, email: 'jon.doe@example.com' // Email for Let's Encrypt account and Greenlock Security store: require("le-store-fs")
, agreeTos: true // Accept Let's Encrypt ToS
, communityMember: true // Join Greenlock to (very rarely) get important updates
//, debug: true
, store: require('le-store-fs')
}); });
var server = glx.listen(80, 443); var server = glx.listen(80, 443);
server.on('listening', function () { server.on("listening", function() {
console.info(server.type + " listening on", server.address()); console.info(server.type + " listening on", server.address());
}); });
function myApproveDomains(opts) { function myApproveDomains(opts) {
console.log('sni:', opts.domain); console.log("sni:", opts.domain);
// must be 'example.com' or start with 'example.com' // must be 'example.com' or start with 'example.com'
if ('example.com' !== opts.domain if (
&& 'example.com' !== opts.domain.split('.').slice(1).join('.')) { "example.com" !== opts.domain &&
return Promise.reject(new Error("we don't serve your kind here: " + 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 // the primary domain for the cert
opts.subject = 'example.com'; opts.subject = "example.com";
// the altnames (including the primary) // the altnames (including the primary)
opts.domains = [ opts.subject, '*.example.com' ]; opts.domains = [opts.subject, "*.example.com"];
if (!opts.challenges) { opts.challenges = {}; } if (!opts.challenges) {
opts.challenges['http-01'] = require('le-challenge-fs').create({}); opts.challenges = {};
// 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. opts.challenges["http-01"] = require("le-challenge-fs").create({});
// That could take several seconds to a few minutes. // Note: When implementing a dns-01 plugin you should make it check in a loop
opts.challenges['dns-01'] = require('le-challenge-dns').create({}); // 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 // explicitly set account id and certificate.id
opts.account = { id: opts.email }; opts.account = { id: opts.email };
opts.certificate = { id: opts.subject }; opts.certificate = { id: opts.subject };
return Promise.resolve(opts); return Promise.resolve(opts);
} }

522
index.js
View File

@ -1,265 +1,321 @@
'use strict'; "use strict";
var PromiseA; var PromiseA;
try { try {
PromiseA = require('bluebird'); PromiseA = require("bluebird");
} catch(e) { } catch (e) {
PromiseA = global.Promise; PromiseA = global.Promise;
} }
// opts.approveDomains(options, certs, cb) // opts.approveDomains(options, certs, cb)
module.exports.create = function (opts) { module.exports.create = function(opts) {
// accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware // accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware
if (!opts._communityPackage) { if (!opts._communityPackage) {
opts._communityPackage = 'greenlock-express.js'; opts._communityPackage = "greenlock-express.js";
opts._communityPackageVersion = require('./package.json').version; opts._communityPackageVersion = require("./package.json").version;
} }
function explainError(e) { function explainError(e) {
console.error('Error:' + e.message); console.error("Error:" + e.message);
if ('EACCES' === e.errno) { if ("EACCES" === e.errno) {
console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'."); 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)\""); console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"');
return; return;
} }
if ('EADDRINUSE' === e.errno) { if ("EADDRINUSE" === e.errno) {
console.error("'" + e.address + ":" + e.port + "' is already being used by some other program."); 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."); console.error("You probably need to stop that program or restart your computer.");
return; return;
} }
console.error(e.code + ": '" + e.address + ":" + e.port + "'"); console.error(e.code + ": '" + e.address + ":" + e.port + "'");
} }
function _createPlain(plainPort) { function _createPlain(plainPort) {
if (!plainPort) { plainPort = 80; } if (!plainPort) {
plainPort = 80;
}
var parts = String(plainPort).split(':'); var parts = String(plainPort).split(":");
var p = parts.pop(); var p = parts.pop();
var addr = parts.join(':').replace(/^\[/, '').replace(/\]$/, ''); var addr = parts
var args = []; .join(":")
var httpType; .replace(/^\[/, "")
var server; .replace(/\]$/, "");
var validHttpPort = (parseInt(p, 10) >= 0); var args = [];
var httpType;
var server;
var validHttpPort = parseInt(p, 10) >= 0;
if (addr) { args[1] = addr; } if (addr) {
if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { args[1] = addr;
console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); }
} if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
}
server = require('http').createServer( server = require("http").createServer(
greenlock.middleware.sanitizeHost(greenlock.middleware(require('redirect-https')())) greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")()))
); );
httpType = 'http'; httpType = "http";
return { server: server, listen: function () { return new PromiseA(function (resolve, reject) { return {
args[0] = p; server: server,
args.push(function () { listen: function() {
if (!greenlock.servername) { return new PromiseA(function(resolve, reject) {
if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) { args[0] = p;
greenlock.servername = greenlock.approvedDomains[0]; args.push(function() {
} if (!greenlock.servername) {
if (Array.isArray(greenlock.approveDomains) && greenlock.approvedDomains.length) { if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) {
greenlock.servername = greenlock.approvedDomains[0]; greenlock.servername = greenlock.approvedDomains[0];
} }
} if (Array.isArray(greenlock.approveDomains) && greenlock.approvedDomains.length) {
greenlock.servername = greenlock.approvedDomains[0];
}
}
if (!greenlock.servername) { if (!greenlock.servername) {
resolve(null); resolve(null);
return; return;
} }
return greenlock.check({ domains: [ greenlock.servername ] }).then(function (certs) { return greenlock
if (certs) { .check({ domains: [greenlock.servername] })
return { .then(function(certs) {
key: Buffer.from(certs.privkey, 'ascii') if (certs) {
, cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii') 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 console.info(
greenlock.tlsOptions.SNICallback(greenlock.servername, function (err/*, secureContext*/) { "Fetching certificate for '%s' to use as default for HTTPS server...",
if (err) { reject(err); return; } greenlock.servername
return greenlock.check({ domains: [ greenlock.servername ] }).then(function (certs) { );
resolve({ return new PromiseA(function(resolve, reject) {
key: Buffer.from(certs.privkey, 'ascii') // using SNICallback because all options will be set
, cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii') greenlock.tlsOptions.SNICallback(greenlock.servername, function(err /*, secureContext*/) {
}); if (err) {
}).catch(reject); reject(err);
}); return;
}); }
}).then(resolve).catch(reject); return greenlock
}); .check({ domains: [greenlock.servername] })
server.listen.apply(server, args).on('error', function (e) { .then(function(certs) {
if (server.listenerCount('error') < 2) { resolve({
console.warn("Did not successfully create http server and bind to port '" + p + "':"); key: Buffer.from(certs.privkey, "ascii"),
explainError(e); cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii")
process.exit(41); });
} })
}); .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) { function _create(port) {
if (!port) { port = 443; } if (!port) {
port = 443;
}
var parts = String(port).split(':'); var parts = String(port).split(":");
var p = parts.pop(); var p = parts.pop();
var addr = parts.join(':').replace(/^\[/, '').replace(/\]$/, ''); var addr = parts
var args = []; .join(":")
var httpType; .replace(/^\[/, "")
var server; .replace(/\]$/, "");
var validHttpPort = (parseInt(p, 10) >= 0); var args = [];
var httpType;
var server;
var validHttpPort = parseInt(p, 10) >= 0;
if (addr) { args[1] = addr; } if (addr) {
if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { args[1] = addr;
console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); }
} if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
}
var https; var https;
try { try {
https = require('spdy'); https = require("spdy");
greenlock.tlsOptions.spdy = { protocols: [ 'h2', 'http/1.1' ], plain: false }; greenlock.tlsOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false };
httpType = 'http2 (spdy/h2)'; httpType = "http2 (spdy/h2)";
} catch(e) { } catch (e) {
https = require('https'); https = require("https");
httpType = 'https'; httpType = "https";
} }
var sniCallback = greenlock.tlsOptions.SNICallback; var sniCallback = greenlock.tlsOptions.SNICallback;
greenlock.tlsOptions.SNICallback = function (domain, cb) { greenlock.tlsOptions.SNICallback = function(domain, cb) {
sniCallback(domain, function (err, context) { sniCallback(domain, function(err, context) {
cb(err, context); cb(err, context);
if (!context || server._hasDefaultSecureContext) { return; } if (!context || server._hasDefaultSecureContext) {
if (!domain) { domain = greenlock.servername; } return;
if (!domain) { return; } }
if (!domain) {
domain = greenlock.servername;
}
if (!domain) {
return;
}
return greenlock.check({ domains: [ domain ] }).then(function (certs) { return greenlock
// ignore the case that check doesn't have all the right args here .check({ domains: [domain] })
// to get the same certs that it just got (eventually the right ones will come in) .then(function(certs) {
if (!certs) { return; } // ignore the case that check doesn't have all the right args here
if (server.setSecureContext) { // to get the same certs that it just got (eventually the right ones will come in)
// only available in node v11.0+ if (!certs) {
server.setSecureContext({ return;
key: Buffer.from(certs.privkey, 'ascii') }
, cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii') if (server.setSecureContext) {
}); // only available in node v11.0+
console.info("Using '%s' as default certificate", domain); server.setSecureContext({
} else { key: Buffer.from(certs.privkey, "ascii"),
console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii")
} });
server._hasDefaultSecureContext = true; console.info("Using '%s' as default certificate", domain);
}).catch(function (/*e*/) { } else {
// this may be that the test.example.com was requested, but it's listed console.info("Setting default certificates dynamically requires node v11.0+. Skipping.");
// 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:"); server._hasDefaultSecureContext = true;
//console.warn(e.message); })
}); .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
if (greenlock.tlsOptions.cert) { //console.warn("Unusual error: couldn't get newly authorized certificate:");
server._hasDefaultSecureContext = true; //console.warn(e.message);
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)"); });
} };
} if (greenlock.tlsOptions.cert) {
server = https.createServer( server._hasDefaultSecureContext = true;
greenlock.tlsOptions if (greenlock.tlsOptions.cert.toString("ascii").split("BEGIN").length < 3) {
, greenlock.middleware.sanitizeHost(function (req, res) { console.warn(
try { "Invalid certificate file. 'tlsOptions.cert' should contain cert.pem (certificate file) *and* chain.pem (intermediate certificates) seperated by an extra newline (CRLF)"
greenlock.app(req, res); );
} catch(e) { }
console.error("[error] [greenlock.app] Your HTTP handler had an uncaught error:"); }
console.error(e); server = https.createServer(
try { greenlock.tlsOptions,
res.statusCode = 500; greenlock.middleware.sanitizeHost(function(req, res) {
res.end("Internal Server Error: [Greenlock] HTTP exception logged for user-provided handler."); try {
} catch(e) { greenlock.app(req, res);
// ignore } catch (e) {
// (headers may have already been sent, etc) 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.");
server.type = httpType; } catch (e) {
// ignore
// (headers may have already been sent, etc)
}
}
})
);
server.type = httpType;
return { server: server, listen: function () { return new PromiseA(function (resolve) { return {
args[0] = p; server: server,
args.push(function () { resolve(/*server*/); }); listen: function() {
server.listen.apply(server, args).on('error', function (e) { return new PromiseA(function(resolve) {
if (server.listenerCount('error') < 2) { args[0] = p;
console.warn("Did not successfully create http server and bind to port '" + p + "':"); args.push(function() {
explainError(e); resolve(/*server*/);
process.exit(41); });
} 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 // NOTE: 'greenlock' is just 'opts' renamed
var greenlock = require('greenlock').create(opts); var greenlock = require("greenlock").create(opts);
if (!opts.app) { if (!opts.app) {
opts.app = function (req, res) { opts.app = function(req, res) {
res.end("Hello, World!\nWith Love,\nGreenlock for Express.js"); res.end("Hello, World!\nWith Love,\nGreenlock for Express.js");
}; };
} }
opts.listen = function (plainPort, port, fnPlain, fn) { opts.listen = function(plainPort, port, fnPlain, fn) {
var server; var server;
var plainServer; var plainServer;
// If there is only one handler for the `listening` (i.e. TCP bound) event // If there is only one handler for the `listening` (i.e. TCP bound) event
// then we want to use it as HTTPS (backwards compat) // then we want to use it as HTTPS (backwards compat)
if (!fn) { if (!fn) {
fn = fnPlain; fn = fnPlain;
fnPlain = null; fnPlain = null;
} }
var obj1 = _createPlain(plainPort, true); var obj1 = _createPlain(plainPort, true);
var obj2 = _create(port, false); var obj2 = _create(port, false);
plainServer = obj1.server; plainServer = obj1.server;
server = obj2.server; server = obj2.server;
server.then = obj1.listen().then(function (tlsOptions) { server.then = obj1.listen().then(function(tlsOptions) {
if (tlsOptions) { if (tlsOptions) {
if (server.setSecureContext) { if (server.setSecureContext) {
// only available in node v11.0+ // only available in node v11.0+
server.setSecureContext(tlsOptions); server.setSecureContext(tlsOptions);
console.info("Using '%s' as default certificate", greenlock.servername); console.info("Using '%s' as default certificate", greenlock.servername);
} else { } else {
console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); console.info("Setting default certificates dynamically requires node v11.0+. Skipping.");
} }
server._hasDefaultSecureContext = true; server._hasDefaultSecureContext = true;
} }
return obj2.listen().then(function () { return obj2.listen().then(function() {
// Report plain http status // Report plain http status
if ('function' === typeof fnPlain) { if ("function" === typeof fnPlain) {
fnPlain.apply(plainServer); fnPlain.apply(plainServer);
} else if (!fn && !plainServer.listenerCount('listening') && !server.listenerCount('listening')) { } else if (!fn && !plainServer.listenerCount("listening") && !server.listenerCount("listening")) {
console.info('[:' + (plainServer.address().port || plainServer.address()) console.info(
+ "] Handling ACME challenges and redirecting to " + server.type); "[:" +
} (plainServer.address().port || plainServer.address()) +
"] Handling ACME challenges and redirecting to " +
server.type
);
}
// Report h2/https status // Report h2/https status
if ('function' === typeof fn) { if ("function" === typeof fn) {
fn.apply(server); fn.apply(server);
} else if (!server.listenerCount('listening')) { } else if (!server.listenerCount("listening")) {
console.info('[:' + (server.address().port || server.address()) + "] Serving " + server.type); console.info("[:" + (server.address().port || server.address()) + "] Serving " + server.type);
} }
}); });
}).then; }).then;
server.unencrypted = plainServer; server.unencrypted = plainServer;
return server; return server;
}; };
opts.middleware.acme = function (opts) { opts.middleware.acme = function(opts) {
return greenlock.middleware.sanitizeHost(greenlock.middleware(require('redirect-https')(opts))); return greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")(opts)));
}; };
opts.middleware.secure = function (app) { opts.middleware.secure = function(app) {
return greenlock.middleware.sanitizeHost(app); return greenlock.middleware.sanitizeHost(app);
}; };
return greenlock; return greenlock;
}; };

View File

@ -1,37 +1,37 @@
'use strict'; "use strict";
function requireBluebird() { function requireBluebird() {
try { try {
return require('bluebird'); return require("bluebird");
} catch(e) { } catch (e) {
console.error(""); console.error("");
console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support."); 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("EASY FIX: `npm install --save bluebird`");
console.error(""); console.error("");
throw e; throw e;
} }
} }
if ('undefined' === typeof Promise) { if ("undefined" === typeof Promise) {
global.Promise = requireBluebird(); global.Promise = requireBluebird();
} }
if ('function' !== typeof require('util').promisify) { if ("function" !== typeof require("util").promisify) {
require('util').promisify = requireBluebird().promisify; require("util").promisify = requireBluebird().promisify;
} }
if (!console.debug) { if (!console.debug) {
console.debug = console.log; console.debug = console.log;
} }
var fs = require('fs'); var fs = require("fs");
var fsAsync = {}; var fsAsync = {};
Object.keys(fs).forEach(function (key) { Object.keys(fs).forEach(function(key) {
var fn = fs[key]; var fn = fs[key];
if ('function' !== typeof fn || !/[a-z]/.test(key[0])) { if ("function" !== typeof fn || !/[a-z]/.test(key[0])) {
return; return;
} }
fsAsync[key] = require('util').promisify(fn); fsAsync[key] = require("util").promisify(fn);
}); });
exports.fsAsync = fsAsync; exports.fsAsync = fsAsync;

1182
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,50 +1,50 @@
{ {
"name": "greenlock-express", "name": "greenlock-express",
"version": "2.7.9", "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.", "description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.",
"main": "index.js", "main": "index.js",
"homepage": "https://greenlock.domains", "homepage": "https://greenlock.domains",
"directories": { "directories": {
"example": "examples" "example": "examples"
}, },
"dependencies": { "dependencies": {
"greenlock": "^2.7.24", "greenlock": "^2.7.24",
"redirect-https": "^1.1.5" "redirect-https": "^1.1.5"
}, },
"files": [ "files": [
"lib" "lib"
], ],
"trulyOptionalDependencies": { "trulyOptionalDependencies": {
"spdy": "^3.4.7" "spdy": "^3.4.7"
}, },
"devDependencies": { "devDependencies": {
"express": "^4.16.3", "express": "^4.16.3",
"express-basic-auth": "^1.1.5", "express-basic-auth": "^1.1.5",
"finalhandler": "^1.1.1", "finalhandler": "^1.1.1",
"serve-index": "^1.9.1", "serve-index": "^1.9.1",
"serve-static": "^1.13.2", "serve-static": "^1.13.2",
"ws": "^5.2.1" "ws": "^5.2.1"
}, },
"scripts": { "scripts": {
"start": "node server.js ./config.js", "start": "node server.js ./config.js",
"test": "node test/greenlock.js" "test": "node test/greenlock.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.rootprojects.org/root/greenlock-express.js.git" "url": "https://git.rootprojects.org/root/greenlock-express.js.git"
}, },
"keywords": [ "keywords": [
"Let's Encrypt", "Let's Encrypt",
"ACME", "ACME",
"greenlock", "greenlock",
"Free SSL", "Free SSL",
"Automated HTTPS", "Automated HTTPS",
"https", "https",
"tls" "tls"
], ],
"author": "AJ ONeal <solderjs@gmail.com> (https://solderjs.com/)", "author": "AJ ONeal <solderjs@gmail.com> (https://solderjs.com/)",
"license": "MPL-2.0", "license": "MPL-2.0",
"bugs": { "bugs": {
"url": "https://git.rootprojects.org/root/greenlock-express.js/issues" "url": "https://git.rootprojects.org/root/greenlock-express.js/issues"
} }
} }

336
server.js
View File

@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
'use strict'; "use strict";
/*global Promise*/ /*global Promise*/
///////////////////////////////// /////////////////////////////////
@ -15,189 +15,219 @@
// ex: /srv/api/api.example.com // ex: /srv/api/api.example.com
// //
var configpath = process.argv[2] || './config.js'; var configpath = process.argv[2] || "./config.js";
var config = require(configpath); var config = require(configpath);
// The prefix where sites go by name. // The prefix where sites go by name.
// For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path // For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path
var path = require('path'); var path = require("path");
var fs = require('./lib/compat.js').fsAsync; var fs = require("./lib/compat.js").fsAsync;
var finalhandler = require('finalhandler'); var finalhandler = require("finalhandler");
var serveStatic = require('serve-static'); var serveStatic = require("serve-static");
//var glx = require('greenlock-express') //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' configDir: config.configDir, // You MUST have access to write to directory where certs
, server: 'https://acme-v02.api.letsencrypt.org/directory' // If at first you don't succeed, stop and switch to staging // are saved. ex: /home/foouser/.config/acme
// https://acme-staging-v02.api.letsencrypt.org/directory
, configDir: config.configDir // You MUST have access to write to directory where certs approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the
// are saved. ex: /home/foouser/.config/acme // domain name here and reject invalid ones
, approveDomains: myApproveDomains // Greenlock's wraps around tls.SNICallback. Check the app: myVhostApp, // Any node-style http app (i.e. express, koa, hapi, rill)
// domain name here and reject invalid ones
, 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
/* CHANGE TO A VALID EMAIL */ agreeTos: true, // Accept Let's Encrypt ToS
, email: config.email // Email for Let's Encrypt account and Greenlock Security //, communityMember: true // Join Greenlock to get important updates, no spam
, agreeTos: true // Accept Let's Encrypt ToS
//, communityMember: true // Join Greenlock to get important updates, no spam
//, debug: true
, store: require('greenlock-store-fs')
//, debug: true
store: require("greenlock-store-fs")
}); });
var server = glx.listen(80, 443); var server = glx.listen(80, 443);
server.on('listening', function () { server.on("listening", function() {
console.info(server.type + " listening on", server.address()); console.info(server.type + " listening on", server.address());
}); });
function myApproveDomains(opts) { function myApproveDomains(opts) {
console.info("SNI:", opts.domain); console.info("SNI:", opts.domain);
// In this example the filesystem is our "database". // In this example the filesystem is our "database".
// We check in /srv/www for whatever.com and if it exists, it's allowed // 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 // SECURITY Greenlock validates opts.domains ahead-of-time so you don't have to
var domains = []; var domains = [];
var domain = opts.domain.replace(/^(www|api)\./, ''); var domain = opts.domain.replace(/^(www|api)\./, "");
return checkWwws(domain).then(function (hostname) { return checkWwws(domain)
// this is either example.com or www.example.com .then(function(hostname) {
domains.push(hostname); // this is either example.com or www.example.com
if ('api.' + domain !== opts.domain) { domains.push(hostname);
if (!domains.includes(opts.domain)) { if ("api." + domain !== opts.domain) {
domains.push(opts.domain) if (!domains.includes(opts.domain)) {
} domains.push(opts.domain);
} }
}).catch(function () { }
// ignore error })
return null; .catch(function() {
}).then(function () { // ignore error
// check for api prefix return null;
var apiname = domain; })
if (domains.length) { .then(function() {
apiname = 'api.' + domain; // check for api prefix
} var apiname = domain;
return checkApi(apiname).then(function (app) { if (domains.length) {
if (!app) { return null; } apiname = "api." + domain;
domains.push(apiname); }
}).catch(function () { return checkApi(apiname)
return null; .then(function(app) {
}); if (!app) {
}).then(function () { return null;
if (0 === domains.length) { }
return Promise.reject(new Error("no bare, www., or api. domain matching '" + opts.domain + "'")); 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); console.info("Approved domains:", domains);
opts.domains = domains; opts.domains = domains;
//opts.email = email; //opts.email = email;
opts.agreeTos = true; opts.agreeTos = true;
// pick the shortest (bare) or latest (www. instead of api.) to be the subject // pick the shortest (bare) or latest (www. instead of api.) to be the subject
opts.subject = opts.domains.sort(function (a, b) { opts.subject = opts.domains.sort(function(a, b) {
var len = a.length - b.length; var len = a.length - b.length;
if (0 !== len) { return len; } if (0 !== len) {
if (a < b) { return 1; } else { return -1; } return len;
})[0]; }
if (a < b) {
return 1;
} else {
return -1;
}
})[0];
if (!opts.challenges) { opts.challenges = {}; } if (!opts.challenges) {
opts.challenges['http-01'] = require('le-challenge-fs'); opts.challenges = {};
//opts.challenges['dns-01'] = require('le-challenge-dns'); }
opts.challenges["http-01"] = require("le-challenge-fs");
//opts.challenges['dns-01'] = require('le-challenge-dns');
// explicitly set account id and certificate.id // explicitly set account id and certificate.id
opts.account = { id: opts.email }; opts.account = { id: opts.email };
opts.certificate = { id: opts.subject }; opts.certificate = { id: opts.subject };
return Promise.resolve(opts); return Promise.resolve(opts);
}); });
} }
function checkApi(hostname) { function checkApi(hostname) {
var apipath = path.join(config.api, hostname); var apipath = path.join(config.api, hostname);
var link = ''; var link = "";
return fs.stat(apipath).then(function (stats) { return fs
if (stats.isDirectory()) { .stat(apipath)
return require(apipath); .then(function(stats) {
} if (stats.isDirectory()) {
return fs.readFile(apipath, 'utf8').then(function (txt) { return require(apipath);
var linkpath = txt.split('\n')[0]; }
link = (' => ' + linkpath + ' '); return fs.readFile(apipath, "utf8").then(function(txt) {
return require(linkpath); var linkpath = txt.split("\n")[0];
}); link = " => " + linkpath + " ";
}).catch(function (e) { return require(linkpath);
if ('ENOENT' === e.code) { return null; } });
console.error(e); })
throw new Error("rejecting '" + hostname + "' because '" + apipath + link + "' failed at require()"); .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) { function checkWwws(_hostname) {
if (!_hostname) { if (!_hostname) {
// SECURITY don't serve the whole config.srv // SECURITY don't serve the whole config.srv
return Promise.reject(new Error("missing hostname")); return Promise.reject(new Error("missing hostname"));
} }
var hostname = _hostname; var hostname = _hostname;
var hostdir = path.join(config.srv, hostname); var hostdir = path.join(config.srv, hostname);
// TODO could test for www/no-www both in directory // TODO could test for www/no-www both in directory
return fs.readdir(hostdir).then(function () { return fs
// TODO check for some sort of htaccess.json and use email in that .readdir(hostdir)
// NOTE: you can also change other options such as `challengeType` and `challenge` .then(function() {
// opts.challengeType = 'http-01'; // TODO check for some sort of htaccess.json and use email in that
// opts.challenge = require('le-challenge-fs').create({}); // NOTE: you can also change other options such as `challengeType` and `challenge`
return hostname; // opts.challengeType = 'http-01';
}).catch(function () { // opts.challenge = require('le-challenge-fs').create({});
if ('www.' === hostname.slice(0, 4)) { return hostname;
// Assume we'll redirect to non-www if it's available. })
hostname = hostname.slice(4); .catch(function() {
hostdir = path.join(config.srv, hostname); if ("www." === hostname.slice(0, 4)) {
return fs.readdir(hostdir).then(function () { // Assume we'll redirect to non-www if it's available.
return hostname; hostname = hostname.slice(4);
}); hostdir = path.join(config.srv, hostname);
} else { return fs.readdir(hostdir).then(function() {
// Or check and see if perhaps we should redirect non-www to www return hostname;
hostname = 'www.' + hostname; });
hostdir = path.join(config.srv, hostname); } else {
return fs.readdir(hostdir).then(function () { // Or check and see if perhaps we should redirect non-www to www
return hostname; hostname = "www." + hostname;
}); hostdir = path.join(config.srv, hostname);
} return fs.readdir(hostdir).then(function() {
}).catch(function () { return hostname;
throw new Error("rejecting '" + _hostname + "' because '" + hostdir + "' could not be read"); });
}); }
})
.catch(function() {
throw new Error("rejecting '" + _hostname + "' because '" + hostdir + "' could not be read");
});
} }
function myVhostApp(req, res) { function myVhostApp(req, res) {
// SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to // SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to
// (also: only domains approved above will get here) // (also: only domains approved above will get here)
console.info(req.method, (req.headers.host||'') + req.url); console.info(req.method, (req.headers.host || "") + req.url);
Object.keys(req.headers).forEach(function (key) { Object.keys(req.headers).forEach(function(key) {
console.info(key, req.headers[key]) console.info(key, req.headers[key]);
}); });
// We could cache wether or not a host exists for some amount of time // We could cache wether or not a host exists for some amount of time
var fin = finalhandler(req, res); var fin = finalhandler(req, res);
return checkWwws(req.headers.host).then(function (hostname) { return checkWwws(req.headers.host)
if (hostname !== req.headers.host) { .then(function(hostname) {
res.statusCode = 302; if (hostname !== req.headers.host) {
res.setHeader('Location', 'https://' + hostname); res.statusCode = 302;
// SECURITY this is safe only because greenlock disallows invalid hostnames res.setHeader("Location", "https://" + hostname);
res.end("<!-- redirecting to https://" + hostname + "-->"); // SECURITY this is safe only because greenlock disallows invalid hostnames
return; res.end("<!-- redirecting to https://" + hostname + "-->");
} return;
var serve = serveStatic(path.join(config.srv, hostname), { redirect: true }); }
serve(req, res, fin); var serve = serveStatic(path.join(config.srv, hostname), { redirect: true });
}).catch(function (err) { serve(req, res, fin);
return checkApi(req.headers.host).then(function (app) { })
if (app) { app(req, res); return; } .catch(function(err) {
console.error("none found", err); return checkApi(req.headers.host)
fin(); .then(function(app) {
}).catch(function (err) { if (app) {
console.error("api crashed error", err); app(req, res);
fin(err); return;
}); }
}); console.error("none found", err);
fin();
})
.catch(function(err) {
console.error("api crashed error", err);
fin(err);
});
});
} }

View File

@ -1,75 +1,85 @@
#!/usr/bin/env node #!/usr/bin/env node
var Greenlock = require('../'); var Greenlock = require("../");
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
version: 'draft-11' version: "draft-11",
, server: 'https://acme-staging-v02.api.letsencrypt.org/directory' server: "https://acme-staging-v02.api.letsencrypt.org/directory",
, agreeTos: true agreeTos: true,
, approvedDomains: [ 'example.com', 'www.example.com' ] approvedDomains: ["example.com", "www.example.com"],
, configDir: require('path').join(require('os').tmpdir(), 'acme') configDir: require("path").join(require("os").tmpdir(), "acme"),
, app: require('express')().use('/', function (req, res) { app: require("express")().use("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('Hello, World!\n\n💚 🔒.js'); res.end("Hello, World!\n\n💚 🔒.js");
}) })
}); });
var server1 = greenlock.listen(5080, 5443); var server1 = greenlock.listen(5080, 5443);
server1.on('listening', function () { server1.on("listening", function() {
console.log("### THREE 3333 - All is well server1", this.address()); console.log("### THREE 3333 - All is well server1", this.address());
setTimeout(function () { setTimeout(function() {
// so that the address() object doesn't disappear // so that the address() object doesn't disappear
server1.close(); server1.close();
server1.unencrypted.close(); server1.unencrypted.close();
}, 10); }, 10);
}); });
setTimeout(function () { setTimeout(function() {
var server2 = greenlock.listen(6080, 6443, function () { var server2 = greenlock.listen(6080, 6443, function() {
console.log("### FIVE 55555 - Started server 2!"); console.log("### FIVE 55555 - Started server 2!");
setTimeout(function () { setTimeout(function() {
server2.close(); server2.close();
server2.unencrypted.close(); server2.unencrypted.close();
server6.close(); server6.close();
server6.unencrypted.close(); server6.unencrypted.close();
server7.close(); server7.close();
server7.unencrypted.close(); server7.unencrypted.close();
setTimeout(function () { setTimeout(function() {
// TODO greenlock needs a close event (and to listen to its server's close event) // TODO greenlock needs a close event (and to listen to its server's close event)
process.exit(0); process.exit(0);
}, 1000); }, 1000);
}, 1000); }, 1000);
}); });
server2.on('listening', function () { server2.on("listening", function() {
console.log("### FOUR 44444 - All is well server2", server2.address()); console.log("### FOUR 44444 - All is well server2", server2.address());
}); });
}, 1000); }, 1000);
var server3 = greenlock.listen(22, 22, function () { var server3 = greenlock.listen(
console.error("Error: expected to get an error when launching plain server on port 22"); 22,
}, function () { 22,
console.error("Error: expected to get an error when launching " + server3.type + " server on port 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 () { server3.on("error", function() {
console.log("Success: caught expected (plain) error"); 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 () { var server4 = greenlock.listen(
console.log('Success: server4: plain'); 7080,
server4.unencrypted.close(); 7443,
}, function () { function() {
console.log('Success: server4: ' + server4.type); console.log("Success: server4: plain");
server4.close(); 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 () { var server6 = greenlock.listen("[::]:11080", "[::1]:11443");
console.log("Server 5 with one fn", this.address());
server5.close();
server5.unencrypted.close();
});
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');