@small-tech/auto-encrypt 4.3.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/index.js CHANGED
@@ -1,139 +1,203 @@
1
1
  /**
2
- * Automatically provisions and renews Let’s Encrypt™ TLS certificates for
3
- * Node.js® https servers (including Express.js, etc.)
4
- *
5
- * Implements the subset of RFC 8555 – Automatic Certificate Management
6
- * Environment (ACME) necessary for a Node.js https server to provision TLS
7
- * certificates from Let’s Encrypt using the HTTP-01 challenge on first
8
- * hit of an HTTPS route via use of the Server Name Indication (SNI) callback.
9
- *
10
- * @module @small-tech/auto-encrypt
11
- * @copyright © 2020 Aral Balkan, Small Technology Foundation.
12
- * @license AGPLv3 or later.
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'
17
- import ocsp from 'ocsp'
18
22
  import monkeyPatchTls from './lib/staging/monkeyPatchTls.js'
19
23
  import LetsEncryptServer from './lib/LetsEncryptServer.js'
20
24
  import Configuration from './lib/Configuration.js'
21
25
  import Certificate from './lib/Certificate.js'
26
+ import AcmeRequest from './lib/AcmeRequest.js'
22
27
  import Pluralise from './lib/util/Pluralise.js'
23
28
  import Throws from './lib/util/Throws.js'
24
29
  import HttpServer from './lib/HttpServer.js'
30
+ import IPAddresses from './lib/IPAddresses.js'
25
31
  import log from './lib/util/log.js'
26
32
 
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')
33
+ // Use module-level await to ensure we have the IP address information we need.
34
+ const ipAddresses = await IPAddresses.getInstanceAsync()
31
35
 
32
36
  // Custom errors thrown by the autoEncrypt function.
33
37
  const throws = new Throws({
34
38
  [Symbol.for('BusyProvisioningCertificateError')]:
35
39
  () => 'We’re busy provisioning TLS certificates and rejecting all other calls at the moment.',
36
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
+
37
47
  [Symbol.for('SNIIgnoreUnsupportedDomainError')]:
38
- (serverName, domains) => {
48
+ (/** @type {string} */ serverName, /** @type {Array<string>} */ domains) => {
39
49
  return `SNI: Not responding to request for unsupported domain ${serverName} (valid ${Pluralise.word('domain', domains)} ${Pluralise.isAre(domains)} ${domains}).`
40
50
  }
41
51
  })
42
52
 
