'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 }; }