mirror of
				https://git.coolaj86.com/coolaj86/acme-challenge-test.js.git
				synced 2025-11-04 02:22:49 +00:00 
			
		
		
		
	v3.3.0: include request, cleanup
This commit is contained in:
		
							parent
							
								
									9e0aedca41
								
							
						
					
					
						commit
						2a383ada48
					
				
							
								
								
									
										171
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										171
									
								
								index.js
									
									
									
									
									
								
							@ -1,6 +1,29 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
/*global Promise*/
 | 
			
		||||
 | 
			
		||||
if (process.version.match(/^v(\d+)/)[1] > 6) {
 | 
			
		||||
  console.warn();
 | 
			
		||||
  console.warn('#########################');
 | 
			
		||||
  console.warn('# Node v6 Compatibility #');
 | 
			
		||||
  console.warn('#########################');
 | 
			
		||||
  console.warn();
 | 
			
		||||
  console.warn("You're using node " + process.version);
 | 
			
		||||
  console.warn(
 | 
			
		||||
    'Please write node-v6 compatible JavaScript (not Babel/ECMAScript) and test with node v6.'
 | 
			
		||||
  );
 | 
			
		||||
  console.warn();
 | 
			
		||||
  console.warn(
 | 
			
		||||
    '(ACME.js and Greenlock.js are widely deployed in enterprise node v6 environments. The few node v6 bugs in Buffer and Promise are hotfixed by ACME.js in just a few lines of code)'
 | 
			
		||||
  );
 | 
			
		||||
  console.warn();
 | 
			
		||||
}
 | 
			
		||||
require('./node-v6-compat.js');
 | 
			
		||||
 | 
			
		||||
// Load _after_ node v6 compat
 | 
			
		||||
var crypto = require('crypto');
 | 
			
		||||
var promisify = require('util').promisify;
 | 
			
		||||
var request = require('@root/request');
 | 
			
		||||
request = promisify(request);
 | 
			
		||||
 | 
			
		||||
