mirror of
				https://git.coolaj86.com/coolaj86/acme-http-01-cli.js
				synced 2025-11-04 10:22:47 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			145 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			145 lines
		
	
	
		
			4.9 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)
 | 
						|
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]");
 | 
						|
  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;
 | 
						|
};
 | 
						|
 | 
						|
// nothing to do here, that's why it's manual
 | 
						|
Challenge._getHttp = function (args) {
 | 
						|
  // Note: Here I'm You can receive cb and use that, or omit cb and return a Promise
 | 
						|
  var ch = args.challenge;
 | 
						|
 | 
						|
  if (!Challenge._getCache[ch.altname + ':' + ch.token]) {
 | 
						|
    Challenge._getCache[ch.altname + ':' + ch.token] = true;
 | 
						|
    console.info("");
 | 
						|
    console.info('GET http://' + ch.altname + '/.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');
 | 
						|
      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
 | 
						|
  };
 | 
						|
}
 |