2020-01-11 22:33:19 -05:00
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 ({
2020-01-12 13:23:01 -05:00
maintainerEmail : WIKI . config . maintainerEmail ,
2020-01-11 22:33:19 -05:00
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 ({
2020-01-12 13:23:01 -05:00
subscriberEmail : WIKI . config . ssl . subscriberEmail ,
2020-01-11 22:33:19 -05:00
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
}
}
}
})
2022-10-04 21:20:54 +02:00
WIKI . logger . info ( `(LETSENCRYPT) New certificate received successfully: [ COMPLETED ]` )
2020-01-11 22:33:19 -05:00
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
}
}
}