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
- [x] Automatic HTTPS
- [x] Free SSL
- [x] Free Wildcard SSL
- [x] Multiple domain support (up to 100 altnames per SAN)
- [x] Dynamic Virtual Hosting (vhost)
- [x] Automatical renewal (10 to 14 days before expiration)
- [x] Great ACME support
- [x] ACME draft 11
- [x] Let's Encrypt v2
- [x] Let's Encrypt v1
- [x] Full node.js support
- [x] node v6+
- [x] core https module
- [x] Express.js
- [x] [Koa](https://git.rootprojects.org/root/greenlock-koa.js)
- [x] [hapi](https://git.rootprojects.org/root/greenlock-hapi.js)
- [x] Extensible Plugin Support
- [x] AWS (S3, Route53)
- [x] Azure
- [x] CloudFlare
- [x] Consul
- [x] Digital Ocean
- [x] etcd
- [x] Redis
- [x] Automatic HTTPS
- [x] Free SSL
- [x] Free Wildcard SSL
- [x] Multiple domain support (up to 100 altnames per SAN)
- [x] Dynamic Virtual Hosting (vhost)
- [x] Automatical renewal (10 to 14 days before expiration)
- [x] Great ACME support
- [x] ACME draft 11
- [x] Let's Encrypt v2
- [x] Let's Encrypt v1
- [x] Full node.js support
- [x] node v6+
- [x] core https module
- [x] Express.js
- [x] [Koa](https://git.rootprojects.org/root/greenlock-koa.js)
- [x] [hapi](https://git.rootprojects.org/root/greenlock-hapi.js)
- [x] Extensible Plugin Support
- [x] AWS (S3, Route53)
- [x] Azure
- [x] CloudFlare
- [x] Consul
- [x] Digital Ocean
- [x] etcd
- [x] Redis
# Install
@ -78,18 +78,18 @@ Watch the QuickStart demonstration: [https://youtu.be/e8vaR4CEZ5s](https://youtu
<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
* [2:22](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=142) - Demonstrating QuickStart Example
* [6:37](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk?t=397) - Troubleshooting / Gotchas
- [0:00](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=0) - Intro
- [2:22](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=142) - Demonstrating QuickStart Example
- [6:37](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk?t=397) - Troubleshooting / Gotchas
#### Beyond the QuickStart (Part 2)
* [1:00](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=60) - Bringing Greenlock into an Existing Express Project
* [2:26](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=146) - The `approveDomains` callback
- [1:00](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=60) - Bringing Greenlock into an Existing Express Project
- [2:26](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=146) - The `approveDomains` callback
#### Security Concerns (Part 3)
* [0:00](https://www.youtube.com/watch?v=aZgVqPzoZTY&index=3&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk) - Potential Attacks, and Mitigation
- [0:00](https://www.youtube.com/watch?v=aZgVqPzoZTY&index=3&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk) - Potential Attacks, and Mitigation
### Working Example Code
@ -110,35 +110,39 @@ node greenlock-express.js/examples/simple.js
All you have to do is start the webserver and then visit it at its domain name.
`server.js`:
```javascript
'use strict';
"use strict";
require('greenlock-express').create({
email: 'john.doe@example.com' // The email address of the ACME user / hosting provider
, agreeTos: true // You must accept the ToS as the host which handles the certs
, configDir: '~/.config/acme/' // Writable directory where certs will be saved
, communityMember: true // Join the community to get notified of important updates
, telemetry: true // Contribute telemetry data to the project
require("greenlock-express")
.create({
email: "john.doe@example.com", // The email address of the ACME user / hosting provider
agreeTos: true, // You must accept the ToS as the host which handles the certs
configDir: "~/.config/acme/", // Writable directory where certs will be saved
communityMember: true, // Join the community to get notified of important updates
telemetry: true, // Contribute telemetry data to the project
// Using your express app:
// simply export it as-is, then include it here
, app: require('./app.js')
// Using your express app:
// simply export it as-is, then include it here
app: require("./app.js")
//, debug: true
}).listen(80, 443);
//, debug: true
})
.listen(80, 443);
```
`app.js`:
```js
'use strict';
var express = require('express');
```js
"use strict";
var express = require("express");
var app = express();
app.use('/', function (req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('Hello, World!\n\n💚 🔒.js');
})
app.use("/", function(req, res) {
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end("Hello, World!\n\n💚 🔒.js");
});
// Don't do this:
// app.listen(3000)
@ -166,29 +170,29 @@ You can see our full privacy policy at <https://greenlock.domains/legal/#privacy
Double check the following:
* **Public Facing IP** for `http-01` challenges
* 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)?
* If you're on a non-public server, are you using the `dns-01` challenge?
* **correct ACME version**
* Let's Encrypt **v2** (ACME v2) must use `version: 'draft-11'`
* Let's Encrypt v1 must use `version: 'v01'`
* **valid email**
* You MUST set `email` to a **valid address**
* MX records must validate (`dig MX example.com` for `'john@example.com'`)
* **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' ]`)
* **write access**
* You MUST set `configDir` to a writeable location (test with `touch ~/acme/etc/tmp.tmp`)
* **port binding privileges**
* 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)
* **API limits**
* You MUST NOT exceed the API [**usage limits**](https://letsencrypt.org/docs/staging-environment/) per domain, certificate, IP address, etc
* **Red Lock, Untrusted**
* You MUST use the **production** server url, not staging
* The API URL should not have 'acme-staging-v02', but should have 'acme-v02'
* Delete the `configDir` used for getting certificates in staging
- **Public Facing IP** for `http-01` challenges
- 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)?
- If you're on a non-public server, are you using the `dns-01` challenge?
- **correct ACME version**
- Let's Encrypt **v2** (ACME v2) must use `version: 'draft-11'`
- Let's Encrypt v1 must use `version: 'v01'`
- **valid email**
- You MUST set `email` to a **valid address**
- MX records must validate (`dig MX example.com` for `'john@example.com'`)
- **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' ]`)
- **write access**
- You MUST set `configDir` to a writeable location (test with `touch ~/acme/etc/tmp.tmp`)
- **port binding privileges**
- 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)
- **API limits**
- You MUST NOT exceed the API [**usage limits**](https://letsencrypt.org/docs/staging-environment/) per domain, certificate, IP address, etc
- **Red Lock, Untrusted**
- You MUST use the **production** server url, not staging
- The API URL should not have 'acme-staging-v02', but should have 'acme-v02'
- Delete the `configDir` used for getting certificates in staging
### Production vs Staging
@ -210,19 +214,19 @@ https://acme-staging-v02.api.letsencrypt.org/directory
## Working Examples
| Example | Location + Description |
|:---------------:|:---------:|
| **QuickStart** | [examples/quickstart.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/quickstart.js) uses the fewest options and accepts all default settings. It's guaranteed to work for you. |
| Production | [examples/production.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/production.js) shows how to require an express app (or other middleware system), expand the `approveDomains` callback, provides an example database shim, and exposes the server instance. |
| Virtual&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. |
| 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;(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. |
| 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. :) |
| Full&nbsp;List | Check out the [examples/](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples) directory |
| Example | Location + Description |
| :-------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| **QuickStart** | [examples/quickstart.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/quickstart.js) uses the fewest options and accepts all default settings. It's guaranteed to work for you. |
| Production | [examples/production.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/production.js) shows how to require an express app (or other middleware system), expand the `approveDomains` callback, provides an example database shim, and exposes the server instance. |
| Virtual&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. |
| 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;(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. |
| 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. :) |
| Full&nbsp;List | Check out the [examples/](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples) directory |
# Plugins
@ -230,51 +234,49 @@ https://acme-staging-v02.api.letsencrypt.org/directory
## HTTP-01 Challenges
| | Plugin |
|:--------------:|:---------:|
| **Default (fs)** | [acme-http-01-fs](https://git.rootprojects.org/root/acme-http-01-webroot.js) |
| **Manual (cli)** | [acme-http-01-cli](https://git.rootprojects.org/root/acme-http-01-cli.js) |
| AWS S3 | [acme-http-01-s3](https://git.rootprojects.org/root/acme-http-01-s3.js) |
| Azure | [kolarcz/node-le-challenge-azure-storage](https://github.com/kolarcz/node-le-challenge-azure-storage) |
| - | Build Your Own <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) |
| | Plugin |
| :--------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| **Default (fs)** | [acme-http-01-fs](https://git.rootprojects.org/root/acme-http-01-webroot.js) |
| **Manual (cli)** | [acme-http-01-cli](https://git.rootprojects.org/root/acme-http-01-cli.js) |
| AWS S3 | [acme-http-01-s3](https://git.rootprojects.org/root/acme-http-01-s3.js) |
| Azure | [kolarcz/node-le-challenge-azure-storage](https://github.com/kolarcz/node-le-challenge-azure-storage) |
| - | Build Your Own <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) |
## DNS-01 Challenges
| | Plugin |
|:--------------:|:---------:|
| **Manual (cli)** | [acme-dns-01-cli](https://git.rootprojects.org/root/acme-dns-01-cli.js) |
| AWS Route 53 | [thadeetrompetter/le-challenge-route53](https://github.com/thadeetrompetter/le-challenge-route53) |
| CloudFlare | [buschtoens/le-challenge-cloudflare](https://github.com/buschtoens/le-challenge-cloudflare) |
| CloudFlare | [llun/le-challenge-cloudflare](https://github.com/llun/le-challenge-cloudflare) |
| Digital Ocean | [bmv437/le-challenge-digitalocean](https://github.com/bmv437/le-challenge-digitalocean) |
| etcd | [ceecko/le-challenge-etcd](https://github.com/ceecko/le-challenge-etcd) |
| - | Build Your Own <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 |
| | Plugin |
| :--------------: | :----------------------------------------------------------------------------------------------------------------------------------------: |
| **Manual (cli)** | [acme-dns-01-cli](https://git.rootprojects.org/root/acme-dns-01-cli.js) |
| AWS Route 53 | [thadeetrompetter/le-challenge-route53](https://github.com/thadeetrompetter/le-challenge-route53) |
| CloudFlare | [buschtoens/le-challenge-cloudflare](https://github.com/buschtoens/le-challenge-cloudflare) |
| CloudFlare | [llun/le-challenge-cloudflare](https://github.com/llun/le-challenge-cloudflare) |
| Digital Ocean | [bmv437/le-challenge-digitalocean](https://github.com/bmv437/le-challenge-digitalocean) |
| etcd | [ceecko/le-challenge-etcd](https://github.com/ceecko/le-challenge-etcd) |
| - | Build Your Own <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 |
## Account & Certificate Storage
| | Plugin |
|:--------------:|:---------:|
| **Simplest** | [greenlock-store-fs](https://git.rootprojects.org/root/greenlock-store-fs.js) |
| certbot (v2 default) | [le-store-certbot](https://git.coolaj86.com/coolaj86/le-store-certbot.js) |
| AWS S3 | [gl-store-s3](https://git.rootprojects.org/root/gl-store-s3.js) |
| Consul | [sebastian-software/le-store-consul](https://github.com/sebastian-software/le-store-consul) |
| json (fs) | [paulgrove/le-store-simple-fs](https://github.com/paulgrove/le-store-simple-fs)
| Redis | [digitalbazaar/le-store-redis](https://github.com/digitalbazaar/le-store-redis) |
| - | Build Your Own <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 |
| | Plugin |
| :------------------: | :---------------------------------------------------------------------------------------------------: |
| **Simplest** | [greenlock-store-fs](https://git.rootprojects.org/root/greenlock-store-fs.js) |
| certbot (v2 default) | [le-store-certbot](https://git.coolaj86.com/coolaj86/le-store-certbot.js) |
| AWS S3 | [gl-store-s3](https://git.rootprojects.org/root/gl-store-s3.js) |
| Consul | [sebastian-software/le-store-consul](https://github.com/sebastian-software/le-store-consul) |
| json (fs) | [paulgrove/le-store-simple-fs](https://github.com/paulgrove/le-store-simple-fs) |
| Redis | [digitalbazaar/le-store-redis](https://github.com/digitalbazaar/le-store-redis) |
| - | Build Your Own <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 |
## Auto-SNI
| | Plugin |
|:-----------:|:---------:|
| | Plugin |
| :---------: | :-------------------------------------------------------------: |
| **Default** | [le-sni-auto](https://git.coolaj86.com/coolaj86/le-sni-auto.js) |
(you probably wouldn't need or want to replace this)
**Bugs**: Please report bugs with the community plugins to the appropriate owner first, then here if you don't get a response.
# Usage
@ -300,34 +302,35 @@ node greenlock-express.js/examples/normal.js
It looks a little more like this:
`serve.js`:
```javascript
'use strict';
"use strict";
// returns an instance of greenlock.js with additional helper methods
var glx = require('greenlock-express').create({
server: 'https://acme-v02.api.letsencrypt.org/directory'
// Note: If at first you don't succeed, stop and switch to staging:
// https://acme-staging-v02.api.letsencrypt.org/directory
, version: 'draft-11' // Let's Encrypt v2 (ACME v2)
var glx = require("greenlock-express").create({
server: "https://acme-v02.api.letsencrypt.org/directory",
// Note: If at first you don't succeed, stop and switch to staging:
// https://acme-staging-v02.api.letsencrypt.org/directory
version: "draft-11", // Let's Encrypt v2 (ACME v2)
// If you wish to replace the default account and domain key storage plugin
, store: require('le-store-certbot').create({
configDir: require('path').join(require('os').homedir(), 'acme', 'etc')
, webrootPath: '/tmp/acme-challenges'
})
// If you wish to replace the default account and domain key storage plugin
store: require("le-store-certbot").create({
configDir: require("path").join(require("os").homedir(), "acme", "etc"),
webrootPath: "/tmp/acme-challenges"
}),
// Contribute telemetry data to the project
, telemetry: true
// Contribute telemetry data to the project
telemetry: true,
// the default servername to use when the client doesn't specify
// (because some IoT devices don't support servername indication)
, servername: 'example.com'
// the default servername to use when the client doesn't specify
// (because some IoT devices don't support servername indication)
servername: "example.com",
, approveDomains: approveDomains
approveDomains: approveDomains
});
var server = glx.listen(80, 443, function () {
console.log("Listening on port 80 for ACME challenges and 443 for express app.");
var server = glx.listen(80, 443, function() {
console.log("Listening on port 80 for ACME challenges and 443 for express app.");
});
```
@ -341,53 +344,54 @@ plainServer.on('error', function (err) { ... });
```
The Automatic Certificate Issuance is initiated via SNI (`httpsOptions.SNICallback`).
For security, domain validation MUST have an approval callback in *production*.
For security, domain validation MUST have an approval callback in _production_.
```javascript
var http01 = require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' });
var http01 = require("le-challenge-fs").create({ webrootPath: "/tmp/acme-challenges" });
function approveDomains(opts, certs, cb) {
// This is where you check your database and associated
// email addresses with domains and agreements and such
// if (!isAllowed(opts.domains)) { return cb(new Error("not allowed")); }
// This is where you check your database and associated
// email addresses with domains and agreements and such
// if (!isAllowed(opts.domains)) { return cb(new Error("not allowed")); }
// The domains being approved for the first time are listed in opts.domains
// Certs being renewed are listed in certs.altnames (if that's useful)
// The domains being approved for the first time are listed in opts.domains
// Certs being renewed are listed in certs.altnames (if that's useful)
// Opt-in to submit stats and get important updates
opts.communityMember = true;
// Opt-in to submit stats and get important updates
opts.communityMember = true;
// If you wish to replace the default challenge plugin, you may do so here
opts.challenges = { 'http-01': http01 };
// If you wish to replace the default challenge plugin, you may do so here
opts.challenges = { "http-01": http01 };
opts.email = 'john.doe@example.com';
opts.agreeTos = true;
opts.email = "john.doe@example.com";
opts.agreeTos = true;
// NOTE: you can also change other options such as `challengeType` and `challenge`
// opts.challengeType = 'http-01';
// opts.challenge = require('le-challenge-fs').create({});
// NOTE: you can also change other options such as `challengeType` and `challenge`
// opts.challengeType = 'http-01';
// opts.challenge = require('le-challenge-fs').create({});
cb(null, { options: opts, certs: certs });
cb(null, { options: opts, certs: certs });
}
```
```javascript
// handles acme-challenge and redirects to https
require('http').createServer(glx.middleware(require('redirect-https')())).listen(80, function () {
console.log("Listening for ACME http-01 challenges on", this.address());
});
require("http")
.createServer(glx.middleware(require("redirect-https")()))
.listen(80, function() {
console.log("Listening for ACME http-01 challenges on", this.address());
});
var app = require('express')();
app.use('/', function (req, res) {
res.end('Hello, World!');
var app = require("express")();
app.use("/", function(req, res) {
res.end("Hello, World!");
});
// handles your app
require('https').createServer(glx.httpsOptions, app).listen(443, function () {
console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address());
});
require("https")
.createServer(glx.httpsOptions, app)
.listen(443, function() {
console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address());
});
```
**Security**:
@ -395,7 +399,6 @@ require('https').createServer(glx.httpsOptions, app).listen(443, function () {
Greenlock will do a self-check on all domain registrations
to prevent you from hitting rate limits.
# API
This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO).
@ -405,35 +408,35 @@ The API is actually located at [greenlock.js options](https://git.rootprojects.o
The only "API" consists of two options, the rest is just a wrapper around `greenlock.js` to take LOC from 15 to 5:
* `opts.app` An express app in the format `function (req, res) { ... }` (no `next`).
* `server = glx.listen(plainAddr, tlsAddr, onListen)` Accepts port numbers (or arrays of port numbers) to listen on, returns secure server.
* `listen(80, 443)`
* `listen(80, 443, onListenSecure)`
* `listen(80, 443, onListenPlain, onListenSecure)`
* `listen('localhost:80', '0.0.0.0:443')`
* `listen('[::1]:80', '[::]:443')`
* `listen('/tmp/glx.plain.sock', '/tmp/glx.secure.sock')`
- `opts.app` An express app in the format `function (req, res) { ... }` (no `next`).
- `server = glx.listen(plainAddr, tlsAddr, onListen)` Accepts port numbers (or arrays of port numbers) to listen on, returns secure server.
- `listen(80, 443)`
- `listen(80, 443, onListenSecure)`
- `listen(80, 443, onListenPlain, onListenSecure)`
- `listen('localhost:80', '0.0.0.0:443')`
- `listen('[::1]:80', '[::]:443')`
- `listen('/tmp/glx.plain.sock', '/tmp/glx.secure.sock')`
Brief overview of some simple options for `greenlock.js`:
* `opts.server` set to https://acme-v02.api.letsencrypt.org/directory in production
* `opts.version` set to `v01` for Let's Encrypt v1 or `draft-11` for Let's Encrypt v2 (mistakenly called ACME v2)
* `opts.email` The default email to use to accept agreements.
* `opts.agreeTos` When set to `true`, this always accepts the LetsEncrypt TOS. When a string it checks the agreement url first.
* `opts.communityMember` Join the community to get notified of important updates and help make greenlock better
* `opts.approveDomains` can be either of:
* An explicit array of allowed domains such as `[ 'example.com', 'www.example.com' ]`
* A callback `function (opts, certs, cb) { cb(null, { options: opts, certs: certs }); }` for setting `email`, `agreeTos`, `domains`, etc (as shown in usage example above)
* `opts.renewWithin` is the **maximum** number of days (in ms) before expiration to renew a certificate.
* `opts.renewBy` is the **minimum** number of days (in ms) before expiration to renew a certificate.
- `opts.server` set to https://acme-v02.api.letsencrypt.org/directory in production
- `opts.version` set to `v01` for Let's Encrypt v1 or `draft-11` for Let's Encrypt v2 (mistakenly called ACME v2)
- `opts.email` The default email to use to accept agreements.
- `opts.agreeTos` When set to `true`, this always accepts the LetsEncrypt TOS. When a string it checks the agreement url first.
- `opts.communityMember` Join the community to get notified of important updates and help make greenlock better
- `opts.approveDomains` can be either of:
- An explicit array of allowed domains such as `[ 'example.com', 'www.example.com' ]`
- A callback `function (opts, certs, cb) { cb(null, { options: opts, certs: certs }); }` for setting `email`, `agreeTos`, `domains`, etc (as shown in usage example above)
- `opts.renewWithin` is the **maximum** number of days (in ms) before expiration to renew a certificate.
- `opts.renewBy` is the **minimum** number of days (in ms) before expiration to renew a certificate.
## Supported ACME versions
* Let's Encrypt v1 (aka v01)
* Let's Encrypt v2 (aka v02 or ACME draft 11)
* ACME draft 11 (ACME v2 is a misnomer)
* Wildcard domains (via dns-01 challenges)
* `*.example.com`
- Let's Encrypt v1 (aka v01)
- Let's Encrypt v2 (aka v02 or ACME draft 11)
- ACME draft 11 (ACME v2 is a misnomer)
- Wildcard domains (via dns-01 challenges)
- `*.example.com`
<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 = {
email: 'jon.doe@example.com'
, configDir: path.join(__dirname, 'acme')
, srv: '/srv/www/'
, api: '/srv/api/'
email: "jon.doe@example.com",
configDir: path.join(__dirname, "acme"),
srv: "/srv/www/",
api: "/srv/api/"
};

View File

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

View File

@ -1,29 +1,30 @@
'use strict';
"use strict";
//require('greenlock-express')
require('../').create({
require("../")
.create({
// Let's Encrypt v2 is ACME draft 11
version: "draft-11",
// Let's Encrypt v2 is ACME draft 11
version: 'draft-11'
server: "https://acme-v02.api.letsencrypt.org/directory",
// Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory
, server: 'https://acme-v02.api.letsencrypt.org/directory'
// Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory
email: "john.doe@example.com",
, email: 'john.doe@example.com'
agreeTos: true,
, agreeTos: true
approvedDomains: ["example.com", "www.example.com"],
, approvedDomains: [ 'example.com', 'www.example.com' ]
app: require("express")().use("/", function(req, res) {
res.end("Hello, World!");
}),
, app: require('express')().use('/', function (req, res) {
res.end('Hello, World!');
})
renewWithin: 91 * 24 * 60 * 60 * 1000,
renewBy: 90 * 24 * 60 * 60 * 1000,
, renewWithin: (91 * 24 * 60 * 60 * 1000)
, renewBy: (90 * 24 * 60 * 60 * 1000)
// Get notified of important updates and help me make greenlock better
, communityMember: true
, debug: true
}).listen(80, 443);
// Get notified of important updates and help me make greenlock better
communityMember: true,
debug: true
})
.listen(80, 443);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

522
index.js
View File

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

View File

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

1182
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

336
server.js
View File

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

View File

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