module.exports.create = function() {
 | 
			
		||||
  throw new Error(
 | 
			
		||||
@ -10,13 +33,12 @@ module.exports.create = function() {
 | 
			
		||||
 | 
			
		||||
// ignore all of this, it's just to normalize Promise vs node-style callback thunk vs synchronous
 | 
			
		||||
function promiseCheckAndCatch(obj, name) {
 | 
			
		||||
  var promisify = require('util').promisify;
 | 
			
		||||
  // don't loose this-ness, just in case that's important
 | 
			
		||||
  var fn = obj[name].bind(obj);
 | 
			
		||||
  var promiser;
 | 
			
		||||
 | 
			
		||||
  // function signature must match, or an error will be thrown
 | 
			
		||||
  if (1 === fn.length) {
 | 
			
		||||
  if (fn.length <= 1) {
 | 
			
		||||
    // wrap so that synchronous errors are caught (alsa handles synchronous results)
 | 
			
		||||
    promiser = function(opts) {
 | 
			
		||||
      return Promise.resolve().then(function() {
 | 
			
		||||
@ -27,13 +49,11 @@ function promiseCheckAndCatch(obj, name) {
 | 
			
		||||
    // wrap as a promise
 | 
			
		||||
    promiser = promisify(fn);
 | 
			
		||||
  } else {
 | 
			
		||||
    return Promise.reject(
 | 
			
		||||
      new Error(
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      "'challenge." +
 | 
			
		||||
        name +
 | 
			
		||||
        "' should accept either one argument, the options," +
 | 
			
		||||
        ' and return a Promise or accept two arguments, the options and a node-style callback thunk'
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -42,7 +62,7 @@ function promiseCheckAndCatch(obj, name) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        "'challenge.'" +
 | 
			
		||||
          name +
 | 
			
		||||
          "' should never return `undefined`. Please explicitly return null" +
 | 
			
		||||
          "' should never return `undefined`. Please explicitly `return null`" +
 | 
			
		||||
          " (or fix the place where a value should have been returned but wasn't)."
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
@ -79,6 +99,7 @@ function newZoneRegExp(zonename) {
 | 
			
		||||
  //  fooexample.com
 | 
			
		||||
  return new RegExp('(^|\\.)' + zonename.replace(/\./g, '\\.') + '$');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function pluckZone(zones, dnsHost) {
 | 
			
		||||
  return zones
 | 
			
		||||
    .filter(function(zonename) {
 | 
			
		||||
@ -95,25 +116,75 @@ function pluckZone(zones, dnsHost) {
 | 
			
		||||
// Here's the meat, where the tests are happening:
 | 
			
		||||
function testEach(type, domains, challenger) {
 | 
			
		||||
  var chr = wrapChallenger(type, challenger);
 | 
			
		||||
  var all = domains.map(function(d) {
 | 
			
		||||
    return { domain: d };
 | 
			
		||||
  });
 | 
			
		||||
  // We want the same rnd for all domains so that we catch errors like removing
 | 
			
		||||
  // the apex (bare) domain TXT record to when creating the wildcard record
 | 
			
		||||
  var rnd = crypto.randomBytes(2).toString('hex');
 | 
			
		||||
 | 
			
		||||
  console.info("Testing each of '%s'", domains.join(', '));
 | 
			
		||||
  return chr.zones({ dnsHosts: domains }).then(function(zones) {
 | 
			
		||||
 | 
			
		||||
  //
 | 
			
		||||
  // Zones
 | 
			
		||||
  //
 | 
			
		||||
  // Get ALL zones for all records on the certificate
 | 
			
		||||
  //
 | 
			
		||||
  return chr
 | 
			
		||||
    .init({ request: request })
 | 
			
		||||
    .then(function() {
 | 
			
		||||
      return chr.zones({ request: request, dnsHosts: domains });
 | 
			
		||||
    })
 | 
			
		||||
    .then(function(zones) {
 | 
			
		||||
      var all = domains.map(function(domain) {
 | 
			
		||||
        var zone = pluckZone(zones, domain);
 | 
			
		||||
        return {
 | 
			
		||||
          domain: domain,
 | 
			
		||||
          challenge: fakeChallenge(type, zone, domain, rnd),
 | 
			
		||||
          request: request
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // resolving for the sake of same indentation / scope
 | 
			
		||||
      return Promise.resolve()
 | 
			
		||||
        .then(function() {
 | 
			
		||||
          return mapAsync(all, function(opts) {
 | 
			
		||||
      var zone = pluckZone(zones, opts.domain);
 | 
			
		||||
      opts.challenge = fakeChallenge(type, zone, opts.domain, rnd);
 | 
			
		||||
            return set(chr, opts);
 | 
			
		||||
          });
 | 
			
		||||
        })
 | 
			
		||||
        .then(function() {
 | 
			
		||||
          return mapAsync(all, function(opts) {
 | 
			
		||||
            return check(chr, opts);
 | 
			
		||||
          });
 | 
			
		||||
        })
 | 
			
		||||
        .then(function() {
 | 
			
		||||
          return mapAsync(all, function(opts) {
 | 
			
		||||
            return remove(chr, opts).then(function() {
 | 
			
		||||
              console.info("PASS '%s'", opts.domain);
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
        })
 | 
			
		||||
        .then(function() {
 | 
			
		||||
          console.info();
 | 
			
		||||
          console.info('It looks like the soft tests all passed.');
 | 
			
		||||
          console.log('It is highly likely that your plugin is correct.');
 | 
			
		||||
          console.log(
 | 
			
		||||
            'Now go test with Greenlock.js and/or ACME.js to be sure.'
 | 
			
		||||
          );
 | 
			
		||||
          console.info();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function set(chr, opts) {
 | 
			
		||||
  var ch = opts.challenge;
 | 
			
		||||
  if ('http-01' === ch.type && ch.wildname) {
 | 
			
		||||
    throw new Error('http-01 cannot be used for wildcard domains');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
      // The first time we just check it against itself
 | 
			
		||||
      // this will cause the prompt to appear
 | 
			
		||||
  //
 | 
			
		||||
  // Set
 | 
			
		||||
  //
 | 
			
		||||
  // Add (not replace) a TXT for the domain
 | 
			
		||||
  //
 | 
			
		||||
  return chr.set(opts).then(function() {
 | 
			
		||||
        // this will cause the final completion message to appear
 | 
			
		||||
    // _test is used by the manual cli reference implementations
 | 
			
		||||
    var query = { type: ch.type, /*debug*/ status: ch.status, _test: true };
 | 
			
		||||
    if ('http-01' === ch.type) {
 | 
			
		||||
@ -138,11 +209,24 @@ function testEach(type, domains, challenger) {
 | 
			
		||||
    opts.query = query;
 | 
			
		||||
    return opts;
 | 
			
		||||
  });
 | 
			
		||||
    })
 | 
			
		||||
      .then(function(all) {
 | 
			
		||||
        return mapAsync(all, function(opts) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function check(chr, opts) {
 | 
			
		||||
  var ch = opts.challenge;
 | 
			
		||||
          return chr.get({ challenge: opts.query }).then(function(secret) {
 | 
			
		||||
 | 
			
		||||
  //
 | 
			
		||||
  // Get
 | 
			
		||||
  //
 | 
			
		||||
  // Check that ONE of the relevant TXT records matches
 | 
			
		||||
  //
 | 
			
		||||
  return chr
 | 
			
		||||
    .get({ request: request, challenge: opts.query })
 | 
			
		||||
    .then(function(secret) {
 | 
			
		||||
      if (!secret) {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
          '`secret` should be an object containing `keyAuthorization` or `dnsAuthorization`'
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      if ('string' === typeof secret) {
 | 
			
		||||
        console.info(
 | 
			
		||||
          'secret was passed as a string, which works historically, but should be an object instead:'
 | 
			
		||||
@ -205,10 +289,14 @@ function testEach(type, domains, challenger) {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
        });
 | 
			
		||||
      })
 | 
			
		||||
      .then(function() {
 | 
			
		||||
        return mapAsync(all, function(opts) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function remove(chr, opts) {
 | 
			
		||||
  //
 | 
			
		||||
  // Remove
 | 
			
		||||
  //
 | 
			
		||||
  // Delete ONLY the SINGLE relevant TXT record
 | 
			
		||||
  //
 | 
			
		||||
  return chr.remove(opts).then(function() {
 | 
			
		||||
    return chr.get(opts).then(function(result) {
 | 
			
		||||
      if (result) {
 | 
			
		||||
@ -221,29 +309,8 @@ function testEach(type, domains, challenger) {
 | 
			
		||||
          'challenge.get() should return null when the value is not set'
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
              console.info("PASS '%s'", opts.domain);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
        });
 | 
			
		||||
      })
 | 
			
		||||
      .then(function() {
 | 
			
		||||
        console.info('All soft tests: PASS');
 | 
			
		||||
        console.warn(
 | 
			
		||||
          'Hard tests (actually checking http URLs and dns records) is implemented in acme-v2.'
 | 
			
		||||
        );
 | 
			
		||||
        console.warn(
 | 
			
		||||
          "We'll copy them over here as well, but that's a TODO for next week."
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function testZone(type, zone, challenger) {
 | 
			
		||||
  var domains = [zone, 'foo.' + zone];
 | 
			
		||||
  if ('dns-01' === type) {
 | 
			
		||||
    domains.push('*.foo.' + zone);
 | 
			
		||||
  }
 | 
			
		||||
  return testEach(type, domains, challenger);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function wrapChallenger(type, challenger) {
 | 
			
		||||
@ -278,7 +345,14 @@ function wrapChallenger(type, challenger) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var init = challenger.init;
 | 
			
		||||
  if ('function' !== typeof init) {
 | 
			
		||||
    init = function(opts) {
 | 
			
		||||
      return null;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  return {
 | 
			
		||||
    init: promiseCheckAndCatch(challenger, 'init'),
 | 
			
		||||
    zones: zones,
 | 
			
		||||
    set: promiseCheckAndCatch(challenger, 'set'),
 | 
			
		||||
    get: promiseCheckAndCatch(challenger, 'get'),
 | 
			
		||||
@ -309,6 +383,7 @@ function fakeChallenge(type, zone, altname, rnd) {
 | 
			
		||||
    thumbprint: thumb,
 | 
			
		||||
    keyAuthorization: keyAuth,
 | 
			
		||||
    url: null, // completed below
 | 
			
		||||
    // we create a random record to prevent self cache-poisoning
 | 
			
		||||
    dnsHost: '_' + rnd.slice(0, 2) + '-acme-challenge-' + rnd.slice(2) + '.', // completed below
 | 
			
		||||
    dnsAuthorization: dnsAuth,
 | 
			
		||||
    altname: altname,
 | 
			
		||||
@ -332,6 +407,14 @@ function fakeChallenge(type, zone, altname, rnd) {
 | 
			
		||||
  return challenge;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function testZone(type, zone, challenger) {
 | 
			
		||||
  var domains = [zone, 'foo.' + zone];
 | 
			
		||||
  if ('dns-01' === type) {
 | 
			
		||||
    domains.push('*.foo.' + zone);
 | 
			
		||||
  }
 | 
			
		||||
  return testEach(type, domains, challenger);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function testRecord(type, altname, challenger) {
 | 
			
		||||
  return testEach(type, [altname], challenger);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								node-v6-compat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								node-v6-compat.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
function requireBluebird() {
 | 
			
		||||
	try {
 | 
			
		||||
		return require("bluebird");
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		console.error("");
 | 
			
		||||
		console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support.");
 | 
			
		||||
		console.error("EASY FIX: `npm install --save bluebird`");
 | 
			
		||||
		console.error("");
 | 
			
		||||
		throw e;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if ("undefined" === typeof Promise) {
 | 
			
		||||
	global.Promise = requireBluebird();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if ("function" !== typeof require("util").promisify) {
 | 
			
		||||
	require("util").promisify = requireBluebird().promisify;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (!console.debug) {
 | 
			
		||||
	console.debug = console.log;
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "acme-challenge-test",
 | 
			
		||||
  "version": "3.2.1",
 | 
			
		||||
  "version": "3.3.0",
 | 
			
		||||
  "description": "ACME challenge test harness for Let's Encrypt integrations. Any `acme-http-01-` or `acme-dns-01-` challenge strategy or Greenlock plugin should be able to pass these tests.",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "homepage": "https://git.rootprojects.org/root/acme-challenge-test.js",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user