@small-tech/auto-encrypt 3.1.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -29
- package/index.js +5 -0
- package/lib/HttpServer.js +8 -14
- package/lib/test-helpers/index.js +12 -5
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Adds automatic provisioning and renewal of [Let’s Encrypt](https://letsencrypt.org) TLS certificates with [OCSP Stapling](https://letsencrypt.org/docs/integration-guide/#implement-ocsp-stapling) to [Node.js](https://nodejs.org) [https](https://nodejs.org/dist/latest-v12.x/docs/api/https.html) servers (including [Express.js](https://expressjs.com/), etc.)
|
|
4
4
|
|
|
5
|
+
Implements the subset of RFC 8555 – Automatic Certificate Management Environment (ACME) – necessary for a client to support TLS certificate provisioning from Let’s Encrypt using HTTP-01 challenges.
|
|
6
|
+
|
|
5
7
|
## How it works
|
|
6
8
|
|
|
7
9
|
The first time your web site is hit, it will take a couple of seconds to load as your Let’s Encrypt TLS certificates are automatically provisioned for you. From there on, your certificates will be seamlessly renewed 30 days before their expiry date.
|
|
8
10
|
|
|
9
11
|
When not provisioning certificates, Auto Encrypt will also forward HTTP calls to HTTPS on your server.
|
|
10
12
|
|
|
13
|
+
## Compatibility
|
|
14
|
+
|
|
15
|
+
All tests pass on Node.js LTS (18.2+).
|
|
16
|
+
|
|
17
|
+
[Tests fail on Node.js 19 (socket hang up error).](https://codeberg.org/small-tech/auto-encrypt/issues/3)
|
|
18
|
+
|
|
11
19
|
## Installation
|
|
12
20
|
|
|
13
21
|
```sh
|
|
@@ -19,31 +27,30 @@ npm i @small-tech/auto-encrypt
|
|
|
19
27
|
### Instructions
|
|
20
28
|
|
|
21
29
|
1. Import the module:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
33
|
+
```
|
|
26
34
|
|
|
27
35
|
2. Prefix your server creation code with a reference to the Auto Encrypt class:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
// const server = https.createServer(…) becomes
|
|
39
|
+
const server = AutoEncrypt.https.createServer(…)
|
|
40
|
+
```
|
|
33
41
|
|
|
34
42
|
3. When done, close your server as you would normally:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
server.close(() => {
|
|
46
|
+
console.log('The server is now closed.')
|
|
47
|
+
})
|
|
48
|
+
```
|
|
41
49
|
|
|
42
50
|
### Example
|
|
43
51
|
|
|
44
52
|
The following code creates an HTTPS server running on port 443 with [OCSP Stapling](https://letsencrypt.org/docs/integration-guide/#implement-ocsp-stapling) that automatically provisions and renews TLS certificates from [Let’s Encrypt](https://letsencrypt.org) for the domains _<hostname>_ and _www.<hostname>_.
|
|
45
53
|
|
|
46
|
-
|
|
47
54
|
```js
|
|
48
55
|
import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
49
56
|
|
|
@@ -94,9 +101,9 @@ const server = AutoEncrypt.https.createServer(options, listener)
|
|
|
94
101
|
|
|
95
102
|
Here is the full list of Auto Encrypt options (all optional) and their defaults:
|
|
96
103
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
- `domains`: the hostname of the current computer and the www subdomain at that hostname.
|
|
105
|
+
- `server`: it will use the production server (which has [rate limits](https://letsencrypt.org/docs/rate-limits/)). Valid values are `AutoEncrypt.server.(PEBBLE|STAGING|PRODUCTION)`.
|
|
106
|
+
- `settingsPath`: _~/.small-tech.org/auto-encrypt/_
|
|
100
107
|
|
|
101
108
|
## Making a graceful exit
|
|
102
109
|
|
|
@@ -174,10 +181,9 @@ Auto Encrypt does one thing and one thing well: it automatically provisions a Le
|
|
|
174
181
|
|
|
175
182
|
Auto Encrypt __does not_ and __will not__:
|
|
176
183
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
- Implement DNS-01 or any other methods that cannot be fully automated.
|
|
184
|
+
- Implement wildcard certificates. For most [small tech](https://small-tech.org/about/#small-technology) needs (personal web sites and web apps), you will likely need no more than two domains (the root domain and, due to historic and conventional reasons, the www subdomain). You will definitely not need more than the 100 domains that are supported per certificate. If you do, chances are you are looking to use Auto Encrypt in a startup or corporate setting, which is not what its for.
|
|
180
185
|
|
|
186
|
+
- Implement DNS-01 or any other methods that cannot be fully automated.
|
|
181
187
|
|
|
182
188
|
## Staging and production server behaviour and rate limits
|
|
183
189
|
|
|
@@ -191,29 +197,28 @@ If you do use the staging environment, be aware that browsers will reject the st
|
|
|
191
197
|
|
|
192
198
|
Needless to say, do not add the fake certificate root to the same trust store you use for your everyday browsing.
|
|
193
199
|
|
|
194
|
-
|
|
195
200
|
## Related projects
|
|
196
201
|
|
|
197
202
|
From lower-level to higher-level:
|
|
198
203
|
|
|
199
204
|
### Auto Encrypt Localhost
|
|
200
205
|
|
|
201
|
-
|
|
202
|
-
|
|
206
|
+
- Source: https://source.small-tech.org/site.js/lib/auto-encrypt-localhost
|
|
207
|
+
- Package: [@small-tech/auto-encrypt-localhost](https://www.npmjs.com/package/@small-tech/auto-encrypt-localhost)
|
|
203
208
|
|
|
204
209
|
Automatically provisions and installs locally-trusted TLS certificates for Node.js https servers (including Express.js, etc.) using [mkcert](https://github.com/FiloSottile/mkcert/).
|
|
205
210
|
|
|
206
211
|
### HTTPS
|
|
207
212
|
|
|
208
|
-
|
|
209
|
-
|
|
213
|
+
- Source: https://source.small-tech.org/site.js/lib/https
|
|
214
|
+
- Package: [@small-tech/https](https://www.npmjs.com/package/@small-tech/https)
|
|
210
215
|
|
|
211
216
|
A drop-in [standard Node.js HTTPS module](https://nodejs.org/dist/latest-v12.x/docs/api/https.html) replacement with both automatic development-time (localhost) certificates via Auto Encrypt Localhost and automatic production certificates via Auto Encrypt.
|
|
212
217
|
|
|
213
218
|
### Site.js
|
|
214
219
|
|
|
215
|
-
|
|
216
|
-
|
|
220
|
+
- Web site: https://sitejs.org
|
|
221
|
+
- Source: https://source.small-tech.org/site.js/app
|
|
217
222
|
|
|
218
223
|
A complete [small technology](https://small-tech.org/about/#small-technology) tool for developing, testing, and deploying a secure static or dynamic personal web site or app with zero configuration.
|
|
219
224
|
|
package/index.js
CHANGED
|
@@ -24,6 +24,11 @@ import Throws from './lib/util/Throws.js'
|
|
|
24
24
|
import HttpServer from './lib/HttpServer.js'
|
|
25
25
|
import log from './lib/util/log.js'
|
|
26
26
|
|
|
27
|
+
// This reverts IP address sort order to pre-Node 17 behaviour.
|
|
28
|
+
// See https://github.com/nodejs/node/issues/40537
|
|
29
|
+
import dns from 'node:dns'
|
|
30
|
+
dns.setDefaultResultOrder('ipv4first')
|
|
31
|
+
|
|
27
32
|
// Custom errors thrown by the autoEncrypt function.
|
|
28
33
|
const throws = new Throws({
|
|
29
34
|
[Symbol.for('BusyProvisioningCertificateError')]:
|
package/lib/HttpServer.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// B. At all other times:
|
|
19
19
|
// ======================
|
|
20
20
|
//
|
|
21
|
-
// Forwards http requests to https requests using a
|
|
21
|
+
// Forwards http requests to https requests using a 307 redirect.
|
|
22
22
|
//
|
|
23
23
|
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
24
24
|
// License: AGPLv3 or later.
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
|
|
28
28
|
import http from 'http'
|
|
29
29
|
import encodeUrl from 'encodeurl'
|
|
30
|
-
import enableDestroy from 'server-destroy'
|
|
31
30
|
import log from './util/log.js'
|
|
32
31
|
|
|
33
32
|
export default class HttpServer {
|
|
@@ -120,18 +119,13 @@ export default class HttpServer {
|
|
|
120
119
|
response.end()
|
|
121
120
|
}
|
|
122
121
|
})
|
|
123
|
-
|
|
124
|
-
// Enable server to be destroyed without waiting for any existing connections to close.
|
|
125
|
-
// (While there shouldn’t be any existing connections and while the likelihood of someone
|
|
126
|
-
// trying to denial-of-service this very low, it’s still the right thing to do.)
|
|
127
|
-
enableDestroy(this.server)
|
|
128
122
|
}
|
|
129
123
|
|
|
130
124
|
set challengeServer (state) {
|
|
131
125
|
if (state) {
|
|
132
126
|
log(` 🔒 ❨auto-encrypt❩ HTTP server is now only responding to Let’s Encrypt challenges.`)
|
|
133
127
|
} else {
|
|
134
|
-
log(` 🔒 ❨auto-encrypt❩ HTTP server is now forwarding HTTP requests to HTTPS (
|
|
128
|
+
log(` 🔒 ❨auto-encrypt❩ HTTP server is now forwarding HTTP requests to HTTPS (307).`)
|
|
135
129
|
}
|
|
136
130
|
this.#isChallengeServer = state
|
|
137
131
|
}
|
|
@@ -156,16 +150,16 @@ export default class HttpServer {
|
|
|
156
150
|
|
|
157
151
|
async destroy () {
|
|
158
152
|
// Starts killing all connections and closes the server.
|
|
159
|
-
this.server.
|
|
153
|
+
this.server.closeAllConnections()
|
|
160
154
|
|
|
161
|
-
// Wait until the server is closed before returning.
|
|
162
155
|
await new Promise((resolve, reject) => {
|
|
163
|
-
this.server.
|
|
156
|
+
this.server.close(error => {
|
|
157
|
+
if (error) {
|
|
158
|
+
console.error(error)
|
|
159
|
+
reject(error)
|
|
160
|
+
}
|
|
164
161
|
resolve()
|
|
165
162
|
})
|
|
166
|
-
this.server.on('error', (error) => {
|
|
167
|
-
reject(error)
|
|
168
|
-
})
|
|
169
163
|
})
|
|
170
164
|
}
|
|
171
165
|
}
|
|
@@ -8,7 +8,6 @@ import fs from 'fs'
|
|
|
8
8
|
import os from 'os'
|
|
9
9
|
import path from 'path'
|
|
10
10
|
import http from 'http'
|
|
11
|
-
import enableServerDestroy from 'server-destroy'
|
|
12
11
|
import Configuration from '../Configuration.js'
|
|
13
12
|
import LetsEncryptServer from '../LetsEncryptServer.js'
|
|
14
13
|
import Throws from '../util/Throws.js'
|
|
@@ -19,7 +18,7 @@ import log from '../util/log.js'
|
|
|
19
18
|
//
|
|
20
19
|
|
|
21
20
|
const throws = new Throws({
|
|
22
|
-
[Symbol.for('MockServerCouldNotBeStartedError')]:
|
|
21
|
+
[Symbol.for('MockServerCouldNotBeStartedError')]: error => `Mock server could not be started (${error})`
|
|
23
22
|
})
|
|
24
23
|
|
|
25
24
|
export class MockServer {
|
|
@@ -45,7 +44,6 @@ export class MockServer {
|
|
|
45
44
|
|
|
46
45
|
async create () {
|
|
47
46
|
const server = http.createServer(this.#responseHandler)
|
|
48
|
-
enableServerDestroy(server)
|
|
49
47
|
this.#server = server
|
|
50
48
|
await new Promise((resolve, reject) => {
|
|
51
49
|
try {
|
|
@@ -60,12 +58,21 @@ export class MockServer {
|
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
async destroy () {
|
|
63
|
-
this.#server.
|
|
61
|
+
this.#server.closeAllConnections()
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
this.#server.close(error => {
|
|
64
|
+
if (error) {
|
|
65
|
+
console.error(error)
|
|
66
|
+
reject(error)
|
|
67
|
+
}
|
|
68
|
+
resolve()
|
|
69
|
+
})
|
|
70
|
+
})
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
export async function httpServerWithResponse (mockResponse) {
|
|
68
|
-
return new Promise((resolve,
|
|
75
|
+
return new Promise((resolve, _reject) => {
|
|
69
76
|
const server = http.createServer((request, response) => {
|
|
70
77
|
response.statusCode = mockResponse.statusCode
|
|
71
78
|
response.end(mockResponse.body)
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@small-tech/auto-encrypt",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Adds automatic provisioning and renewal of Let’s Encrypt TLS certificates with OCSP Stapling to Node.js https servers (including Express.js, etc.)",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=18.2.0"
|
|
7
|
+
},
|
|
5
8
|
"keywords": [
|
|
6
9
|
"let's encrypt",
|
|
7
10
|
"acme",
|
|
@@ -10,6 +13,7 @@
|
|
|
10
13
|
"tls",
|
|
11
14
|
"auto encrypt",
|
|
12
15
|
"small tech",
|
|
16
|
+
"small web",
|
|
13
17
|
"automatic"
|
|
14
18
|
],
|
|
15
19
|
"author": {
|
|
@@ -60,24 +64,20 @@
|
|
|
60
64
|
]
|
|
61
65
|
},
|
|
62
66
|
"dependencies": {
|
|
63
|
-
"bent": "
|
|
67
|
+
"bent": "^7.3.12",
|
|
64
68
|
"encodeurl": "^1.0.2",
|
|
65
|
-
"jose": "^1.
|
|
66
|
-
"moment": "^2.
|
|
69
|
+
"jose": "^1.28.2",
|
|
70
|
+
"moment": "^2.29.4",
|
|
67
71
|
"node-forge": "^1.3.1",
|
|
68
|
-
"ocsp": "^1.2.0"
|
|
69
|
-
"server-destroy": "^1.0.1"
|
|
72
|
+
"ocsp": "^1.2.0"
|
|
70
73
|
},
|
|
71
74
|
"devDependencies": {
|
|
72
75
|
"@small-tech/esm-tape-runner": "^1.0.3",
|
|
73
76
|
"@small-tech/node-pebble": "^4.2.4",
|
|
74
77
|
"@small-tech/tap-monkey": "^1.3.0",
|
|
75
|
-
"c8": "^7.
|
|
76
|
-
"dependency-cruiser": "^
|
|
77
|
-
"esbuild": "^0.8.53",
|
|
78
|
-
"jsdoc": "^3.6.6",
|
|
78
|
+
"c8": "^7.12.0",
|
|
79
|
+
"dependency-cruiser": "^12.3.0",
|
|
79
80
|
"jsdoc-to-markdown": "^6.0.1",
|
|
80
|
-
"tape": "^5.2.1"
|
|
81
|
-
"wtfnode": "^0.8.1"
|
|
81
|
+
"tape": "^5.2.1"
|
|
82
82
|
}
|
|
83
83
|
}
|