const ACME = require ( 'acme' )
const Keypairs = require ( '@root/keypairs' )
const _ = require ( 'lodash' )
const moment = require ( 'moment' )
const CSR = require ( '@root/csr' )
const PEM = require ( '@root/pem' )
// eslint-disable-next-line node/no-deprecated-api
const punycode = require ( 'punycode' )
/* global WIKI */
module . exports = {
apiDirectory : WIKI . dev ? 'https://acme-staging-v02.api.letsencrypt.org/directory' : 'https://acme-v02.api.letsencrypt.org/directory' ,
acme : null ,
async init ( ) {
if ( ! _ . get ( WIKI . config , 'letsencrypt.payload' , false ) ) {
await this . requestCertificate ( )
} else if ( WIKI . config . letsencrypt . domain !== WIKI . config . ssl . domain ) {
WIKI . logger . info ( ` (LETSENCRYPT) Domain has changed. Requesting new certificates... ` )
await this . requestCertificate ( )
} else if ( moment ( WIKI . config . letsencrypt . payload . expires ) . isSameOrBefore ( moment ( ) . add ( 5 , 'days' ) ) ) {
WIKI . logger . info ( ` (LETSENCRYPT) Certificate is about to or has expired, requesting a new one... ` )
await this . requestCertificate ( )
} else {
WIKI . logger . info ( ` (LETSENCRYPT) Using existing certificate for ${ WIKI . config . ssl . domain } , expires on ${ WIKI . config . letsencrypt . payload . expires } : [ OK ] ` )
}
WIKI . config . ssl . format = 'pem'
WIKI . config . ssl . inline = true
WIKI . config . ssl . key = WIKI . config . letsencrypt . serverKey
WIKI . config . ssl . cert = WIKI . config . letsencrypt . payload . cert + '\n' + WIKI . config . letsencrypt . payload . chain
WIKI . config . ssl . passphrase = null
WIKI . config . ssl . dhparam = null
} ,
async requestCertificate ( ) {
try {
WIKI . logger . info ( ` (LETSENCRYPT) Initializing Let's Encrypt client... ` )
this . acme = ACME . create ( {
maintainerEmail : WIKI . config . maintainerEmail ,
packageAgent : ` wikijs/ ${ WIKI . version } ` ,
notify : ( ev , msg ) => {
if ( _ . includes ( [ 'warning' , 'error' ] , ev ) ) {
WIKI . logger . warn ( ` ${ ev } : ${ msg } ` )
} else {
WIKI . logger . debug ( ` ${ ev } : ${ JSON . stringify ( msg ) } ` )
}
}
} )
await this . acme . init ( this . apiDirectory )
// -> Create ACME Subscriber account
if ( ! _ . get ( WIKI . config , 'letsencrypt.account' , false ) ) {
WIKI . logger . info ( ` (LETSENCRYPT) Setting up account for the first time... ` )
const accountKeypair = await Keypairs . generate ( { kty : 'EC' , format : 'jwk' } )
const account = await this . acme . accounts . create ( {
subscriberEmail : WIKI . config . ssl . subscriberEmail ,
agreeToTerms : true ,
accountKey : accountKeypair . private
} )
WIKI . config . letsencrypt = {
accountKeypair : accountKeypair ,
account : account ,
domain : WIKI . config . ssl . domain
}
await WIKI . configSvc . saveToDb ( [ 'letsencrypt' ] )
WIKI . logger . info ( ` (LETSENCRYPT) Account was setup successfully [ OK ] ` )
}
// -> Create Server Keypair
if ( ! WIKI . config . letsencrypt . serverKey ) {
WIKI . logger . info ( ` (LETSENCRYPT) Generating server keypairs... ` )
const serverKeypair = await Keypairs . generate ( { kty : 'RSA' , format : 'jwk' } )
WIKI . config . letsencrypt . serverKey = await Keypairs . export ( { jwk : serverKeypair . private } )
WIKI . logger . info ( ` (LETSENCRYPT) Server keypairs generated successfully [ OK ] ` )
}
// -> Create CSR
WIKI . logger . info ( ` (LETSENCRYPT) Generating certificate signing request (CSR)... ` )
const domains = [ punycode . toASCII ( WIKI . config . ssl . domain ) ]
const serverKey = await Keypairs . import ( { pem : WIKI . config . letsencrypt . serverKey } )
const csrDer = await CSR . csr ( { jwk : serverKey , domains , encoding : 'der' } )
const csr = PEM . packBlock ( { type : 'CERTIFICATE REQUEST' , bytes : csrDer } )
WIKI . logger . info ( ` (LETSENCRYPT) CSR generated successfully [ OK ] ` )
// -> Verify Domain + Get Certificate
WIKI . logger . info ( ` (LETSENCRYPT) Requesting certificate from Let's Encrypt... ` )
const certResp = await this . acme . certificates . create ( {
account : WIKI . config . letsencrypt . account ,
accountKey : WIKI . config . letsencrypt . accountKeypair . private ,
csr ,
domains ,
challenges : {
'http-01' : {
init ( ) { } ,
set ( data ) {
WIKI . logger . info ( ` (LETSENCRYPT) Setting HTTP challenge for ${ data . challenge . hostname } : [ READY ] ` )
WIKI . config . letsencrypt . challenge = data . challenge
WIKI . logger . info ( ` (LETSENCRYPT) Waiting for challenge to complete... ` )
return null // <- this is needed, cannot be undefined
} ,
get ( data ) {
return WIKI . config . letsencrypt . challenge
} ,
async remove ( data ) {
WIKI . logger . info ( ` (LETSENCRYPT) Removing HTTP challenge: [ OK ] ` )
WIKI . config . letsencrypt . challenge = null
return null // <- this is needed, cannot be undefined
}
}
}
} )
WIKI . logger . info ( ` (LETSENCRYPT) New certificate received successfully: [ COMPLETED ] ` )
WIKI . config . letsencrypt . payload = certResp
WIKI . config . letsencrypt . domain = WIKI . config . ssl . domain
await WIKI . configSvc . saveToDb ( [ 'letsencrypt' ] )
} catch ( err ) {
WIKI . logger . warn ( ` (LETSENCRYPT) ${ err } ` )
throw err
}
}
}