Commit 079e4787 authored by Aral Balkan's avatar Aral Balkan
Browse files

Merge branch 'esm'

parents b2b3b047 fc187e1e
.vscode .vscode
.nyc_output .nyc_output
node_modules node_modules
coverage
dist
...@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. ...@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.1.0] - Work in progress
### Changed
- Is now an ECMAScript Modules (ESM) project
- Dev: now using esm-tape-runner
- Dev: replaced tap-spec and tap-nyc with @small-tech/tap-monkey
## [2.0.6] - 2021-02-16 ## [2.0.6] - 2021-02-16
### Fixed ### Fixed
......
...@@ -219,7 +219,7 @@ A complete [small technology](https://small-tech.org/about/#small-technology) to ...@@ -219,7 +219,7 @@ A complete [small technology](https://small-tech.org/about/#small-technology) to
## Tests and coverage ## Tests and coverage
This project aims for > 80% coverage. At a recent check, coverage was at 95.29% (statements), 82.69% (branch), 95.19% (functions), 95.68% (lines). This project aims for > 80% coverage. At a recent check, coverage was at 97.42% (statements), 92.64% (branch), 91.49% (functions), 97.42% (lines).
To see the current state of code coverage, run `npm run coverage`. To see the current state of code coverage, run `npm run coverage`.
......
...@@ -24,9 +24,12 @@ ...@@ -24,9 +24,12 @@
// //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const os = require('os') import AutoEncrypt from '../index.js'
const AutoEncrypt = require('../index') import Pebble from '@small-tech/node-pebble'
const Pebble = require('@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') console.log('\n 🌄 Auto Encrypt “Hello, world!” Example \n')
...@@ -35,7 +38,7 @@ async function main() { ...@@ -35,7 +38,7 @@ async function main() {
// Pebble is the local Let’s Encrypt testing server. // Pebble is the local Let’s Encrypt testing server.
await Pebble.ready() await Pebble.ready()
options = { const options = {
/* Custom http server options, if any, go here (we don’t have any in this /* 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). */ example, so we could just not have passed this empty object at all). */
......
...@@ -11,19 +11,18 @@ ...@@ -11,19 +11,18 @@
* @copyright © 2020 Aral Balkan, Small Technology Foundation. * @copyright © 2020 Aral Balkan, Small Technology Foundation.
* @license AGPLv3 or later. * @license AGPLv3 or later.
*/ */
import os from 'os'
const os = require('os') import util from 'util'
const util = require('util') import https from 'https'
const https = require('https') import ocsp from 'ocsp'
const ocsp = require('ocsp') import monkeyPatchTls from './lib/staging/monkeyPatchTls.js'
const monkeyPatchTls = require('./lib/staging/monkeyPatchTls') import LetsEncryptServer from './lib/LetsEncryptServer.js'
const LetsEncryptServer = require('./lib/LetsEncryptServer') import Configuration from './lib/Configuration.js'
const Configuration = require('./lib/Configuration') import Certificate from './lib/Certificate.js'
const Certificate = require('./lib/Certificate') import Pluralise from './lib/util/Pluralise.js'
const Pluralise = require('./lib/util/Pluralise') import Throws from './lib/util/Throws.js'
const Throws = require('./lib/util/Throws') import HttpServer from './lib/HttpServer.js'
const HttpServer = require('./lib/HttpServer') import log from './lib/util/log.js'
const log = require('./lib/util/log')
// Custom errors thrown by the autoEncrypt function. // Custom errors thrown by the autoEncrypt function.
const throws = new Throws({ const throws = new Throws({
...@@ -44,13 +43,13 @@ const throws = new Throws({ ...@@ -44,13 +43,13 @@ const throws = new Throws({
* @alias module:@small-tech/auto-encrypt * @alias module:@small-tech/auto-encrypt
* @hideconstructor * @hideconstructor
*/ */
class AutoEncrypt { export default class AutoEncrypt {
static #letsEncryptServer = null static letsEncryptServer = null
static #defaultDomains = null static defaultDomains = null
static #domains = null static domains = null
static #settingsPath = null static settingsPath = null
static #listener = null static listener = null
static #certificate = null static certificate = null
/** /**
* Enumeration. * Enumeration.
...@@ -66,7 +65,7 @@ class AutoEncrypt { ...@@ -66,7 +65,7 @@ class AutoEncrypt {
* people to add AutoEncrypt to their existing apps by requiring the module * people to add AutoEncrypt to their existing apps by requiring the module
* and prefixing their https.createServer(…) line with AutoEncrypt: * 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() * const server = AutoEncrypt.https.createServer()
* *
* @static * @static
...@@ -74,7 +73,7 @@ class AutoEncrypt { ...@@ -74,7 +73,7 @@ class AutoEncrypt {
static get https () { return AutoEncrypt } static get https () { return AutoEncrypt }
static #ocspCache = null static ocspCache = null
/** /**
* Automatically manages Let’s Encrypt certificate provisioning and renewal for Node.js * Automatically manages Let’s Encrypt certificate provisioning and renewal for Node.js
...@@ -144,12 +143,12 @@ class AutoEncrypt { ...@@ -144,12 +143,12 @@ class AutoEncrypt {
const configuration = new Configuration({ settingsPath, domains, server: letsEncryptServer}) const configuration = new Configuration({ settingsPath, domains, server: letsEncryptServer})
const certificate = new Certificate(configuration) const certificate = new Certificate(configuration)
this.#letsEncryptServer = letsEncryptServer this.letsEncryptServer = letsEncryptServer
this.#defaultDomains = defaultDomains this.defaultDomains = defaultDomains
this.#domains = domains this.domains = domains
this.#settingsPath = settingsPath this.settingsPath = settingsPath
this.#listener = listener this.listener = listener
this.#certificate = certificate this.certificate = certificate
function sniError (symbolName, callback, emoji, ...args) { function sniError (symbolName, callback, emoji, ...args) {
const error = Symbol.for(symbolName) const error = Symbol.for(symbolName)
...@@ -227,7 +226,7 @@ class AutoEncrypt { ...@@ -227,7 +226,7 @@ class AutoEncrypt {
*/ */
static shutdown () { static shutdown () {
this.clearOcspCacheTimers() this.clearOcspCacheTimers()
this.#certificate.stopCheckingForRenewal() this.certificate.stopCheckingForRenewal()
} }
// //
...@@ -255,7 +254,7 @@ class AutoEncrypt { ...@@ -255,7 +254,7 @@ class AutoEncrypt {
// By turning on OCSP Stapling, you can improve the performance of your website, provide better privacy protections // 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. // … 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() this.ocspCache = new ocsp.Cache()
const cache = this.ocspCache const cache = this.ocspCache
...@@ -295,12 +294,12 @@ class AutoEncrypt { ...@@ -295,12 +294,12 @@ class AutoEncrypt {
// Custom object description for console output (for debugging). // Custom object description for console output (for debugging).
static [util.inspect.custom] () { static [util.inspect.custom] () {
return ` return `
# AutoEncrypt (static class) # AutoEncrypt (static class)
- Using Let’s Encrypt ${this.#letsEncryptServer.name} server. - Using Let’s Encrypt ${this.letsEncryptServer.name} server.
- Managing TLS for ${this.#domains.toString().replace(',', ', ')}${this.#domains === this.#defaultDomains ? ' (default domains)' : ''}. - Managing TLS for ${this.domains.toString().replace(',', ', ')}${this.domains === this.defaultDomains ? ' (default domains)' : ''}.
- Settings stored at ${this.#settingsPath === null ? 'default settings path' : this.#settingsPath}. - Settings stored at ${this.settingsPath === null ? 'default settings path' : this.settingsPath}.
- Listener ${typeof this.#listener === 'function' ? 'is set' : 'not set'}. - Listener ${typeof this.listener === 'function' ? 'is set' : 'not set'}.
` `
} }
...@@ -308,5 +307,3 @@ class AutoEncrypt { ...@@ -308,5 +307,3 @@ class AutoEncrypt {
throws.error(Symbol.for('StaticClassCannotBeInstantiatedError')) throws.error(Symbol.for('StaticClassCannotBeInstantiatedError'))
} }
} }
module.exports = AutoEncrypt
...@@ -14,15 +14,15 @@ ...@@ -14,15 +14,15 @@
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
const fs = require('fs-extra') import fs from 'fs-extra'
const Throws = require('./util/Throws') import Throws from './util/Throws.js'
const NewAccountRequest = require('./acme-requests/NewAccountRequest') import NewAccountRequest from './acme-requests/NewAccountRequest.js'
const throws = new Throws({ const throws = new Throws({
// No custom errors are thrown by this class. // No custom errors are thrown by this class.
}) })
class Account { export default class Account {
// //
// Async factory method. // Async factory method.
// //
...@@ -65,5 +65,3 @@ class Account { ...@@ -65,5 +65,3 @@ class Account {
get kid () { return this.data.kid } get kid () { return this.data.kid }
set kid (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'kid') } set kid (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'kid') }
} }
module.exports = Account
...@@ -6,12 +6,12 @@ ...@@ -6,12 +6,12 @@
* @license AGPLv3 or later. * @license AGPLv3 or later.
*/ */
const jose = require('jose') import jose from 'jose'
const prepareRequest = require('bent') import prepareRequest from 'bent'
const types = require('../typedefs/lib/AcmeRequest') import types from '../typedefs/lib/AcmeRequest.js'
const Nonce = require('./Nonce') import Nonce from './Nonce.js'
const Throws = require('./util/Throws') import Throws from './util/Throws.js'
const log = require('./util/log') import log from './util/log.js'
const throws = new Throws({ const throws = new Throws({
[Symbol.for('AcmeRequest.classNotInitialisedError')]: [Symbol.for('AcmeRequest.classNotInitialisedError')]:
...@@ -28,33 +28,33 @@ const throws = new Throws({ ...@@ -28,33 +28,33 @@ const throws = new Throws({
* *
* @alias module:lib/AcmeRequest * @alias module:lib/AcmeRequest
*/ */
class AcmeRequest { export default class AcmeRequest {
static #initialised = false static initialised = false
static #directory = null static directory = null
static #accountIdentity = null static accountIdentity = null
static #nonce = null static nonce = null
static #account = null static __account = null
static initialise (directory = throws.ifMissing(), accountIdentity = throws.ifMissing()) { static initialise (directory = throws.ifMissing(), accountIdentity = throws.ifMissing()) {
this.#directory = directory this.directory = directory
this.#accountIdentity = accountIdentity this.accountIdentity = accountIdentity
this.#nonce = new Nonce(directory) this.nonce = new Nonce(directory)
this.#initialised = true this.initialised = true
} }
static uninitialise () { static uninitialise () {
this.#directory = null this.directory = null
this.#accountIdentity = null this.accountIdentity = null
this.#nonce = null this.nonce = null
this.#account = null this.__account = null
this.#initialised = false this.initialised = false
} }
static set account (_account = throws.ifMissing()) { this.#account = _account } static set account (_account = throws.ifMissing()) { this.__account = _account }
static get account () { return this.#account } static get account () { return this.__account }
constructor () { constructor () {
if (!AcmeRequest.#initialised) { if (!AcmeRequest.initialised) {
throws.error(Symbol.for('AcmeRequest.classNotInitialisedError')) throws.error(Symbol.for('AcmeRequest.classNotInitialisedError'))
} }
} }
...@@ -145,7 +145,7 @@ class AcmeRequest { ...@@ -145,7 +145,7 @@ class AcmeRequest {
// Always save the fresh nonce returned from API calls. // Always save the fresh nonce returned from API calls.
const freshNonce = response.headers['replay-nonce'] 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 // The response returned is the raw response object. Let’s consume
// it and return a more relevant response. // it and return a more relevant response.
...@@ -208,11 +208,11 @@ class AcmeRequest { ...@@ -208,11 +208,11 @@ class AcmeRequest {
// ===== the arguments array as the latter does not reflect default parameters. // ===== the arguments array as the latter does not reflect default parameters.
const originalRequestDetails = [command, payload, useKid, successCodes, url, nonce] const originalRequestDetails = [command, payload, useKid, successCodes, url, nonce]
url = url || AcmeRequest.#directory[`${command}Url`] url = url || AcmeRequest.directory[`${command}Url`]
const protectedHeader = { const protectedHeader = {
alg: 'RS256', alg: 'RS256',
nonce: nonce || await AcmeRequest.#nonce.get(), nonce: nonce || await AcmeRequest.nonce.get(),
url url
} }
...@@ -221,10 +221,10 @@ class AcmeRequest { ...@@ -221,10 +221,10 @@ class AcmeRequest {
protectedHeader.kid = AcmeRequest.account.kid protectedHeader.kid = AcmeRequest.account.kid
} else { } else {
// If we’re not using the kid, we must use the public JWK (see RFC 8555 § 6.2 Request Authentication) // 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 = { const httpsHeaders = {
'Content-Type': 'application/jose+json', 'Content-Type': 'application/jose+json',
...@@ -244,6 +244,3 @@ class AcmeRequest { ...@@ -244,6 +244,3 @@ class AcmeRequest {
} }
} }
} }
module.exports = AcmeRequest
...@@ -13,14 +13,14 @@ ...@@ -13,14 +13,14 @@
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
const EventEmitter = require('events') import EventEmitter from 'events'
const log = require('./util/log') import log from './util/log.js'
const AuthorisationRequest = require('./acme-requests/AuthorisationRequest') import AuthorisationRequest from './acme-requests/AuthorisationRequest.js'
const ReadyForChallengeValidationRequest = require('./acme-requests/ReadyForChallengeValidationRequest') import ReadyForChallengeValidationRequest from './acme-requests/ReadyForChallengeValidationRequest.js'
const HttpServer = require('./HttpServer') import HttpServer from './HttpServer.js'
const waitFor = require('./util/waitFor') import waitFor from './util/waitFor.js'
class Authorisation extends EventEmitter { export default class Authorisation extends EventEmitter {
// Async factory method. Use this to instantiate. // Async factory method. Use this to instantiate.
// TODO: add check to ensure factory method is used. // TODO: add check to ensure factory method is used.
...@@ -181,5 +181,3 @@ class Authorisation extends EventEmitter { ...@@ -181,5 +181,3 @@ class Authorisation extends EventEmitter {
} }
} }
} }
module.exports = Authorisation
...@@ -6,19 +6,19 @@ ...@@ -6,19 +6,19 @@
* @license AGPLv3 or later. * @license AGPLv3 or later.
*/ */
const fs = require('fs-extra') import fs from 'fs-extra'
const tls = require('tls') import tls from 'tls'
const util = require('util') import util from 'util'
const moment = require('moment') import moment from 'moment'
const log = require('./util/log') import log from './util/log.js'
const x509 = require('./x.509/rfc5280') import { Certificate as X509Certificate } from './x.509/rfc5280.js'
const Account = require('./Account') import Account from './Account.js'
const AccountIdentity = require('./identities/AccountIdentity') import AccountIdentity from './identities/AccountIdentity.js'
const Directory = require('./Directory') import Directory from './Directory.js'
const Order = require('./Order') import Order from './Order.js'
const CertificateIdentity = require('./identities/CertificateIdentity') import CertificateIdentity from './identities/CertificateIdentity.js'
const AcmeRequest = require('./AcmeRequest') import AcmeRequest from './AcmeRequest.js'
const Throws = require('./util/Throws') import Throws from './util/Throws.js'
const throws = new Throws({ const throws = new Throws({
// No custom errors are thrown by this class. // No custom errors are thrown by this class.
...@@ -30,7 +30,7 @@ const throws = new Throws({ ...@@ -30,7 +30,7 @@ const throws = new Throws({
* @alias module:lib/Certificate * @alias module:lib/Certificate
* @param {String[]} domains List of domains this certificate covers. * @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. * Get a SecureContext that can be used in an SNICallback.
* *
...@@ -367,7 +367,7 @@ class Certificate { ...@@ -367,7 +367,7 @@ class Certificate {
} }
parseDetails (certificatePem) { 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 serialNumber = certificate.serialNumber
const issuer = certificate.issuer.value[0][0].value.toString('utf-8').slice(2).trim() const issuer = certificate.issuer.value[0][0].value.toString('utf-8').slice(2).trim()
...@@ -417,5 +417,3 @@ class Certificate { ...@@ -417,5 +417,3 @@ class Certificate {
` `
} }
} }
module.exports = Certificate
...@@ -7,13 +7,13 @@ ...@@ -7,13 +7,13 @@
* @license AGPLv3 or later. * @license AGPLv3 or later.
*/ */
const os = require('os') import os from 'os'
const fs = require('fs-extra') import fs from 'fs-extra'
const path = require('path') import path from 'path'
const util = require('util') import util from 'util'
const crypto = require('crypto') import crypto from 'crypto'
const log = require('./util/log') import log from './util/log.js'
const Throws = require('./util/Throws') import Throws from './util/Throws.js'
// Custom errors thrown by this class. // Custom errors thrown by this class.
const throws = new Throws({ const throws = new Throws({
...@@ -30,7 +30,7 @@ function isAnArrayOfStrings (object) { ...@@ -30,7 +30,7 @@ function isAnArrayOfStrings (object) {
* @alias module:lib/Configuration * @alias module:lib/Configuration
* @hideconstructor * @hideconstructor
*/ */
class Configuration { export default class Configuration {
#server = null #server = null
#domains = null #domains = null
#settingsPath = null #settingsPath = null
...@@ -230,4 +230,3 @@ class Configuration { ...@@ -230,4 +230,3 @@ class Configuration {
` `
} }
} }
module.exports = Configuration
...@@ -11,25 +11,25 @@ ...@@ -11,25 +11,25 @@
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
const util = require('util') import util from 'util'
const prepareRequest = require('bent') import prepareRequest from 'bent'
const log = require('./util/log') import log from './util/log.js'
const Throws = require('./util/Throws') import Throws from './util/Throws.js'
const throws = new Throws() const throws = new Throws()
class Directory { export default class Directory {
#directory = null directory = null
#letsEncryptServer = null letsEncryptServer = null
#directoryRequest = null directoryRequest = null
// //
// Factory method access (async). // Factory method access (async).
// //
static #isBeingInstantiatedViaAsyncFactoryMethod = false static isBeingInstantiatedViaAsyncFactoryMethod = false