43
53
  /**
44
- * Auto Encrypt is a static class. Please do not instantiate.
45
- *
46
- * Use: AutoEncrypt.https.createServer(…)
47
- *
48
- * @alias module:@small-tech/auto-encrypt
49
- * @hideconstructor
50
- */
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
+ */
51
65
  export default class AutoEncrypt {
66
+ /** @type { AutoEncryptedServer } */
67
+ static server = null
68
+
69
+ /** @type { import('./lib/LetsEncryptServer.js').default | null } */
52
70
  static letsEncryptServer = null
71
+
72
+ /** @type { string[] | null } */
53
73
  static defaultDomains = null
74
+
75
+ /** @type { string[] | null } */
54
76
  static domains = null
77
+
78
+ /** @type { string | null } */
55
79
  static settingsPath = null
80
+
81
+ /** @type { ((req: import('http').IncomingMessage, res: import('http').ServerResponse) => void) | null } */
56
82
  static listener = null
83
+
84
+ /** @type { Certificate | null } */
57
85
  static certificate = null
58
86
 
59
87
  /**
60
- * Enumeration.
61
- *
62
- * @type {LetsEncryptServer.type}
63
- * @readonly
64
- * @static
65
- */
88
+ Enumeration.
89
+
90
+ @type {LetsEncryptServer.type}
91
+ @readonly
92
+ @static
93
+ */
66
94
  static serverType = LetsEncryptServer.type
67
95
 
68
96
  /**
69
- * By aliasing the https property to the AutoEncrypt static class itself, we enable
70
- * people to add AutoEncrypt to their existing apps by requiring the module
71
- * and prefixing their https.createServer(…) line with AutoEncrypt:
72
- *
73
- * @example import AutoEncrypt from '@small-tech/auto-encrypt'
74
- * const server = AutoEncrypt.https.createServer()
75
- *
76
- * @static
77
- */
78
- static get https () { return AutoEncrypt }
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:
79
100
 
101
+ @example import AutoEncrypt from '@small-tech/auto-encrypt'
102
+ const server = await AutoEncrypt.https.createServer()
80
103
 
81
- static ocspCache = null
104
+ @static
105
+ */
106
+ static get https () { return AutoEncrypt }
82
107
 
83
108
  /**
84
- * Automatically manages Let’s Encrypt certificate provisioning and renewal for Node.js
85
- * https servers using the HTTP-01 challenge on first hit of an HTTPS route via use of
86
- * the Server Name Indication (SNI) callback.
87
- *
88
- * @static
89
- * @param {Object} [options] Optional HTTPS options object with optional additional
90
- * Auto Encrypt-specific configuration settings.
91
- * @param {String[]} [options.domains] Domain names to provision TLS certificates for. If missing, defaults to
92
- * the hostname of the current computer and its www prefixed subdomain.
93
- * @param {Enum} [options.serverType=AutoEncrypt.serverType.PRODUCTION] Let’s Encrypt server type to use.
94
- * AutoEncrypt.serverType.PRODUCTION, ….STAGING,
95
- * or ….PEBBLE (see LetsEncryptServer.type).
96
- * @param {String} [options.settingsPath=~/.small-tech.org/auto-encrypt/] Path to save certificates/keys to.
97
- *
98
- * @returns {https.Server} The server instance returned by Node’s https.createServer() method.
99
- */
100
- static createServer(_options, _listener) {
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
+
101
136
  // The first parameter is optional. If omitted, the first argument, if any, is treated as the request listener.
102
137
  if (typeof _options === 'function') {
103
138
  _listener = _options
104
139
  _options = {}
105
140
  }
106
141
 
107
- const defaultStagingAndProductionDomains = [os.hostname(), `www.${os.hostname()}`]
108
- const defaultPebbleDomains = ['localhost', 'pebble']
142
+ const defaultPebbleDomains = []
109
143
  const options = _options || {}
110
144
  const letsEncryptServer = new LetsEncryptServer(options.serverType || LetsEncryptServer.type.PRODUCTION)
111
145
  const listener = _listener || null
112
146
  const settingsPath = options.settingsPath || null
113
147
 
114
- //
115
- // Ignore passed domains (if any) if we’re using pebble as we can only issue for localhost and pebble.
116
- //
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
+
117
190
  let defaultDomains = defaultStagingAndProductionDomains
118
191
 
192
+ // Behaviour specific to Let’s Encrypt server type (Pebble/staging).
119
193
  switch (letsEncryptServer.type) {
120
194
  case LetsEncryptServer.type.PEBBLE:
121
195
  options.domains = null
122
196
  defaultDomains = defaultPebbleDomains
123
197
  break
124
198
 
125
- // If this is a staging server, we add the intermediary certificate to Node.js’s trust store (only valid during
126
- // the current Node.js process) so that Node will accept the certificate. Useful when running tests against the
127
- // staging server.
128
- //
129
- // If you’re using Pebble for your tests, please install and use node-pebble manually in your tests.
130
- // (We cannot automatically provide support for Pebble as it dynamically generates its root and
131
- // intermediary CA certificates, which is an asynchronous process whereas the createServer method is
132
- // synchronous.)*
133
- //
134
- // * Yes, we could check for and start the Pebble server in the asynchronous SNICallback, below, but given how
135
- // often that function is called, I will not add anything to it beyond the essentials for performance reasons.
136
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.
137
201
  monkeyPatchTls()
138
202
  break
139
203
  }
@@ -144,6 +208,9 @@ export default class AutoEncrypt {
144
208
  delete options.domains
145
209
  delete options.serverType
146
210
  delete options.settingsPath
211
+ delete options.ipv4
212
+ delete options.ipv6
213
+ delete options.ipOnly
147
214
 
148
215
  const configuration = new Configuration({ settingsPath, domains, server: letsEncryptServer})
149
216
  const certificate = new Certificate(configuration)
@@ -155,159 +222,107 @@ export default class AutoEncrypt {
155
222
  this.listener = listener
156
223
  this.certificate = certificate
157
224
 
158
- function sniError (symbolName, callback, emoji, ...args) {
159
- const error = Symbol.for(symbolName)
160
- log(` ${emoji} ❨auto-encrypt❩ ${throws.errors[error](...args)}`)
161
- callback(throws.createError(error, ...args))
162
- }
225
+ // Start HTTP server.
226
+ log(' 🌍 ❨auto-encrypt❩ Starting HTTP challenge responder server…')
227
+ await HttpServer.getSharedInstance()
163
228
 
164
- options.SNICallback = async (serverName, callback) => {
165
- if (domains.includes(serverName)) {
166
- const secureContext = await certificate.getSecureContext()
167
- if (secureContext === null) {
168
- sniError('BusyProvisioningCertificateError', callback, '⏳')
169
- return
170
- }
171
- callback(null, secureContext)
172
- } else {
173
- sniError('SNIIgnoreUnsupportedDomainError', callback, '🤨', serverName, domains)
174
- }
175
- }
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).')
176
233
 
177
- const shouldAddOcspMustStaple = certificate.hasOcspMustStaple || false
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
178
239
 
179
- // During the transitionary period where Let’s Encrypt has shut down
180
- // OCSP support but there are still servers out there with certificates
181
- // that have OCSP stapling because their 6-month validity period isn’t
182
- // over, we create the server accordingly based on whether the certificate
183
- // has OCSP stapling or not so it works for both cases.
184
- // TODO: OCSP support can be fully removed in August 2025. All existing
185
- // servers with valid certificates will be non-OCSP at that point.
186
- const serverWithoutOcspMustStaple = https.createServer(options, listener)
187
- const server = shouldAddOcspMustStaple
188
- ? this.addOcspStapling(serverWithoutOcspMustStaple)
189
- : serverWithoutOcspMustStaple
240
+ // Create node:https server.
241
+ const server = /** @type {import('./index.d.ts').AutoEncryptedServer} */ (/** @type {unknown} */ (https.createServer(options, listener)))
242
+ this.server = server
190
243
 
191
244
  //
192
- // Monkey-patch the server.
245
+ // Monkey-patch server.
193
246
  //
194
247
 
195
- server.__autoEncrypt__self = this
196
-
197
- // Monkey-patch the server’s listen method so that we can start up the HTTP
198
- // Server at the same time.
199
- server.__autoEncrypt__originalListen = server.listen
200
- server.listen = function(...args) {
201
- // Start the HTTP server.
202
- HttpServer.getSharedInstance().then(() => {
203
- // Start the HTTPS server.
204
- return this.__autoEncrypt__originalListen.apply(this, args)
248
+ /**
249
+ Auto Encrypt’s async version of Node’s built-in `https.listen()` method.
250
+
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)
205
262
  })
206
263
  }
207
-
208
-
209
- // Monkey-patch the server’s close method so that we can perform clean-up and
210
- // also shut down the HTTP server transparently when server.close() is called.
211
- server.__autoEncrypt__originalClose = server.close
212
- server.close = function (...args) {
264
+ server['__original_listen__'] = server.listen.bind(server)
265
+ server.listen = listenAsync.bind(server)
266
+
267
+ /**
268
+ Auto Encrypt Localhost’s async version of Node’s built-in `https.close()` method.
269
+
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 () {
213
281
  // Clean-up our own house.
214
- this.__autoEncrypt__self.shutdown()
215
-
216
- // Shut down the HTTP server.
217
- HttpServer.destroySharedInstance().then(() => {
218
- // Shut down the HTTPS server.
219
- return this.__autoEncrypt__originalClose.apply(this, args)
282
+ await AutoEncrypt.shutdown()
283
+
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
+ })
220
293
  })
221
- }
222
-
223
- return server
224
- }
225
294
 
295
+ // Sever all idle and active connections.
296
+ this.closeAllConnections()
226
297
 
227
- /**
228
- * The OCSP module does not have a means of clearing its cache check timers
229
- * so we do it here. (Otherwise, the test suite would hang.)
230
- */
231
- static clearOcspCacheTimers () {
232
- if (this.ocspCache !== null) {
233
- const cacheIds = Object.keys(this.ocspCache.cache)
234
- cacheIds.forEach(cacheId => {
235
- clearInterval(this.ocspCache.cache[cacheId].timer)
236
- })
298
+ // Only now await the promise.
299
+ // (See note at: https://nodejs.org/docs/latest-v24.x/api/http.html#servercloseallconnections)
300
+ await closePromise
237
301
  }
238
- }
302
+ server['__original_close__'] = server.close.bind(server)
303
+ server.close = closeAsync.bind(server)
239
304
 
305
+ // Return the server singleton.
306
+ return this.server
307
+ }
308
+
240
309
  /**
241
- * Shut Auto Encrypt down. Do this before app exit. Performs necessary clean-up and removes
242
- * any references that might cause the app to not exit.
243
- */
244
- static shutdown () {
245
- this.clearOcspCacheTimers()
246
- this.certificate.stopCheckingForRenewal()
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
247
320
  }
248
321
 
249
322
  //
250
323
  // Private.
251
324
  //
252
325
 
253
- /**
254
- * Adds Online Certificate Status Protocol (OCSP) stapling (also known as TLS Certificate Status Request extension)
255
- * support to the passed server instance.
256
- *
257
- * @private
258
- * @param {https.Server} server HTTPS server instance without OCSP Stapling support.
259
- * @returns {https.Server} HTTPS server instance with OCSP Stapling support.
260
- */
261
- static addOcspStapling(server) {
262
- // OCSP stapling
263
- //
264
- // Many browsers will fetch OCSP from Let’s Encrypt when they load your site. This is a performance and privacy
265
- // problem. Ideally, connections to your site should not wait for a secondary connection to Let’s Encrypt. Also,
266
- // OCSP requests tell Let’s Encrypt which sites people are visiting. We have a good privacy policy and do not record
267
- // individually identifying details from OCSP requests, we’d rather not even receive the data in the first place.
268
- // Additionally, we anticipate our bandwidth costs for serving OCSP every time a browser visits a Let’s Encrypt site
269
- // for the first time will be a big part of our infrastructure expense.
270
- //
271
- // By turning on OCSP Stapling, you can improve the performance of your website, provide better privacy protections
272
- // … and help Let’s Encrypt efficiently serve as many people as possible.
273
- //
274
- // (Source: https://letsencrypt.org/docs/integration-guide/implement-ocsp-stapling)
275
-
276
- this.ocspCache = new ocsp.Cache()
277
- const cache = this.ocspCache
278
-
279
- server.on('OCSPRequest', (certificate, issuer, callback) => {
280
-
281
- if (certificate == null) {
282
- return callback(new Error('Cannot OCSP staple: certificate not yet provisioned.'))
283
- }
284
-
285
- ocsp.getOCSPURI(certificate, function(error, uri) {
286
- if (error) return callback(error)
287
- if (uri === null) return callback()
288
-
289
- const request = ocsp.request.generate(certificate, issuer)
290
-
291
- cache.probe(request.id, (error, cached) => {
292
- if (error) return callback(error)
293
-
294
- if (cached !== false) {
295
- return callback(null, cached.response)
296
- }
297
-
298
- const options = {
299
- url: uri,
300
- ocsp: request.data
301
- }
302
-
303
- cache.request(request.id, options, callback);
304
- })
305
- })
306
- })
307
-
308
- return server
309
- }
310
-
311
326
  // Custom object description for console output (for debugging).
312
327
  static [util.inspect.custom] () {
313
328
  return `
package/lib/Account.js CHANGED
@@ -1,18 +1,10 @@
1
- ////////////////////////////////////////////////////////////////////////////////
2
- //
3
- // Account
4
- //
5
- // (Async; please use Account.getInstanceAsync() factory method.)
6
- //
7
- // Represents a Let’s Encrypt account. Currently this is limited to the account
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
- // Async factory method.
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
- // TODO: throw error if Account has not been initialised instead of crashing in getter below.
72
+ /** @type { string } */
65
73
  get kid () { return this.data.kid }
66
- set kid (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'kid') }
74
+ set kid (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'kid') }
67
75
  }