This commit is contained in:
AJ ONeal 2016-10-06 16:42:38 -06:00
parent 3a1efa7cdf
commit 36b15be276
4 changed files with 226 additions and 25 deletions

2
app.js
View File

@ -21,7 +21,7 @@ module.exports = function (opts) {
if (opts.livereload) {
livereload = '<script src="//'
+ (res.getHeader('Host') || opts.servername).split(':')[0]
+ ':35729/livereload.js?snipver=1"></script>';
+ ':' + opts.lrPort + '/livereload.js?snipver=1"></script>';
addLen = livereload.length;
}

107
match-ips.js Normal file
View File

@ -0,0 +1,107 @@
'use strict';
var PromiseA = require('bluebird');
module.exports.match = function (servername, opts) {
return PromiseA.promisify(require('ipify'))().then(function (ip) {
var dns = PromiseA.promisifyAll(require('dns'));
opts.externalIps = [ { address: ip, family: 'IPv4' } ];
opts.ifaces = require('./local-ip.js').find({ externals: opts.externalIps });
opts.externalIfaces = Object.keys(opts.ifaces).reduce(function (all, iname) {
var iface = opts.ifaces[iname];
iface.ipv4.forEach(function (addr) {
if (addr.external) {
addr.iface = iname;
all.push(addr);
}
});
iface.ipv6.forEach(function (addr) {
if (addr.external) {
addr.iface = iname;
all.push(addr);
}
});
return all;
}, []).filter(Boolean);
function resolveIps(hostname) {
var allIps = [];
return PromiseA.all([
dns.resolve4Async(hostname).then(function (records) {
records.forEach(function (ip) {
allIps.push({
address: ip
, family: 'IPv4'
});
});
}, function () {})
, dns.resolve6Async(hostname).then(function (records) {
records.forEach(function (ip) {
allIps.push({
address: ip
, family: 'IPv6'
});
});
}, function () {})
]).then(function () {
return allIps;
});
}
function resolveIpsAndCnames(hostname) {
return PromiseA.all([
resolveIps(hostname)
, dns.resolveCnameAsync(hostname).then(function (records) {
return PromiseA.all(records.map(function (hostname) {
return resolveIps(hostname);
})).then(function (allIps) {
return allIps.reduce(function (all, ips) {
return all.concat(ips);
}, []);
});
}, function () {
return [];
})
]).then(function (ips) {
return ips.reduce(function (all, set) {
return all.concat(set);
}, []);
});
}
return resolveIpsAndCnames(servername).then(function (allIps) {
var matchingIps = [];
if (!allIps.length) {
console.warn("Could not resolve '" + servername + "'");
}
// { address, family }
allIps.some(function (ip) {
function match(addr) {
if (ip.address === addr.address) {
matchingIps.push(addr);
}
}
opts.externalIps.forEach(match);
// opts.externalIfaces.forEach(match);
Object.keys(opts.ifaces).forEach(function (iname) {
var iface = opts.ifaces[iname];
iface.ipv4.forEach(match);
iface.ipv6.forEach(match);
});
return matchingIps.length;
});
return matchingIps;
});
});
};

View File

@ -1,6 +1,6 @@
{
"name": "serve-https",
"version": "1.5.6",
"version": "2.0.0",
"description": "Serves HTTPS using TLS (SSL) certs for localhost.daplie.com - great for testing and development.",
"main": "serve.js",
"scripts": {
@ -38,8 +38,12 @@
},
"homepage": "https://github.com/Daplie/serve-https#readme",
"dependencies": {
"bluebird": "^3.4.6",
"finalhandler": "^0.4.0",
"ipify": "^1.1.0",
"le-challenge-dns": "^2.0.1",
"le-challenge-fs": "^2.0.5",
"letsencrypt-express": "^2.0.2",
"livereload": "^0.4.0",
"localhost.daplie.com-certificates": "^1.2.0",
"minimist": "^1.1.1",

136
serve.js
View File

@ -6,6 +6,7 @@ var https = require('https');
var http = require('http');
var fs = require('fs');
var path = require('path');
var DDNS = require('ddns-cli');
var portFallback = 8443;
var insecurePortFallback = 4080;
@ -49,13 +50,74 @@ function createInsecureServer(port, pubdir, opts) {
}
function createServer(port, pubdir, content, opts) {
function approveDomains(params, certs, cb) {
// This is where you check your database and associated
// email addresses with domains and agreements and such
var domains = params.domains;
//var p;
console.log('approveDomains');
console.log(domains);
// The domains being approved for the first time are listed in opts.domains
// Certs being renewed are listed in certs.altnames
if (certs) {
params.domains = certs.altnames;
//p = PromiseA.resolve();
}
else {
//params.email = opts.email;
if (!opts.agreeTos) {
console.error("You have not previously registered '" + domains + "' so you must specify --agree-tos to agree to both the Let's Encrypt and Daplie DNS terms of service.");
process.exit(1);
return;
}
params.agreeTos = opts.agreeTos;
}
// ddns.token(params.email, domains[0])
params.email = opts.email;
params.refreshToken = opts.refreshToken;
params.challengeType = 'dns-01';
params.cli = opts.argv;
cb(null, { options: params, certs: certs });
}
return new PromiseA(function (resolve) {
var server = https.createServer(opts);
var app = require('./app');
var directive = { public: pubdir, content: content, livereload: opts.livereload
, servername: opts.servername, expressApp: opts.expressApp };
// returns an instance of node-letsencrypt with additional helper methods
var webrootPath = require('os').tmpdir();
var leChallengeFs = require('le-challenge-fs').create({ webrootPath: webrootPath });
var leChallengeDns = require('le-challenge-dns').create({ ttl: 1 });
var lex = require('letsencrypt-express').create({
// set to https://acme-v01.api.letsencrypt.org/directory in production
server: opts.debug ? 'staging' : 'https://acme-v01.api.letsencrypt.org/directory'
// If you wish to replace the default plugins, you may do so here
//
, challenges: {
'http-01': leChallengeFs
, 'tls-sni-01': leChallengeFs
, 'dns-01': leChallengeDns
}
, challengeType: 'dns-01'
, store: require('le-store-certbot').create({ webrootPath: webrootPath })
, webrootPath: webrootPath
// You probably wouldn't need to replace the default sni handler
// See https://github.com/Daplie/le-sni-auto if you think you do
//, sni: require('le-sni-auto').create({})
, approveDomains: approveDomains
});
opts.httpsOptions.SNICallback = lex.httpsOptions.SNICallback;
var server = https.createServer(opts.httpsOptions);
server.on('error', function (err) {
if (opts.errorPort || opts.manualPort) {
showError(err, port);
@ -71,8 +133,9 @@ function createServer(port, pubdir, content, opts) {
server.listen(port, function () {
opts.port = port;
opts.lrPort = 35729;
var livereload = require('livereload');
var server2 = livereload.createServer({ https: opts });
var server2 = livereload.createServer({ https: opts.httpsOptions, port: opts.lrPort });
server2.watch(pubdir);
@ -90,6 +153,8 @@ function createServer(port, pubdir, content, opts) {
}
server.on('request', function (req, res) {
console.log('[' + req.method + '] ' + req.url);
if ('function' === typeof app) {
app(req, res);
return;
@ -118,22 +183,23 @@ function run() {
var tls = require('tls');
// letsencrypt
var email = argv.email;
var agreeTos = argv.agreeTos || argv['agree-tos'];
var cert = require('localhost.daplie.com-certificates');
var opts = {
key: cert.key
, cert: cert.cert
//, ca: cert.ca
, email: email
, agreeTos: agreeTos
agreeTos: argv.agreeTos || argv['agree-tos']
, debug: argv.debug
, email: argv.email
, httpsOptions: {
key: cert.key
, cert: cert.cert
//, ca: cert.ca
}
, argv: argv
};
var peerCa;
var p;
opts.SNICallback = function (servername, cb) {
cb(null, tls.createSecureContext(opts));
opts.httpsOptions.SNICallback = function (servername, cb) {
cb(null, tls.createSecureContext(opts.httpsOptions));
return;
};
@ -161,8 +227,8 @@ function run() {
argv.root = [argv.root];
}
opts.key = fs.readFileSync(argv.key);
opts.cert = fs.readFileSync(argv.cert);
opts.httpsOptions.key = fs.readFileSync(argv.key);
opts.httpsOptions.cert = fs.readFileSync(argv.cert);
// turn multiple-cert pemfile into array of cert strings
peerCa = argv.root.reduce(function (roots, fullpath) {
@ -181,9 +247,9 @@ function run() {
// TODO * `--verify /path/to/root.pem` require peers to present certificates from said authority
if (argv.verify) {
opts.ca = peerCa;
opts.requestCert = true;
opts.rejectUnauthorized = true;
opts.httpsOptions.ca = peerCa;
opts.httpsOptions.requestCert = true;
opts.httpsOptions.rejectUnauthorized = true;
}
if (argv['serve-root']) {
@ -208,6 +274,29 @@ function run() {
opts.expressApp = require(path.resolve(process.cwd(), argv['express-app']));
}
if (opts.email || opts.servername) {
if (!opts.agreeTos) {
console.warn("You may need to specify --agree-tos to agree to both the Let's Encrypt and Daplie DNS terms of service.");
}
if (!opts.email) {
// TODO store email in .ddnsrc.json
console.warn("You may need to specify --email to register with both the Let's Encrypt and Daplie DNS.");
}
p = DDNS.refreshToken({
email: opts.email
, silent: true
}, {
debug: false
, email: opts.argv.email
}).then(function (refreshToken) {
opts.refreshToken = refreshToken;
});
}
else {
p = PromiseA.resolve();
}
return p.then(function () {
return createServer(port, pubdir, content, opts).then(function () {
var msg;
var p;
@ -252,7 +341,7 @@ function run() {
return promise.then(function (matchingIps) {
if (matchingIps) {
if (!matchingIps.length) {
console.log("Neither the attached nor external interfaces match '" + argv.servername + "'");
console.info("Neither the attached nor external interfaces match '" + argv.servername + "'");
}
opts.matchingIps = matchingIps || [];
}
@ -292,11 +381,11 @@ function run() {
}
console.info('\t' + httpsUrl);
httpsUrl = 'https://[' + iface.ipv6[0].address + ']';
if (443 !== opts.port) {
httpsUrl += ':' + opts.port;
}
if (iface.ipv6.length) {
httpsUrl = 'https://[' + iface.ipv6[0].address + ']';
if (443 !== opts.port) {
httpsUrl += ':' + opts.port;
}
console.info('\t' + httpsUrl);
}
}
@ -305,6 +394,7 @@ function run() {
console.info('');
});
});
});
}
if (require.main === module) {