mirror of
https://git.coolaj86.com/coolaj86/acme-http-01-cli.js
synced 2025-03-13 18:50:37 +00:00
148 lines
5.0 KiB
JavaScript
148 lines
5.0 KiB
JavaScript
'use strict';
|
|
/*global Promise*/
|
|
|
|
var Challenge = module.exports;
|
|
|
|
// If your implementation needs config options, set them. Otherwise, don't bother (duh).
|
|
Challenge.create = function (config) {
|
|
|
|
var challenger = {};
|
|
|
|
// Note: normally you'd these right in the method body, but for the sake of
|
|
// "Table of Contents"-style documentation, I've pulled them out.
|
|
|
|
// Note: All of these methods can be synchronous, async, Promise, and callback-style
|
|
// (the calling functions check function.length and then Promisify accordingly)
|
|
|
|
// Called when it's tiem to set the challenge
|
|
challenger.set = function (opts, cb) {
|
|
return Challenge._setHttp(opts, cb);
|
|
};
|
|
|
|
// Called when it's time to remove the challenge
|
|
challenger.remove = function (opts) {
|
|
return Challenge._removeHttp(opts);
|
|
};
|
|
|
|
// Optional (only really useful for http)
|
|
// Called when the challenge needs to be retrieved
|
|
challenger.get = function (opts) {
|
|
return Challenge._getHttp(opts);
|
|
};
|
|
|
|
// Whatever you assign to 'options' will be merged into the incoming 'opts' beforehand
|
|
// (for convenience, so you don't have to do the if (!x) { x = y; } dance)
|
|
// (also, some defaults are layered, so it's good to set it any that you have)
|
|
challenger.options = { debug: config.debug };
|
|
|
|
return challenger;
|
|
};
|
|
|
|
// Prints the challenge URL and keyAuthorization to the screen
|
|
// (so that you can go set it and then continue)
|
|
// if you need per-run / per-domain options set them in approveDomains() and they'll be on 'args' here.
|
|
Challenge._setHttp = function (args, cb) {
|
|
// Note: You can receive cb and use that, or omit cb and return a Promise
|
|
|
|
var ch = args.challenge;
|
|
console.info("[ACME http-01 '" + ch.altname + "' CHALLENGE]");
|
|
console.info("You're about to receive the following HTTP request:");
|
|
console.info("");
|
|
// TODO let acme-v2 handle generating this url
|
|
console.info('\tGET http://' + ch.altname + '/.well-known/acme-challenge/' + ch.token);
|
|
console.info("");
|
|
console.info("The ACME server expects the following plaintext \"key authorization\" response:");
|
|
console.info("");
|
|
console.info("\t" + ch.keyAuthorization);
|
|
console.info("");
|
|
console.info("Depending on what you're implementing you'll either enter that again in the next step,"
|
|
+ " simulating a webserver response, or you'll actually copy it over to your true webserver");
|
|
if (args.debug) {
|
|
console.info("Debug Info:");
|
|
console.info("");
|
|
console.info(JSON.stringify(httpChallengeToJson(ch), null, 2).replace(/^/gm, '\t'));
|
|
console.info("");
|
|
}
|
|
console.info("[Press the ANY key to continue...]");
|
|
|
|
process.stdin.resume();
|
|
process.stdin.once('data', function () {
|
|
process.stdin.pause();
|
|
|
|
// The return value will checked. It must not be 'undefined'.
|
|
cb(null, null);
|
|
});
|
|
};
|
|
|
|
// might as well tell the user that whatever they were setting up has been checked
|
|
Challenge._removeHttp = function (args) {
|
|
// I'm not defining 'cb' here, which means I'll either have to return a value or a Promise
|
|
|
|
var ch = args.challenge;
|
|
console.info("");
|
|
console.info("[ACME http-01 '" + ch.altname + "' COMPLETE]: " + ch.status);
|
|
console.info("Challenge complete. You may now remove the challenge file:");
|
|
console.info("");
|
|
console.info('\thttp://' + ch.altname + '/.well-known/acme-challenge/' + ch.token);
|
|
console.info("");
|
|
|
|
// The return value will checked. It must not be 'undefined'.
|
|
return null;
|
|
};
|
|
|
|
Challenge._getHttp = function (args) {
|
|
var ch = args.challenge;
|
|
var altname = ch.altname || ch.identifier.value;
|
|
var hostname = ch.hostname || ch.identifier.value;
|
|
|
|
console.log(args);
|
|
if (ch._test || !Challenge._getCache[ch.token]) {
|
|
Challenge._getCache[ch.token] = true;
|
|
console.info("");
|
|
console.info("[ACME " + ch.type + " '" + altname + "' REQUEST]: " + ch.status);
|
|
console.info('GET http://' + hostname + '/.well-known/acme-challenge/' + ch.token);
|
|
console.info("(paste in the \"Key Authorization\" you received a moment ago to respond)");
|
|
process.stdout.write("> ");
|
|
}
|
|
|
|
// Using a promise here just to show that Promises are support
|
|
// (in fact, they're the default)
|
|
return new Promise(function (resolve, reject) {
|
|
process.stdin.resume();
|
|
process.stdin.once('error', reject);
|
|
process.stdin.once('data', function (chunk) {
|
|
process.stdin.pause();
|
|
|
|
var result = chunk.toString('utf8').trim();
|
|
try {
|
|
result = JSON.parse(result);
|
|
} catch(e) {
|
|
args.challenge.keyAuthorization = result;
|
|
result = args.challenge;
|
|
}
|
|
if (result.keyAuthorization) {
|
|
resolve(result);
|
|
return;
|
|
}
|
|
|
|
// The return value will checked. It must not be 'undefined'.
|
|
resolve(null);
|
|
});
|
|
});
|
|
};
|
|
// Because the ACME server will hammer us with requests, and that's confusing during a manual test:
|
|
Challenge._getCache = {};
|
|
|
|
function httpChallengeToJson(ch) {
|
|
return {
|
|
type: ch.type
|
|
, altname: ch.altname
|
|
, identifier: ch.identifier
|
|
, wildcard: false
|
|
, expires: ch.expires
|
|
, token: ch.token
|
|
, thumbprint: ch.thumbprint
|
|
, keyAuthorization: ch.keyAuthorization
|
|
};
|
|
}
|