'use strict'; const config = require('wild-config'); const log = require('npmlog'); const Joi = require('joi'); const AcmeChallenge = require('../acme/acme-challenge'); const { asyncifyJson, validationErrors, getHostname, normalizeIp } = require('../tools'); module.exports = (db, server, routeOptions) => { routeOptions = routeOptions || {}; const acmeChallenge = AcmeChallenge.create({ db: db.database }); server.get( { name: 'acmeToken', path: '/.well-known/acme-challenge/:token' }, asyncifyJson(async (req, res, next) => { res.charSet('utf-8'); const ip = normalizeIp(res.socket.remoteAddress); const domain = getHostname(req); const schema = Joi.object().keys({ token: Joi.string().empty('').max(256).required() }); const result = schema.validate(req.params, { abortEarly: false, convert: true, allowUnknown: true }); if (result.error) { res.status(400); res.json({ error: result.error.message, code: 'InputValidationError', details: validationErrors(result) }); return next(); } const token = result.value.token; let challenge; try { challenge = await acmeChallenge.get({ challenge: { token, identifier: { value: domain } } }); } catch (err) { log.error('ACME', `Error verifying challenge ${domain}: ${token} (${ip}, ${req.url}) ${err.message}`); let resErr = new Error(`Failed to verify authentication token`); resErr.responseCode = 500; throw resErr; } if (!challenge || !challenge.keyAuthorization) { log.error('ACME', `Unknown challenge ${domain}: ${token} (${ip}, ${req.url})`); let err = new Error(`Unknown challenge`); err.responseCode = 404; throw err; } res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end(challenge.keyAuthorization); }) ); if (!routeOptions.disableRedirect) { server.on('NotFound', (req, res, err, cb) => { res.redirect(302, config.acme.agent.redirect, cb); }); } };