Files
Kenton Varda 8252c9a846 Revert "Use es6 modules in the Sandcats ACME.js plugin."
This reverts commit 76af6368e4.

This doesn't work because the module is dynamically loaded with `require()`. Dynamic `import()` doesn't seem to work either -- it says the module is not found. I think Meteor has special requirements on dynamic imports. I am just going to revert to what we had previously tested.
2020-06-06 14:04:01 -05:00

133 lines
3.6 KiB
JavaScript

'use strict';
const https = require("https");
const querystring = require("querystring");
const dns = require("dns");
class Challenge {
constructor(options) {
this.key = options.key;
this.cert = options.cert;
this.hostname = options.hostname;
this.bindIp = options.bindIp;
this.isTest = options.isTest;
// Set `propagationDelay` for ACME.js.
//
// The DNS entries should be immediately available... unless it happens that someone had just
// queried them before they were modified, in which case they could be cached for 60-90
// seconds, but that's unusual and we don't want to wait that long every time we issue a cert.
this.propagationDelay = 0;
}
async init({request}) {
return null;
}
async zones({dnsHosts}) {
return [this.hostname + ".sandcats.io"];
}
async set({challenge: {dnsAuthorization, dnsHost}}) {
if (dnsHost != "_acme-challenge." + this.hostname + ".sandcats.io") {
throw new Error("Can only set records for _acme-challenge, not: " + dnsHost);
}
return this._request({
rawHostname: this.hostname,
value: dnsAuthorization
});
}
async get({challenge: {dnsAuthorization, dnsHost}}) {
if (this.isTest) {
// The test driver tends to run into problems with caching that don't appear in real-world
// usage.
console.log("waiting 30 seconds for propagation");
await new Promise(resolve => setTimeout(resolve, 30000));
}
if (dnsHost != "_acme-challenge." + this.hostname + ".sandcats.io") {
throw new Error("Can only set records for _acme-challenge, not: " + dnsHost);
}
let results = await new Promise((resolve, reject) => {
dns.resolveTxt("_acme-challenge." + this.hostname + ".sandcats.io", (err, result) => {
if (err) {
if (err.code == dns.NOTFOUND || err.code == dns.NODATA) {
resolve([]);
} else {
reject(err);
}
} else {
resolve(result);
}
});
});
let records = results.map(chunks => chunks.join(""));
let match = records.filter(record => record === dnsAuthorization);
if (match[0]) {
return {dnsAuthorization: match[0]};
} else {
return null;
}
}
async remove() {
// HACK: We always remove all challenges when asked to remove anything, because in practice
// ACME.js only calls remove() when it's time to remove everything, and I didn't want to
// build an API for removing individual challenges.
return this._request({
rawHostname: this.hostname
});
}
async _request(postData) {
let options = {
hostname: "sandcats.io",
path: "/acme-challenge",
method: "POST",
agent: false,
key: this.key,
cert: this.cert,
headers: {
"X-Sand": "cats",
"Content-Type": "application/x-www-form-urlencoded",
},
};
if (this.bindIp) {
options.localAddress = this.bindIp;
}
let postDataString = querystring.stringify(postData);
let response = await new Promise((resolve, reject) => {
const req = https.request(options, resolve);
req.write(postDataString);
req.end();
req.on("error", reject);
});
let responseBody = "";
response.on("data", chunk => { responseBody += chunk; });
await new Promise((resolve, reject) => {
response.on("end", resolve);
response.on("error", reject);
});
if (response.statusCode != 200) {
throw new Error("sandcats request failed: " + response.statusCode + ": " + responseBody);
}
return null;
}
};
module.exports = {
create(options) {
return new Challenge(options);
}
};