@small-tech/auto-encrypt 4.2.0 → 5.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 +64 -64
- package/index.d.ts +152 -0
- package/index.js +219 -116
- package/lib/Account.js +28 -20
- package/lib/AcmeRequest.js +187 -117
- package/lib/Authorisation.js +80 -36
- package/lib/Certificate.js +299 -145
- package/lib/Configuration.js +93 -77
- package/lib/Directory.js +28 -17
- package/lib/HttpServer.js +55 -28
- package/lib/IPAddresses.js +114 -0
- package/lib/Identity.js +65 -37
- package/lib/LetsEncryptServer.js +15 -15
- package/lib/Nonce.js +36 -27
- package/lib/Order.js +65 -39
- package/lib/acme-requests/AuthorisationRequest.js +15 -14
- package/lib/acme-requests/CertificateRequest.js +16 -15
- package/lib/acme-requests/CheckOrderStatusRequest.js +19 -17
- package/lib/acme-requests/FinaliseOrderRequest.js +21 -19
- package/lib/acme-requests/NewAccountRequest.js +13 -14
- package/lib/acme-requests/NewOrderRequest.js +30 -17
- package/lib/acme-requests/ReadyForChallengeValidationRequest.js +17 -16
- package/lib/acmeCsr.js +116 -59
- package/lib/identities/AccountIdentity.js +15 -14
- package/lib/identities/CertificateIdentity.js +16 -16
- package/lib/staging/monkeyPatchTls.js +12 -12
- package/lib/test-helpers/index.js +8 -23
- package/lib/util/Pluralise.js +2 -2
- package/lib/util/Throws.js +61 -21
- package/lib/util/async-foreach.js +21 -21
- package/lib/util/fromNow.js +27 -0
- package/lib/util/log.js +5 -1
- package/lib/util/waitFor.js +5 -0
- package/lib/x.509/rfc5280.js +47 -12
- package/package.json +12 -10
- package/typedefs/lib/AcmeRequest.js +36 -25
package/index.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
Automatically provisions and renews Let’s Encrypt™ TLS certificates for Node.js® https servers (as used in @small-tech/https and Kitten.)
|
|
3
|
+
|
|
4
|
+
Implements the subset of RFC 8555 – Automatic Certificate Management Environment (ACME) – necessary for a Node.js https server to provision TLS certificates from Let’s Encrypt using the HTTP-01 challenge.
|
|
5
|
+
|
|
6
|
+
The certificates are provisioned, if necessary, at time of server creation, prior to server start with hourly renewal checks and automatic renewals.
|
|
7
|
+
|
|
8
|
+
Auto Encrypt uses the new Let’s Encrypt `shortlived` profile *exclusively*, which means certificates are valid for (and renewed before) 160 hours. This should occur at around the 80 hour mark of the certificate’s lifespan, in accordance with data returned by ACME Renewal Information (ARI).
|
|
9
|
+
|
|
10
|
+
Auto Encrypt also implements automatic support for obtaining certificates on IPv4 and IPv6 addresses to enable the use of Web Numbers on the Small Web (see https://ar.al/2025/06/25/web-numbers/).
|
|
11
|
+
|
|
12
|
+
Remember that Auto Encrypt is for the Small Web (peer-to-peer Web). You might find it useful for everyday web purposes also but it is specifically focused for Small Web use cases and explicitly does not aim to be a generic or exhaustive ACME client.
|
|
13
|
+
|
|
14
|
+
@module @small-tech/auto-encrypt
|
|
15
|
+
@copyright © 2020-present Aral Balkan, Small Technology Foundation.
|
|
16
|
+
@license AGPLv3 or later.
|
|
13
17
|
*/
|
|
18
|
+
|
|
14
19
|
import os from 'os'
|
|
15
20
|
import util from 'util'
|
|
16
21
|
import https from 'https'
|
|
@@ -18,118 +23,181 @@ import monkeyPatchTls from './lib/staging/monkeyPatchTls.js'
|
|
|
18
23
|
import LetsEncryptServer from './lib/LetsEncryptServer.js'
|
|
19
24
|
import Configuration from './lib/Configuration.js'
|
|
20
25
|
import Certificate from './lib/Certificate.js'
|
|
26
|
+
import AcmeRequest from './lib/AcmeRequest.js'
|
|
21
27
|
import Pluralise from './lib/util/Pluralise.js'
|
|
22
28
|
import Throws from './lib/util/Throws.js'
|
|
23
29
|
import HttpServer from './lib/HttpServer.js'
|
|
30
|
+
import IPAddresses from './lib/IPAddresses.js'
|
|
24
31
|
import log from './lib/util/log.js'
|
|
25
32
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
import dns from 'node:dns'
|
|
29
|
-
dns.setDefaultResultOrder('ipv4first')
|
|
33
|
+
// Use module-level await to ensure we have the IP address information we need.
|
|
34
|
+
const ipAddresses = await IPAddresses.getInstanceAsync()
|
|
30
35
|
|
|
31
36
|
// Custom errors thrown by the autoEncrypt function.
|
|
32
37
|
const throws = new Throws({
|
|
33
38
|
[Symbol.for('BusyProvisioningCertificateError')]:
|
|
34
39
|
() => 'We’re busy provisioning TLS certificates and rejecting all other calls at the moment.',
|
|
35
40
|
|
|
41
|
+
[Symbol.for('IPv4AddressNotFound')]:
|
|
42
|
+
() => 'IPv4 certificate requested but could not automatically find machine’s IPv4 address',
|
|
43
|
+
|
|
44
|
+
[Symbol.for('IPv6AddressNotFound')]:
|
|
45
|
+
() => 'IPv6 certificates requested but could not automatically find any stable IPv6 addresses',
|
|
46
|
+
|
|
36
47
|
[Symbol.for('SNIIgnoreUnsupportedDomainError')]:
|
|
37
|
-
(serverName, domains) => {
|
|
48
|
+
(/** @type {string} */ serverName, /** @type {Array<string>} */ domains) => {
|
|
38
49
|
return `SNI: Not responding to request for unsupported domain ${serverName} (valid ${Pluralise.word('domain', domains)} ${Pluralise.isAre(domains)} ${domains}).`
|
|
39
50
|
}
|
|
40
51
|
})
|
|
41
52
|
|
|
42
53
|
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
@typedef {import('./index.d.ts').AutoEncryptedServer} AutoEncryptedServer
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
Auto Encrypt is a static class. Please do not instantiate.
|
|
59
|
+
|
|
60
|
+
Use: AutoEncrypt.https.createServer(…)
|
|
61
|
+
|
|
62
|
+
@alias module:@small-tech/auto-encrypt
|
|
63
|
+
@hideconstructor
|
|
64
|
+
*/
|
|
50
65
|
export default class AutoEncrypt {
|
|
66
|
+
/** @type { AutoEncryptedServer } */
|
|
67
|
+
static server = null
|
|
68
|
+
|
|
69
|
+
/** @type { import('./lib/LetsEncryptServer.js').default | null } */
|
|
51
70
|
static letsEncryptServer = null
|
|
71
|
+
|
|
72
|
+
/** @type { string[] | null } */
|
|
52
73
|
static defaultDomains = null
|
|
74
|
+
|
|
75
|
+
/** @type { string[] | null } */
|
|
53
76
|
static domains = null
|
|
77
|
+
|
|
78
|
+
/** @type { string | null } */
|
|
54
79
|
static settingsPath = null
|
|
80
|
+
|
|
81
|
+
/** @type { ((req: import('http').IncomingMessage, res: import('http').ServerResponse) => void) | null } */
|
|
55
82
|
static listener = null
|
|
83
|
+
|
|
84
|
+
/** @type { Certificate | null } */
|
|
56
85
|
static certificate = null
|
|
57
86
|
|
|
58
87
|
/**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
88
|
+
Enumeration.
|
|
89
|
+
|
|
90
|
+
@type {LetsEncryptServer.type}
|
|
91
|
+
@readonly
|
|
92
|
+
@static
|
|
93
|
+
*/
|
|
65
94
|
static serverType = LetsEncryptServer.type
|
|
66
95
|
|
|
67
96
|
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
By aliasing the https property to the AutoEncrypt static class itself, we enable
|
|
98
|
+
people to add AutoEncrypt to their existing apps by requiring the module
|
|
99
|
+
and prefixing their https.createServer(…) line with await AutoEncrypt:
|
|
100
|
+
|
|
101
|
+
@example import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
102
|
+
const server = await AutoEncrypt.https.createServer()
|
|
103
|
+
|
|
104
|
+
@static
|
|
105
|
+
*/
|
|
77
106
|
static get https () { return AutoEncrypt }
|
|
78
107
|
|
|
79
108
|
/**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
Automatically manages Let’s Encrypt certificate provisioning and renewal for Node.js
|
|
110
|
+
https servers using the HTTP-01 challenge on first hit of an HTTPS route via use of
|
|
111
|
+
the Server Name Indication (SNI) callback.
|
|
112
|
+
|
|
113
|
+
@static
|
|
114
|
+
|
|
115
|
+
@param {import('tls').SecureContextOptions & {
|
|
116
|
+
domains?: Array<string>,
|
|
117
|
+
serverType?: number,
|
|
118
|
+
settingsPath?: string,
|
|
119
|
+
ipv4?: boolean,
|
|
120
|
+
ipv6?: boolean,
|
|
121
|
+
ipOnly?: boolean,
|
|
122
|
+
SNICallback?: (serverName: string, callback: (...args: any[]) => void) => Promise<void> | void
|
|
123
|
+
} | ((...any: any[]) => void)} _options
|
|
124
|
+
|
|
125
|
+
@param {(...any: any[]) => void} [_listener]
|
|
126
|
+
|
|
127
|
+
@returns {Promise<import('./index.d.ts').AutoEncryptedServer>} A promise for the server instance (a monkey-patched https.Server similar to the one returned by Node’s https.createServer() method).
|
|
128
|
+
*/
|
|
129
|
+
static async createServer(_options, _listener) {
|
|
130
|
+
// Auto Encrypt only supports one server per Node process. As such, if the server already exists, return the existing instance.
|
|
131
|
+
if (this.server !== null) {
|
|
132
|
+
log(' ☝️ ❨auto-encrypt❩ Returning reference to existing server (there can be only one… per process).')
|
|
133
|
+
return this.server
|
|
134
|
+
}
|
|
135
|
+
|
|
97
136
|
// The first parameter is optional. If omitted, the first argument, if any, is treated as the request listener.
|
|
98
137
|
if (typeof _options === 'function') {
|
|
99
138
|
_listener = _options
|
|
100
139
|
_options = {}
|
|
101
140
|
}
|
|
102
141
|
|
|
103
|
-
const
|
|
104
|
-
const defaultPebbleDomains = ['localhost', 'pebble']
|
|
142
|
+
const defaultPebbleDomains = []
|
|
105
143
|
const options = _options || {}
|
|
106
144
|
const letsEncryptServer = new LetsEncryptServer(options.serverType || LetsEncryptServer.type.PRODUCTION)
|
|
107
145
|
const listener = _listener || null
|
|
108
146
|
const settingsPath = options.settingsPath || null
|
|
109
147
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
148
|
+
let defaultStagingAndProductionDomains = []
|
|
149
|
+
if (!options.ipOnly) {
|
|
150
|
+
defaultStagingAndProductionDomains.push(os.hostname())
|
|
151
|
+
defaultPebbleDomains.push('localhost')
|
|
152
|
+
defaultPebbleDomains.push('pebble')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (options.ipv4 === true) {
|
|
156
|
+
if (letsEncryptServer.type === LetsEncryptServer.type.PEBBLE) {
|
|
157
|
+
const localIPv4Address = '127.0.0.1'
|
|
158
|
+
defaultPebbleDomains.push(localIPv4Address)
|
|
159
|
+
log(` 📍 ❨auto-encrypt❩ Will provision TLS certificate from Pebble server for local IPv4 address (${localIPv4Address})`)
|
|
160
|
+
} else {
|
|
161
|
+
if (ipAddresses.hasIPv4Address) {
|
|
162
|
+
const ipv4Address = ipAddresses.ipv4Address
|
|
163
|
+
defaultStagingAndProductionDomains.push(ipv4Address)
|
|
164
|
+
log(` 📍 ❨auto-encrypt❩ Will provision TLS certificate for detected IPv4 address: ${ipv4Address}`)
|
|
165
|
+
} else {
|
|
166
|
+
throws.error(Symbol.for('IPv4AddressNotFound'))
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (options.ipv6 === true) {
|
|
172
|
+
if (letsEncryptServer.type === LetsEncryptServer.type.PEBBLE) {
|
|
173
|
+
// IPv6: Pebble.
|
|
174
|
+
const localIPv6Address = '::1'
|
|
175
|
+
defaultPebbleDomains.push(localIPv6Address)
|
|
176
|
+
log(` 📍 ❨auto-encrypt❩ Will provision TLS certificate from Pebble server for local IPv6 address (${localIPv6Address})`)
|
|
177
|
+
} else {
|
|
178
|
+
// IPv6: Staging and production.
|
|
179
|
+
const ipv6Addresses = ipAddresses.ipv6Addresses
|
|
180
|
+
if (ipv6Addresses.length > 0) {
|
|
181
|
+
defaultStagingAndProductionDomains = defaultStagingAndProductionDomains.concat(ipv6Addresses)
|
|
182
|
+
ipv6Addresses.forEach(ipv6Address => defaultPebbleDomains.push(ipv6Address))
|
|
183
|
+
log(` 📍 ❨auto-encrypt❩ Will provision TLS certificate for detected stable IPv6 address${ipv6Addresses.length > 1 ? 'es' : ''}: ${ipv6Addresses}`)
|
|
184
|
+
} else {
|
|
185
|
+
throws.error(Symbol.for('IPv6AddressNotFound'))
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
113
190
|
let defaultDomains = defaultStagingAndProductionDomains
|
|
114
191
|
|
|
192
|
+
// Behaviour specific to Let’s Encrypt server type (Pebble/staging).
|
|
115
193
|
switch (letsEncryptServer.type) {
|
|
116
194
|
case LetsEncryptServer.type.PEBBLE:
|
|
117
195
|
options.domains = null
|
|
118
196
|
defaultDomains = defaultPebbleDomains
|
|
119
197
|
break
|
|
120
198
|
|
|
121
|
-
// If this is a staging server, we add the intermediary certificate to Node.js’s trust store (only valid during
|
|
122
|
-
// the current Node.js process) so that Node will accept the certificate. Useful when running tests against the
|
|
123
|
-
// staging server.
|
|
124
|
-
//
|
|
125
|
-
// If you’re using Pebble for your tests, please install and use node-pebble manually in your tests.
|
|
126
|
-
// (We cannot automatically provide support for Pebble as it dynamically generates its root and
|
|
127
|
-
// intermediary CA certificates, which is an asynchronous process whereas the createServer method is
|
|
128
|
-
// synchronous.)*
|
|
129
|
-
//
|
|
130
|
-
// * Yes, we could check for and start the Pebble server in the asynchronous SNICallback, below, but given how
|
|
131
|
-
// often that function is called, I will not add anything to it beyond the essentials for performance reasons.
|
|
132
199
|
case LetsEncryptServer.type.STAGING:
|
|
200
|
+
// Add the intermediary certificate to Node.js’s trust store (only valid during the current Node.js process) so that Node will accept the certificate. Useful when running tests against the staging server.
|
|
133
201
|
monkeyPatchTls()
|
|
134
202
|
break
|
|
135
203
|
}
|
|
@@ -140,6 +208,9 @@ export default class AutoEncrypt {
|
|
|
140
208
|
delete options.domains
|
|
141
209
|
delete options.serverType
|
|
142
210
|
delete options.settingsPath
|
|
211
|
+
delete options.ipv4
|
|
212
|
+
delete options.ipv6
|
|
213
|
+
delete options.ipOnly
|
|
143
214
|
|
|
144
215
|
const configuration = new Configuration({ settingsPath, domains, server: letsEncryptServer})
|
|
145
216
|
const certificate = new Certificate(configuration)
|
|
@@ -151,69 +222,101 @@ export default class AutoEncrypt {
|
|
|
151
222
|
this.listener = listener
|
|
152
223
|
this.certificate = certificate
|
|
153
224
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
callback(throws.createError(error, ...args))
|
|
158
|
-
}
|
|
225
|
+
// Start HTTP server.
|
|
226
|
+
log(' 🌍 ❨auto-encrypt❩ Starting HTTP challenge responder server…')
|
|
227
|
+
await HttpServer.getSharedInstance()
|
|
159
228
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
}
|
|
229
|
+
// Provision certificate (if required).
|
|
230
|
+
log(` 📃 ❨auto-encrypt❩ Provisioning certificate (if required) for domains: ${domains.join(', ')}…`)
|
|
231
|
+
await certificate.getSecureContext()
|
|
232
|
+
log(' ✅ ❨auto-encrypt❩ Certificate provisioned (or already exists).')
|
|
233
|
+
|
|
234
|
+
// Pass the certificate and key to the https.createServer options as well.
|
|
235
|
+
// This is required for IP hits (which don't use SNI) and also acts as the
|
|
236
|
+
// default certificate.
|
|
237
|
+
options.key = certificate.key
|
|
238
|
+
options.cert = certificate.pem
|
|
172
239
|
|
|
173
|
-
|
|
240
|
+
// Create node:https server.
|
|
241
|
+
const server = /** @type {import('./index.d.ts').AutoEncryptedServer} */ (/** @type {unknown} */ (https.createServer(options, listener)))
|
|
242
|
+
this.server = server
|
|
174
243
|
|
|
175
244
|
//
|
|
176
|
-
// Monkey-patch
|
|
245
|
+
// Monkey-patch server.
|
|
177
246
|
//
|
|
178
247
|
|
|
179
|
-
|
|
248
|
+
/**
|
|
249
|
+
Auto Encrypt’s async version of Node’s built-in `https.listen()` method.
|
|
180
250
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
251
|
+
You can use this method with exactly the same signature as Node’s and
|
|
252
|
+
with a callback but, ideally, you should be awaiting it and not using the callback.
|
|
253
|
+
|
|
254
|
+
@param {...*} args - Arguments to pass to https.Server.listen()
|
|
255
|
+
@returns {Promise<AutoEncryptedServer>}
|
|
256
|
+
*/
|
|
257
|
+
const listenAsync = async function (...args) {
|
|
258
|
+
return new Promise((resolve, reject) => {
|
|
259
|
+
const server = this['__original_listen__'](...args)
|
|
260
|
+
.once('listening', () => resolve(server))
|
|
261
|
+
.once('error', reject)
|
|
189
262
|
})
|
|
190
263
|
}
|
|
264
|
+
server['__original_listen__'] = server.listen.bind(server)
|
|
265
|
+
server.listen = listenAsync.bind(server)
|
|
191
266
|
|
|
267
|
+
/**
|
|
268
|
+
Auto Encrypt Localhost’s async version of Node’s built-in `https.close()` method.
|
|
192
269
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
270
|
+
Unlike Node’s `close()` method, this version also calls `closeAllConnections()` so it
|
|
271
|
+
will sever any existing idle and active connections. When `await`ed, this call will
|
|
272
|
+
only return once `node:https`’s close callback is called. This is also where your
|
|
273
|
+
callback will fire, if you are using Node’s original method signature will callbacks.
|
|
274
|
+
|
|
275
|
+
You can still use this method with exactly the same signature as Node’s and
|
|
276
|
+
with a callback but, ideally, you should be awaiting it and not using the callback.
|
|
277
|
+
|
|
278
|
+
@returns {Promise<void>}
|
|
279
|
+
*/
|
|
280
|
+
const closeAsync = async function () {
|
|
197
281
|
// Clean-up our own house.
|
|
198
|
-
|
|
282
|
+
await AutoEncrypt.shutdown()
|
|
199
283
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
284
|
+
// Start shutting down the HTTPS server.
|
|
285
|
+
const closePromise = new Promise((resolve, reject) => {
|
|
286
|
+
this['__original_close__']((/** @type { Error } */ error) => {
|
|
287
|
+
if (error) {
|
|
288
|
+
reject(error)
|
|
289
|
+
} else {
|
|
290
|
+
resolve()
|
|
291
|
+
}
|
|
292
|
+
})
|
|
204
293
|
})
|
|
205
|
-
}
|
|
206
294
|
|
|
207
|
-
|
|
208
|
-
|
|
295
|
+
// Sever all idle and active connections.
|
|
296
|
+
this.closeAllConnections()
|
|
209
297
|
|
|
298
|
+
// Only now await the promise.
|
|
299
|
+
// (See note at: https://nodejs.org/docs/latest-v24.x/api/http.html#servercloseallconnections)
|
|
300
|
+
await closePromise
|
|
301
|
+
}
|
|
302
|
+
server['__original_close__'] = server.close.bind(server)
|
|
303
|
+
server.close = closeAsync.bind(server)
|
|
210
304
|
|
|
305
|
+
// Return the server singleton.
|
|
306
|
+
return this.server
|
|
307
|
+
}
|
|
308
|
+
|
|
211
309
|
/**
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
static shutdown () {
|
|
216
|
-
this.certificate
|
|
310
|
+
Shut Auto Encrypt down. Do this before app exit. Performs necessary clean-up and removes
|
|
311
|
+
any references that might cause the app to not exit.
|
|
312
|
+
*/
|
|
313
|
+
static async shutdown () {
|
|
314
|
+
if (this.certificate) {
|
|
315
|
+
this.certificate.stopCheckingForRenewal()
|
|
316
|
+
}
|
|
317
|
+
await HttpServer.destroySharedInstance()
|
|
318
|
+
AcmeRequest.uninitialise()
|
|
319
|
+
this.server = null
|
|
217
320
|
}
|
|
218
321
|
|
|
219
322
|
//
|
package/lib/Account.js
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// URL used as the "kid" value in the JWS authenticating subsequent requests
|
|
9
|
-
// by this account after it is created using the JWT public key.
|
|
10
|
-
// See RFC 8555 § 6.2, 7.3.
|
|
11
|
-
//
|
|
12
|
-
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
13
|
-
// License: AGPLv3 or later.
|
|
14
|
-
//
|
|
15
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
Represents a Let’s Encrypt account.
|
|
3
|
+
|
|
4
|
+
@module
|
|
5
|
+
@copyright Copyright © 2020-present Aral Balkan, Small Technology Foundation.
|
|
6
|
+
@license AGPLv3 or later.
|
|
7
|
+
*/
|
|
16
8
|
|
|
17
9
|
import fs from 'fs'
|
|
18
10
|
import Throws from './util/Throws.js'
|
|
@@ -22,11 +14,23 @@ const throws = new Throws({
|
|
|
22
14
|
// No custom errors are thrown by this class.
|
|
23
15
|
})
|
|
24
16
|
|
|
17
|
+
/**
|
|
18
|
+
(Async; please use Account.getInstanceAsync() factory method.)
|
|
19
|
+
|
|
20
|
+
Represents a Let’s Encrypt account. Currently this is limited to the account
|
|
21
|
+
URL used as the "kid" value in the JWS authenticating subsequent requests
|
|
22
|
+
by this account after it is created using the JWT public key.
|
|
23
|
+
See RFC 8555 § 6.2, 7.3.
|
|
24
|
+
|
|
25
|
+
@alias module:lib/Account
|
|
26
|
+
*/
|
|
25
27
|
export default class Account {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
//
|
|
28
|
+
/**
|
|
29
|
+
Async static factory method. __Use this to instantiate, not the constructor.__
|
|
29
30
|
|
|
31
|
+
@param { import('./Configuration.js').default } configuration
|
|
32
|
+
@public
|
|
33
|
+
*/
|
|
30
34
|
static async getInstanceAsync (configuration) {
|
|
31
35
|
Account.isBeingInstantiatedViaSingletonFactoryMethod = true
|
|
32
36
|
const account = new Account(configuration)
|
|
@@ -39,6 +43,10 @@ export default class Account {
|
|
|
39
43
|
//
|
|
40
44
|
static isBeingInstantiatedViaSingletonFactoryMethod = false
|
|
41
45
|
|
|
46
|
+
/**
|
|
47
|
+
@private
|
|
48
|
+
@param { import('./Configuration.js').default } configuration
|
|
49
|
+
*/
|
|
42
50
|
constructor (configuration) {
|
|
43
51
|
// Ensure factory method-based initialisation.
|
|
44
52
|
if (Account.isBeingInstantiatedViaSingletonFactoryMethod === false) {
|
|
@@ -61,7 +69,7 @@ export default class Account {
|
|
|
61
69
|
}
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
/** @type { string } */
|
|
65
73
|
get kid () { return this.data.kid }
|
|
66
|
-
set kid (
|
|
74
|
+
set kid (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'kid') }
|
|
67
75
|
}
|