Commit fc187e1e authored by Aral Balkan's avatar Aral Balkan
Browse files

Convert to ESM; all tests passing

parent 2a981a8a
......@@ -24,9 +24,12 @@
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const os = require('os')
const AutoEncrypt = require('../index')
const Pebble = require('@small-tech/node-pebble')
import AutoEncrypt from '../index.js'
import Pebble from '@small-tech/node-pebble'
// const os = require('os')
// const AutoEncrypt = require('../dist/auto-encrypt.js')
// const Pebble = require('@small-tech/node-pebble')
console.log('\n 🌄 Auto Encrypt “Hello, world!” Example \n')
......@@ -35,7 +38,7 @@ async function main() {
// Pebble is the local Let’s Encrypt testing server.
await Pebble.ready()
options = {
const options = {
/* Custom http server options, if any, go here (we don’t have any in this
example, so we could just not have passed this empty object at all). */
......
......@@ -11,19 +11,18 @@
* @copyright © 2020 Aral Balkan, Small Technology Foundation.
* @license AGPLv3 or later.
*/
const os = require('os')
const util = require('util')
const https = require('https')
const ocsp = require('ocsp')
const monkeyPatchTls = require('./lib/staging/monkeyPatchTls')
const LetsEncryptServer = require('./lib/LetsEncryptServer')
const Configuration = require('./lib/Configuration')
const Certificate = require('./lib/Certificate')
const Pluralise = require('./lib/util/Pluralise')
const Throws = require('./lib/util/Throws')
const HttpServer = require('./lib/HttpServer')
const log = require('./lib/util/log')
import os from 'os'
import util from 'util'
import https from 'https'
import ocsp from 'ocsp'
import monkeyPatchTls from './lib/staging/monkeyPatchTls.js'
import LetsEncryptServer from './lib/LetsEncryptServer.js'
import Configuration from './lib/Configuration.js'
import Certificate from './lib/Certificate.js'
import Pluralise from './lib/util/Pluralise.js'
import Throws from './lib/util/Throws.js'
import HttpServer from './lib/HttpServer.js'
import log from './lib/util/log.js'
// Custom errors thrown by the autoEncrypt function.
const throws = new Throws({
......@@ -44,13 +43,13 @@ const throws = new Throws({
* @alias module:@small-tech/auto-encrypt
* @hideconstructor
*/
class AutoEncrypt {
static #letsEncryptServer = null
static #defaultDomains = null
static #domains = null
static #settingsPath = null
static #listener = null
static #certificate = null
export default class AutoEncrypt {
static letsEncryptServer = null
static defaultDomains = null
static domains = null
static settingsPath = null
static listener = null
static certificate = null
/**
* Enumeration.
......@@ -66,7 +65,7 @@ class AutoEncrypt {
* people to add AutoEncrypt to their existing apps by requiring the module
* and prefixing their https.createServer(…) line with AutoEncrypt:
*
* @example const AutoEncrypt = require('@small-tech/auto-encrypt')
* @example import AutoEncrypt from '@small-tech/auto-encrypt'
* const server = AutoEncrypt.https.createServer()
*
* @static
......@@ -74,7 +73,7 @@ class AutoEncrypt {
static get https () { return AutoEncrypt }
static #ocspCache = null
static ocspCache = null
/**
* Automatically manages Let’s Encrypt certificate provisioning and renewal for Node.js
......@@ -144,12 +143,12 @@ class AutoEncrypt {
const configuration = new Configuration({ settingsPath, domains, server: letsEncryptServer})
const certificate = new Certificate(configuration)
this.#letsEncryptServer = letsEncryptServer
this.#defaultDomains = defaultDomains
this.#domains = domains
this.#settingsPath = settingsPath
this.#listener = listener
this.#certificate = certificate
this.letsEncryptServer = letsEncryptServer
this.defaultDomains = defaultDomains
this.domains = domains
this.settingsPath = settingsPath
this.listener = listener
this.certificate = certificate
function sniError (symbolName, callback, emoji, ...args) {
const error = Symbol.for(symbolName)
......@@ -227,7 +226,7 @@ class AutoEncrypt {
*/
static shutdown () {
this.clearOcspCacheTimers()
this.#certificate.stopCheckingForRenewal()
this.certificate.stopCheckingForRenewal()
}
//
......@@ -255,7 +254,7 @@ class AutoEncrypt {
// By turning on OCSP Stapling, you can improve the performance of your website, provide better privacy protections
// … and help Let’s Encrypt efficiently serve as many people as possible.
//
// (Source: https://letsencrypt.org/docs/integration-guide/#implement-ocsp-stapling)
// (Source: https://letsencrypt.org/docs/integration-guide/implement-ocsp-stapling)
this.ocspCache = new ocsp.Cache()
const cache = this.ocspCache
......@@ -295,12 +294,12 @@ class AutoEncrypt {
// Custom object description for console output (for debugging).
static [util.inspect.custom] () {
return `
# AutoEncrypt (static class)
# AutoEncrypt (static class)
- Using Let’s Encrypt ${this.#letsEncryptServer.name} server.
- Managing TLS for ${this.#domains.toString().replace(',', ', ')}${this.#domains === this.#defaultDomains ? ' (default domains)' : ''}.
- Settings stored at ${this.#settingsPath === null ? 'default settings path' : this.#settingsPath}.
- Listener ${typeof this.#listener === 'function' ? 'is set' : 'not set'}.
- Using Let’s Encrypt ${this.letsEncryptServer.name} server.
- Managing TLS for ${this.domains.toString().replace(',', ', ')}${this.domains === this.defaultDomains ? ' (default domains)' : ''}.
- Settings stored at ${this.settingsPath === null ? 'default settings path' : this.settingsPath}.
- Listener ${typeof this.listener === 'function' ? 'is set' : 'not set'}.
`
}
......@@ -308,5 +307,3 @@ class AutoEncrypt {
throws.error(Symbol.for('StaticClassCannotBeInstantiatedError'))
}
}
module.exports = AutoEncrypt
......@@ -14,15 +14,15 @@
//
////////////////////////////////////////////////////////////////////////////////
const fs = require('fs-extra')
const Throws = require('./util/Throws')
const NewAccountRequest = require('./acme-requests/NewAccountRequest')
import fs from 'fs-extra'
import Throws from './util/Throws.js'
import NewAccountRequest from './acme-requests/NewAccountRequest.js'
const throws = new Throws({
// No custom errors are thrown by this class.
})
class Account {
export default class Account {
//
// Async factory method.
//
......@@ -65,5 +65,3 @@ class Account {
get kid () { return this.data.kid }
set kid (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'kid') }
}
module.exports = Account
......@@ -6,12 +6,12 @@
* @license AGPLv3 or later.
*/
const jose = require('jose')
const prepareRequest = require('bent')
const types = require('../typedefs/lib/AcmeRequest')
const Nonce = require('./Nonce')
const Throws = require('./util/Throws')
const log = require('./util/log')
import jose from 'jose'
import prepareRequest from 'bent'
import types from '../typedefs/lib/AcmeRequest.js'
import Nonce from './Nonce.js'
import Throws from './util/Throws.js'
import log from './util/log.js'
const throws = new Throws({
[Symbol.for('AcmeRequest.classNotInitialisedError')]:
......@@ -28,33 +28,33 @@ const throws = new Throws({
*
* @alias module:lib/AcmeRequest
*/
class AcmeRequest {
static #initialised = false
static #directory = null
static #accountIdentity = null
static #nonce = null
static #account = null
export default class AcmeRequest {
static initialised = false
static directory = null
static accountIdentity = null
static nonce = null
static __account = null
static initialise (directory = throws.ifMissing(), accountIdentity = throws.ifMissing()) {
this.#directory = directory
this.#accountIdentity = accountIdentity
this.#nonce = new Nonce(directory)
this.#initialised = true
this.directory = directory
this.accountIdentity = accountIdentity
this.nonce = new Nonce(directory)
this.initialised = true
}
static uninitialise () {
this.#directory = null
this.#accountIdentity = null
this.#nonce = null
this.#account = null
this.#initialised = false
this.directory = null
this.accountIdentity = null
this.nonce = null
this.__account = null
this.initialised = false
}
static set account (_account = throws.ifMissing()) { this.#account = _account }
static get account () { return this.#account }
static set account (_account = throws.ifMissing()) { this.__account = _account }
static get account () { return this.__account }
constructor () {
if (!AcmeRequest.#initialised) {
if (!AcmeRequest.initialised) {
throws.error(Symbol.for('AcmeRequest.classNotInitialisedError'))
}
}
......@@ -145,7 +145,7 @@ class AcmeRequest {
// Always save the fresh nonce returned from API calls.
const freshNonce = response.headers['replay-nonce']
AcmeRequest.#nonce.set(freshNonce)
AcmeRequest.nonce.set(freshNonce)
// The response returned is the raw response object. Let’s consume
// it and return a more relevant response.
......@@ -208,11 +208,11 @@ class AcmeRequest {
// ===== the arguments array as the latter does not reflect default parameters.
const originalRequestDetails = [command, payload, useKid, successCodes, url, nonce]
url = url || AcmeRequest.#directory[`${command}Url`]
url = url || AcmeRequest.directory[`${command}Url`]
const protectedHeader = {
alg: 'RS256',
nonce: nonce || await AcmeRequest.#nonce.get(),
nonce: nonce || await AcmeRequest.nonce.get(),
url
}
......@@ -221,10 +221,10 @@ class AcmeRequest {
protectedHeader.kid = AcmeRequest.account.kid
} else {
// If we’re not using the kid, we must use the public JWK (see RFC 8555 § 6.2 Request Authentication)
protectedHeader.jwk = AcmeRequest.#accountIdentity.publicJWK
protectedHeader.jwk = AcmeRequest.accountIdentity.publicJWK
}
const signedRequest = jose.JWS.sign.flattened(payload, AcmeRequest.#accountIdentity.key, protectedHeader)
const signedRequest = jose.JWS.sign.flattened(payload, AcmeRequest.accountIdentity.key, protectedHeader)
const httpsHeaders = {
'Content-Type': 'application/jose+json',
......@@ -244,6 +244,3 @@ class AcmeRequest {
}
}
}
module.exports = AcmeRequest
......@@ -13,14 +13,14 @@
//
////////////////////////////////////////////////////////////////////////////////
const EventEmitter = require('events')
const log = require('./util/log')
const AuthorisationRequest = require('./acme-requests/AuthorisationRequest')
const ReadyForChallengeValidationRequest = require('./acme-requests/ReadyForChallengeValidationRequest')
const HttpServer = require('./HttpServer')
const waitFor = require('./util/waitFor')
import EventEmitter from 'events'
import log from './util/log.js'
import AuthorisationRequest from './acme-requests/AuthorisationRequest.js'
import ReadyForChallengeValidationRequest from './acme-requests/ReadyForChallengeValidationRequest.js'
import HttpServer from './HttpServer.js'
import waitFor from './util/waitFor.js'
class Authorisation extends EventEmitter {
export default class Authorisation extends EventEmitter {
// Async factory method. Use this to instantiate.
// TODO: add check to ensure factory method is used.
......@@ -181,5 +181,3 @@ class Authorisation extends EventEmitter {
}
}
}
module.exports = Authorisation
......@@ -6,19 +6,19 @@
* @license AGPLv3 or later.
*/
const fs = require('fs-extra')
const tls = require('tls')
const util = require('util')
const moment = require('moment')
const log = require('./util/log')
const x509 = require('./x.509/rfc5280')
const Account = require('./Account')
const AccountIdentity = require('./identities/AccountIdentity')
const Directory = require('./Directory')
const Order = require('./Order')
const CertificateIdentity = require('./identities/CertificateIdentity')
const AcmeRequest = require('./AcmeRequest')
const Throws = require('./util/Throws')
import fs from 'fs-extra'
import tls from 'tls'
import util from 'util'
import moment from 'moment'
import log from './util/log.js'
import { Certificate as X509Certificate } from './x.509/rfc5280.js'
import Account from './Account.js'
import AccountIdentity from './identities/AccountIdentity.js'
import Directory from './Directory.js'
import Order from './Order.js'
import CertificateIdentity from './identities/CertificateIdentity.js'
import AcmeRequest from './AcmeRequest.js'
import Throws from './util/Throws.js'
const throws = new Throws({
// No custom errors are thrown by this class.
......@@ -30,7 +30,7 @@ const throws = new Throws({
* @alias module:lib/Certificate
* @param {String[]} domains List of domains this certificate covers.
*/
class Certificate {
export default class Certificate {
/**
* Get a SecureContext that can be used in an SNICallback.
*
......@@ -367,7 +367,7 @@ class Certificate {
}
parseDetails (certificatePem) {
const certificate = (x509.Certificate.decode(certificatePem, 'pem', {label: 'CERTIFICATE'})).tbsCertificate
const certificate = (X509Certificate.decode(certificatePem, 'pem', {label: 'CERTIFICATE'})).tbsCertificate
const serialNumber = certificate.serialNumber
const issuer = certificate.issuer.value[0][0].value.toString('utf-8').slice(2).trim()
......@@ -417,5 +417,3 @@ class Certificate {
`
}
}
module.exports = Certificate
......@@ -7,13 +7,13 @@
* @license AGPLv3 or later.
*/
const os = require('os')
const fs = require('fs-extra')
const path = require('path')
const util = require('util')
const crypto = require('crypto')
const log = require('./util/log')
const Throws = require('./util/Throws')
import os from 'os'
import fs from 'fs-extra'
import path from 'path'
import util from 'util'
import crypto from 'crypto'
import log from './util/log.js'
import Throws from './util/Throws.js'
// Custom errors thrown by this class.
const throws = new Throws({
......@@ -30,7 +30,7 @@ function isAnArrayOfStrings (object) {
* @alias module:lib/Configuration
* @hideconstructor
*/
class Configuration {
export default class Configuration {
#server = null
#domains = null
#settingsPath = null
......@@ -230,4 +230,3 @@ class Configuration {
`
}
}
module.exports = Configuration
......@@ -11,25 +11,25 @@
//
////////////////////////////////////////////////////////////////////////////////
const util = require('util')
const prepareRequest = require('bent')
const log = require('./util/log')
const Throws = require('./util/Throws')
import util from 'util'
import prepareRequest from 'bent'
import log from './util/log.js'
import Throws from './util/Throws.js'
const throws = new Throws()
class Directory {
#directory = null
#letsEncryptServer = null
#directoryRequest = null
export default class Directory {
directory = null
letsEncryptServer = null
directoryRequest = null
//
// Factory method access (async).
//
static #isBeingInstantiatedViaAsyncFactoryMethod = false
static isBeingInstantiatedViaAsyncFactoryMethod = false
static async getInstanceAsync (configuration = throws.ifMissing()) {
Directory.#isBeingInstantiatedViaAsyncFactoryMethod = true
Directory.isBeingInstantiatedViaAsyncFactoryMethod = true
const directory = new Directory(configuration)
await directory.getUrls()
return directory
......@@ -40,13 +40,13 @@ class Directory {
//
// Directory URLs.
get keyChangeUrl() { return this.#directory.keyChange }
get newAccountUrl() { return this.#directory.newAccount }
get newNonceUrl() { return this.#directory.newNonce }
get newOrderUrl() { return this.#directory.newOrder }
get revokeCertUrl() { return this.#directory.revokeCert }
get termsOfServiceUrl() { return this.#directory.meta.termsOfService }
get websiteUrl() { return this.#directory.meta.website }
get keyChangeUrl() { return this.directory.keyChange }
get newAccountUrl() { return this.directory.newAccount }
get newNonceUrl() { return this.directory.newNonce }
get newOrderUrl() { return this.directory.newOrder }
get revokeCertUrl() { return this.directory.revokeCert }
get termsOfServiceUrl() { return this.directory.meta.termsOfService }
get websiteUrl() { return this.directory.meta.website }
//
// Private.
......@@ -54,28 +54,28 @@ class Directory {
constructor(configuration) {
// Ensure async factory method instantiation.
if (Directory.#isBeingInstantiatedViaAsyncFactoryMethod === false) {
if (Directory.isBeingInstantiatedViaAsyncFactoryMethod === false) {
throws.error(Symbol.for('MustBeInstantiatedViaAsyncFactoryMethodError'), 'Directory')
}
Directory.#isBeingInstantiatedViaAsyncFactoryMethod = false
Directory.isBeingInstantiatedViaAsyncFactoryMethod = false
this.#letsEncryptServer = configuration.server
this.#directoryRequest = prepareRequest('GET', 'json', this.#letsEncryptServer.endpoint)
this.letsEncryptServer = configuration.server
this.directoryRequest = prepareRequest('GET', 'json', this.letsEncryptServer.endpoint)
log(` 📕 ❨auto-encrypt❩ Directory is using endpoint ${this.#letsEncryptServer.endpoint}`)
log(` 📕 ❨auto-encrypt❩ Directory is using endpoint ${this.letsEncryptServer.endpoint}`)
}
// (Async) Fetches the latest Urls from the Let’s Encrypt ACME endpoint being used.
// This will throw if the request fails. Ensure that you catch the error when
// using it.
async getUrls() { this.#directory = await this.#directoryRequest() }
async getUrls() { this.directory = await this.directoryRequest() }
// Custom object description for console output (for debugging).
[util.inspect.custom] () {
return `
# Directory
Endpoint: ${this.#letsEncryptServer.endpoint}
Endpoint: ${this.letsEncryptServer.endpoint}
## URLs:
......@@ -89,5 +89,3 @@ class Directory {
`
}
}
module.exports = Directory
......@@ -25,12 +25,12 @@
//
////////////////////////////////////////////////////////////////////////////////
const http = require('http')
const encodeUrl = require('encodeurl')
const enableDestroy = require('server-destroy')
const log = require('./util/log')
import http from 'http'
import encodeUrl from 'encodeurl'
import enableDestroy from 'server-destroy'
import log from './util/log.js'
class HttpServer {
export default class HttpServer {
//
// Singleton access (async).
//
......@@ -169,5 +169,3 @@ class HttpServer {
})
}
}
module.exports = HttpServer
......@@ -14,17 +14,17 @@
//
////////////////////////////////////////////////////////////////////////////////
const util = require('util')
const fs = require('fs-extra')
const jose = require('jose')
const Throws = require('./util/Throws')
const log = require('./util/log')
import util from 'util'
import fs from 'fs-extra'
import jose from 'jose'
import Throws from './util/Throws.js'
import log from './util/log.js'
const throws = new Throws({
[Symbol.for('UnsupportedIdentityType')]: identityFilePath => `The identity file path passed (${identityFilePath}) is for an unsupported identity type.`
})
class Identity {
export default class Identity {
constructor (configuration = throws.ifMissing(), identityFilePathKey = throws.ifMissing()) {
const identityFilePath = configuration[identityFilePathKey]
......@@ -109,5 +109,3 @@ class Identity {
//
#identityFilePath = null
}
module.exports = Identity
const util = require('util')
import util from 'util'
class LetsEncryptServer {
export default class LetsEncryptServer {
/**
* Enumeration.
*
......@@ -54,5 +54,3 @@ class LetsEncryptServer {
'http://localhost:9829/directory'
]
}
module.exports = LetsEncryptServer
......@@ -20,13 +20,13 @@
//
////////////////////////////////////////////////////////////////////////////////
const prepareRequest = require('bent')
const log = require('./util/log')
const Throws = require('./util/Throws')
import prepareRequest from 'bent'
import log from './util/log.js'
import Throws from './util/Throws.js'
const throws = new Throws()
class Nonce {
export default class Nonce {
constructor (directory = throws.ifMissing()) {
this.#directory = directory
}
......@@ -73,5 +73,3 @@ class Nonce {
#directory = null
#freshNonce = null
}
module.exports = Nonce
......@@ -12,23 +12,23 @@
//
////////////////////////////////////////////////////////////////////////////////