From 36b15be2761888c779575a0cef5343f4f7232921 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 6 Oct 2016 16:42:38 -0600 Subject: [PATCH 01/19] updates --- app.js | 2 +- match-ips.js | 107 ++++++++++++++++++++++++++++++++++++++++ package.json | 6 ++- serve.js | 136 ++++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 226 insertions(+), 25 deletions(-) create mode 100644 match-ips.js diff --git a/app.js b/app.js index bd05672..505c824 100644 --- a/app.js +++ b/app.js @@ -21,7 +21,7 @@ module.exports = function (opts) { if (opts.livereload) { livereload = ''; + + ':' + opts.lrPort + '/livereload.js?snipver=1">'; addLen = livereload.length; } diff --git a/match-ips.js b/match-ips.js new file mode 100644 index 0000000..061e2d4 --- /dev/null +++ b/match-ips.js @@ -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; + }); + }); +}; diff --git a/package.json b/package.json index c94d28a..9ab5141 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/serve.js b/serve.js index 0cfe08c..e4cb91a 100755 --- a/serve.js +++ b/serve.js @@ -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) { From 992d0a609ac3330c23e6a11106fb4bf1ca24130c Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 7 Oct 2016 10:44:25 -0600 Subject: [PATCH 02/19] add tunnel arg --- serve.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/serve.js b/serve.js index d4d6d33..59ef2ff 100755 --- a/serve.js +++ b/serve.js @@ -278,6 +278,9 @@ function run() { if (argv.p || argv.port || argv._[0]) { opts.manualPort = true; } + if (argv.t || argv.tunnel) { + opts.tunnel = true; + } if (argv.i || argv['insecure-port']) { opts.manualInsecurePort = true; } From fa0990b02f96b4f86b908de112b88748536956e4 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 11 Oct 2016 13:41:29 -0600 Subject: [PATCH 03/19] add some tunnel support --- lib/tunnel.js | 30 ++++++++++++++++++++++++++++++ serve.js | 5 ++++- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 lib/tunnel.js diff --git a/lib/tunnel.js b/lib/tunnel.js new file mode 100644 index 0000000..ea09873 --- /dev/null +++ b/lib/tunnel.js @@ -0,0 +1,30 @@ +'use strict'; + +module.exports.create = function (opts/*, servers*/) { + // servers = { plainserver, server } + var tunnel = require('daplie-tunnel'); + var stunnel = require('stunnel'); + + + return tunnel.token({ + refreshToken: opts.refreshToken + , email: opts.email + , domains: [ opts.servername ] + }).then(function (result) { + // { jwt, tunnelUrl } + stunnel.connect({ + token: result.jwt + , stunneld: result.tunnelUrl + , locals: [ + { protocol: 'https' + , hostname: opts.servername + , port: opts.port + } + , { protocol: 'http' + , hostname: opts.servername + , port: opts.insecurePort || opts.port + } + ] + }); + }); +}; diff --git a/serve.js b/serve.js index a2ec860..b4f7fcd 100755 --- a/serve.js +++ b/serve.js @@ -181,7 +181,7 @@ function createServer(port, pubdir, content, opts) { server.on('request', function (req, res) { console.log('[' + req.method + '] ' + req.url); - if (!req.socket.encrypted) { + if (!req.socket.encrypted && !/\/\.well-known\/acme-challenge\//.test(req.url)) { opts.redirectApp(req, res); return; } @@ -424,6 +424,9 @@ function run() { } }); } + else { + require('./lib/tunnel.js').create(opts); + } Object.keys(opts.ifaces).forEach(function (iname) { var iface = opts.ifaces[iname]; From cd2fda3f2bfa7c862143e66758bb43121a9c8c6d Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 11 Oct 2016 17:20:10 -0600 Subject: [PATCH 04/19] partial tunnel integration --- lib/tunnel.js | 114 ++++++++++++++++++++++++++++++++++++++++++++++++-- serve.js | 34 +++++++++++---- 2 files changed, 137 insertions(+), 11 deletions(-) diff --git a/lib/tunnel.js b/lib/tunnel.js index ea09873..dc1010f 100644 --- a/lib/tunnel.js +++ b/lib/tunnel.js @@ -1,20 +1,75 @@ 'use strict'; -module.exports.create = function (opts/*, servers*/) { +module.exports.create = function (opts, servers) { // servers = { plainserver, server } - var tunnel = require('daplie-tunnel'); + var Oauth3 = require('oauth3-cli'); + var Tunnel = require('daplie-tunnel').create({ + Oauth3: Oauth3 + , PromiseA: opts.PromiseA + , CLI: { + init: function (/*rs, ws, state, options*/) { + // noop + } + } + }).Tunnel; var stunnel = require('stunnel'); + var killcount = 0; + /* + var Dup = { + write: function (chunk, encoding, cb) { + this.__my_socket.push(chunk, encoding); + cb(); + } + , read: function (size) { + var x = this.__my_socket.read(size); + if (x) { this.push(x); } + } + , setTimeout: function () { + console.log('TODO implement setTimeout on Duplex'); + } + }; - return tunnel.token({ + var httpServer = require('http').createServer(function (req, res) { + console.log('req.socket.encrypted', req.socket.encrypted); + res.end('Hello, tunneled World!'); + }); + + var tlsServer = require('tls').createServer(opts.httpsOptions, function (tlsSocket) { + console.log('tls connection'); + // things get a little messed up here + httpServer.emit('connection', tlsSocket); + + // try again + //servers.server.emit('connection', tlsSocket); + }); + */ + + process.on('SIGINT', function () { + killcount += 1; + console.log('[quit] closing http and https servers'); + if (killcount >= 3) { + process.exit(1); + } + if (servers.server) { + servers.server.close(); + } + if (servers.insecureServer) { + servers.insecureServer.close(); + } + }); + + return Tunnel.token({ refreshToken: opts.refreshToken , email: opts.email , domains: [ opts.servername ] }).then(function (result) { // { jwt, tunnelUrl } - stunnel.connect({ + return stunnel.connect({ token: result.jwt , stunneld: result.tunnelUrl + // XXX TODO BUG // this is just for testing + , insecure: /*opts.insecure*/ true , locals: [ { protocol: 'https' , hostname: opts.servername @@ -25,6 +80,57 @@ module.exports.create = function (opts/*, servers*/) { , port: opts.insecurePort || opts.port } ] + // a simple passthru is proving to not be so simple + , net: require('net') /* + { + createConnection: function (info, cb) { + // data is the hello packet / first chunk + // info = { data, servername, port, host, remoteAddress: { family, address, port } } + + var myDuplex = new (require('stream').Duplex)(); + var myDuplex2 = new (require('stream').Duplex)(); + // duplex = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] }; + + myDuplex2.__my_socket = myDuplex; + myDuplex.__my_socket = myDuplex2; + + myDuplex2._write = Dup.write; + myDuplex2._read = Dup.read; + + myDuplex._write = Dup.write; + myDuplex._read = Dup.read; + + myDuplex.remoteFamily = info.remoteFamily; + myDuplex.remoteAddress = info.remoteAddress; + myDuplex.remotePort = info.remotePort; + + // socket.local{Family,Address,Port} + myDuplex.localFamily = 'IPv4'; + myDuplex.localAddress = '127.0.01'; + myDuplex.localPort = info.port; + + myDuplex.setTimeout = Dup.setTimeout; + + // this doesn't seem to work so well + //servers.server.emit('connection', myDuplex); + + // try a little more manual wrapping / unwrapping + var firstByte = info.data[0]; + if (firstByte < 32 || firstByte >= 127) { + tlsServer.emit('connection', myDuplex); + } + else { + httpServer.emit('connection', myDuplex); + } + + if (cb) { + process.nextTick(cb); + } + + return myDuplex2; + } + } + //*/ }); }); }; diff --git a/serve.js b/serve.js index b4f7fcd..1c6e082 100755 --- a/serve.js +++ b/serve.js @@ -3,6 +3,7 @@ //var PromiseA = global.Promise; var PromiseA = require('bluebird'); +var tls = require('tls'); var https = require('httpolyglot'); var http = require('http'); var fs = require('fs'); @@ -107,6 +108,7 @@ function createServer(port, pubdir, content, opts) { // 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 leChallengeSni = require('le-challenge-sni').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 @@ -116,10 +118,10 @@ function createServer(port, pubdir, content, opts) { // , challenges: { 'http-01': leChallengeFs - , 'tls-sni-01': leChallengeFs + , 'tls-sni-01': leChallengeFs // leChallengeSni , 'dns-01': leChallengeDns } - , challengeType: 'dns-01' + , challengeType: (opts.tunnel ? 'http-01' : 'dns-01') , store: require('le-store-certbot').create({ webrootPath: webrootPath }) , webrootPath: webrootPath @@ -129,7 +131,20 @@ function createServer(port, pubdir, content, opts) { , approveDomains: approveDomains }); - opts.httpsOptions.SNICallback = lex.httpsOptions.SNICallback; + var secureContext; + opts.httpsOptions.SNICallback = function (servername, cb ) { + console.log('[https] servername', servername); + + if ('localhost.daplie.com' === servername) { + if (!secureContext) { + secureContext = tls.createSecureContext(opts.httpsOptions); + } + cb(null, secureContext); + return; + } + + lex.httpsOptions.SNICallback(servername, cb); + }; var server = https.createServer(opts.httpsOptions); server.on('error', function (err) { @@ -211,7 +226,6 @@ function run() { var pubdir = path.resolve(argv.d || argv._[1] || process.cwd()); var content = argv.c; var letsencryptHost = argv['letsencrypt-certs']; - var tls = require('tls'); if (argv.V || argv.version || argv.v) { if (argv.v) { @@ -239,6 +253,7 @@ function run() { var peerCa; var p; + opts.PromiseA = PromiseA; opts.httpsOptions.SNICallback = function (servername, cb) { if (!secureContext) { secureContext = tls.createSecureContext(opts.httpsOptions); @@ -354,7 +369,7 @@ function run() { }; opts.redirectApp = require('redirect-https')(opts.redirectOptions); - return createServer(port, pubdir, content, opts).then(function () { + return createServer(port, pubdir, content, opts).then(function (servers) { var msg; var p; var httpsUrl; @@ -424,8 +439,13 @@ function run() { } }); } - else { - require('./lib/tunnel.js').create(opts); + else if (!opts.tunnel) { + console.info("External IP address does not match local IP address."); + console.info("Use --tunnel to allow the people of the Internet to access your server."); + } + + if (opts.tunnel) { + require('./lib/tunnel.js').create(opts, servers); } Object.keys(opts.ifaces).forEach(function (iname) { From 7634414d824d047aacfb6125b9fd4515a40b1e82 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 12 Oct 2016 18:22:43 -0600 Subject: [PATCH 05/19] cleanup to lib/ --- local-ip.js => lib/local-ip.js | 0 match-ips.js => lib/match-ips.js | 0 serve.js | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename local-ip.js => lib/local-ip.js (100%) rename match-ips.js => lib/match-ips.js (100%) diff --git a/local-ip.js b/lib/local-ip.js similarity index 100% rename from local-ip.js rename to lib/local-ip.js diff --git a/match-ips.js b/lib/match-ips.js similarity index 100% rename from match-ips.js rename to lib/match-ips.js diff --git a/serve.js b/serve.js index 1c6e082..b55f8c7 100755 --- a/serve.js +++ b/serve.js @@ -398,12 +398,12 @@ function run() { if (!(argv.servername && defaultServername !== argv.servername && !(argv.key && argv.cert))) { // ifaces - opts.ifaces = require('./local-ip.js').find(); + opts.ifaces = require('./lib/local-ip.js').find(); promise = PromiseA.resolve(); } else { console.info("Attempting to resolve external connection for '" + argv.servername + "'"); try { - promise = require('./match-ips.js').match(argv.servername, opts); + promise = require('./lib/match-ips.js').match(argv.servername, opts); } catch(e) { console.warn("Upgrade to version 2.x to use automatic certificate issuance for '" + argv.servername + "'"); promise = PromiseA.resolve(); From 1d2aa52b02419386a6efbce227fec491562819b4 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 12 Oct 2016 18:23:39 -0600 Subject: [PATCH 06/19] cleanup to lib/ --- app.js => lib/app.js | 0 serve.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename app.js => lib/app.js (100%) diff --git a/app.js b/lib/app.js similarity index 100% rename from app.js rename to lib/app.js diff --git a/serve.js b/serve.js index b55f8c7..5f15d49 100755 --- a/serve.js +++ b/serve.js @@ -92,7 +92,7 @@ function createServer(port, pubdir, content, opts) { } return new PromiseA(function (realResolve) { - var app = require('./app'); + var app = require('./lib/app.js'); var directive = { public: pubdir, content: content, livereload: opts.livereload , servername: opts.servername, expressApp: opts.expressApp }; From 62a2f7d44d413b3847eda7b32231c73a49ae2998 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 17 Oct 2016 17:40:55 -0600 Subject: [PATCH 07/19] make it better --- lib/ddns.js | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/match-ips.js | 14 ++++++-- package.json | 2 +- serve.js | 5 ++- 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 lib/ddns.js diff --git a/lib/ddns.js b/lib/ddns.js new file mode 100644 index 0000000..f860926 --- /dev/null +++ b/lib/ddns.js @@ -0,0 +1,88 @@ +'use strict'; + +module.exports.create = function (opts/*, servers*/) { + var PromiseA = opts.PromiseA; + var dns = PromiseA.promisifyAll(require('dns')); + + return PromiseA.all([ + dns.resolve4Async(opts.servername).then(function (results) { + return results; + }, function () {}) + , dns.resolve6Async(opts.servername).then(function (results) { + return results; + }, function () {}) + ]).then(function (results) { + var ipv4 = results[0] || []; + var ipv6 = results[1] || []; + var record; + + opts.dnsRecords = { + A: ipv4 + , AAAA: ipv6 + }; + + Object.keys(opts.ifaces).some(function (ifacename) { + var iface = opts.ifaces[ifacename]; + + return iface.ipv4.some(function (localIp) { + return ipv4.some(function (remoteIp) { + if (localIp.address === remoteIp) { + record = localIp; + return record; + } + }); + }) || iface.ipv6.some(function (localIp) { + return ipv6.forEach(function (remoteIp) { + if (localIp.address === remoteIp) { + record = localIp; + return record; + } + }); + }); + }); + + if (!record) { + console.info("DNS Record '" + ipv4.concat(ipv6).join(',') + "' does not match any local IP address."); + console.info("Use --ddns to allow the people of the Internet to access your server."); + } + + opts.externalIps.ipv4.some(function (localIp) { + return ipv4.some(function (remoteIp) { + if (localIp.address === remoteIp) { + record = localIp; + return record; + } + }); + }); + + opts.externalIps.ipv6.some(function (localIp) { + return ipv6.some(function (remoteIp) { + if (localIp.address === remoteIp) { + record = localIp; + return record; + } + }); + }); + + if (!record) { + console.info("DNS Record '" + ipv4.concat(ipv6).join(',') + "' does not match any local IP address."); + console.info("Use --ddns to allow the people of the Internet to access your server."); + } + }); +}; + +if (require.main === module) { + var opts = { + servername: 'aj.daplie.me' + , PromiseA: require('bluebird') + }; + // ifaces + opts.ifaces = require('./local-ip.js').find(); + console.log('opts.ifaces'); + console.log(opts.ifaces); + require('./match-ips.js').match(opts.servername, opts).then(function (ips) { + opts.matchingIps = ips.matchingIps || []; + opts.externalIps = ips.externalIps; + module.exports.create(opts); + }); +} diff --git a/lib/match-ips.js b/lib/match-ips.js index 061e2d4..dbb3ff1 100644 --- a/lib/match-ips.js +++ b/lib/match-ips.js @@ -3,10 +3,10 @@ var PromiseA = require('bluebird'); module.exports.match = function (servername, opts) { - return PromiseA.promisify(require('ipify'))().then(function (ip) { + return PromiseA.promisify(require('ipify'))().then(function (externalIp) { var dns = PromiseA.promisifyAll(require('dns')); - opts.externalIps = [ { address: ip, family: 'IPv4' } ]; + opts.externalIps = [ { address: externalIp, 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]; @@ -101,6 +101,16 @@ module.exports.match = function (servername, opts) { return matchingIps.length; }); + matchingIps.externalIps = { + ipv4: [ + { address: externalIp + , family: 'IPv4' + } + ] + , ipv6: [ + ] + }; + matchingIps.matchingIps = matchingIps; return matchingIps; }); }); diff --git a/package.json b/package.json index e2889e1..cf328fe 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "le-challenge-dns": "^2.0.1", "le-challenge-fs": "^2.0.5", "letsencrypt-express": "^2.0.2", - "livereload": "^0.5.0", + "livereload": "^0.6.0", "localhost.daplie.com-certificates": "^1.2.0", "minimist": "^1.1.1", "redirect-https": "^1.1.0", diff --git a/serve.js b/serve.js index 5f15d49..f020692 100755 --- a/serve.js +++ b/serve.js @@ -169,7 +169,7 @@ function createServer(port, pubdir, content, opts) { var server2 = livereload.createServer({ https: opts.httpsOptions , port: opts.lrPort - , exclusions: [ '.hg', '.git', '.svn', 'node_modules' ] + , exclusions: [ 'node_modules' ] }); console.info("[livereload] watching " + pubdir); @@ -447,6 +447,9 @@ function run() { if (opts.tunnel) { require('./lib/tunnel.js').create(opts, servers); } + else if (opts.ddns) { + require('./lib/ddns.js').create(opts, servers); + } Object.keys(opts.ifaces).forEach(function (iname) { var iface = opts.ifaces[iname]; From ff5857bd27213c00c8da3d74d7762a5372ae3b22 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 17 Oct 2016 17:46:11 -0600 Subject: [PATCH 08/19] update options --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ab4008..b26bf42 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,14 @@ Usage * `-p ` - i.e. `sudo serve-https -p 443` (defaults to 80+443 or 8443) * `-d ` - i.e. `serve-https -d /tmp/` (defaults to `pwd`) * `-c ` - i.e. `server-https -c 'Hello, World! '` (defaults to directory index) -* `--express-app` - path to a file the exports an express-style app (`function (req, res, next) { ... }`) +* `--express-app ` - path to a file the exports an express-style app (`function (req, res, next) { ... }`) * `--livereload` - inject livereload into all html pages (see also: [fswatch](http://stackoverflow.com/a/13807906/151312)), but be careful if `` has thousands of files it will spike your CPU usage to 100% +* `--email ` - email to use for Let's Encrypt, Daplie DNS, Daplie Tunnel +* `--agree-tos` - agree to terms for Let's Encrypt, Daplie DNS +* `--servername ` - use `` instead of `localhost.daplie.com` +* `--tunnel` - make world-visible (must use `--servername`) + Specifying a custom HTTPS certificate: * `--key /path/to/privkey.pem` specifies the server private key From 8de4178a7d027e120f557afce57240d4bfb7b723 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 17 Oct 2016 17:50:17 -0600 Subject: [PATCH 09/19] add example --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index b26bf42..5cf1ddb 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,12 @@ Serving /Users/foo/ at https://localhost.daplie.com:8443 Usage ----- +Examples: + +``` +node serve.js --servername jane.daplie.me --agree-tos --email jane@example.com --tunnel +``` + * `-p ` - i.e. `sudo serve-https -p 443` (defaults to 80+443 or 8443) * `-d ` - i.e. `serve-https -d /tmp/` (defaults to `pwd`) * `-c ` - i.e. `server-https -c 'Hello, World! '` (defaults to directory index) From 31fec862f476eb3e5c29efcb2db221c5d3c5e4fb Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 17 Oct 2016 17:52:32 -0600 Subject: [PATCH 10/19] add options --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5cf1ddb..fd66ae9 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Examples: node serve.js --servername jane.daplie.me --agree-tos --email jane@example.com --tunnel ``` +Options: + * `-p ` - i.e. `sudo serve-https -p 443` (defaults to 80+443 or 8443) * `-d ` - i.e. `serve-https -d /tmp/` (defaults to `pwd`) * `-c ` - i.e. `server-https -c 'Hello, World! '` (defaults to directory index) From 0e31557576bb6c2dbe33d46e7fab0fa0889f58c4 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 18 Oct 2016 12:25:08 -0600 Subject: [PATCH 11/19] add packages from master --- package.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cf328fe..bf97abe 100644 --- a/package.json +++ b/package.json @@ -39,17 +39,21 @@ "homepage": "https://github.com/Daplie/serve-https#readme", "dependencies": { "bluebird": "^3.4.6", + "ddns-cli": "git+https://github.com:Daplie/node-ddns-client.git#master", + "daplie-tunnel": "git+https://github.com:Daplie/daplie-cli-tunnel.git#master", "finalhandler": "^0.4.0", "httpolyglot": "^0.1.1", "ipify": "^1.1.0", - "le-challenge-dns": "^2.0.1", + "le-challenge-ddns": "^2.0.1", "le-challenge-fs": "^2.0.5", "letsencrypt-express": "^2.0.2", "livereload": "^0.6.0", "localhost.daplie.com-certificates": "^1.2.0", "minimist": "^1.1.1", + "oauth3-cli": "git+https://github.com:OAuth3/oauth3-cli.git#master", "redirect-https": "^1.1.0", "serve-index": "^1.7.0", - "serve-static": "^1.10.0" + "serve-static": "^1.10.0", + "stunnel": "git+https://github.com:Daplie/node-tunnel-client.git#master" } } From 89f26753bfa9b4e2712b4a1004d7f532e8ea48cb Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 19 Oct 2016 14:09:10 -0600 Subject: [PATCH 12/19] load oauth3 device by devicename --- lib/tunnel.js | 1 + serve.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/tunnel.js b/lib/tunnel.js index dc1010f..353e701 100644 --- a/lib/tunnel.js +++ b/lib/tunnel.js @@ -63,6 +63,7 @@ module.exports.create = function (opts, servers) { refreshToken: opts.refreshToken , email: opts.email , domains: [ opts.servername ] + , device: { hostname: opts.devicename || opts.device } }).then(function (result) { // { jwt, tunnelUrl } return stunnel.connect({ diff --git a/serve.js b/serve.js index f020692..236af8d 100755 --- a/serve.js +++ b/serve.js @@ -109,7 +109,7 @@ function createServer(port, pubdir, content, opts) { var webrootPath = require('os').tmpdir(); var leChallengeFs = require('le-challenge-fs').create({ webrootPath: webrootPath }); //var leChallengeSni = require('le-challenge-sni').create({ webrootPath: webrootPath }); - var leChallengeDns = require('le-challenge-dns').create({ ttl: 1 }); + var leChallengeDdns = require('le-challenge-ddns').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' @@ -119,7 +119,7 @@ function createServer(port, pubdir, content, opts) { , challenges: { 'http-01': leChallengeFs , 'tls-sni-01': leChallengeFs // leChallengeSni - , 'dns-01': leChallengeDns + , 'dns-01': leChallengeDdns } , challengeType: (opts.tunnel ? 'http-01' : 'dns-01') , store: require('le-store-certbot').create({ webrootPath: webrootPath }) @@ -242,6 +242,7 @@ function run() { var opts = { agreeTos: argv.agreeTos || argv['agree-tos'] , debug: argv.debug + , device: argv.device , email: argv.email , httpsOptions: { key: httpsOptions.key From 92de847d2739fd9507c1937647216740500118ef Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 20 Oct 2016 15:02:47 -0600 Subject: [PATCH 13/19] add deps --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bf97abe..1c73a87 100644 --- a/package.json +++ b/package.json @@ -39,13 +39,14 @@ "homepage": "https://github.com/Daplie/serve-https#readme", "dependencies": { "bluebird": "^3.4.6", - "ddns-cli": "git+https://github.com:Daplie/node-ddns-client.git#master", "daplie-tunnel": "git+https://github.com:Daplie/daplie-cli-tunnel.git#master", + "ddns-cli": "git+https://github.com:Daplie/node-ddns-client.git#master", "finalhandler": "^0.4.0", "httpolyglot": "^0.1.1", "ipify": "^1.1.0", "le-challenge-ddns": "^2.0.1", "le-challenge-fs": "^2.0.5", + "le-challenge-sni": "^2.0.1", "letsencrypt-express": "^2.0.2", "livereload": "^0.6.0", "localhost.daplie.com-certificates": "^1.2.0", From bc81ffa5161805d02f828bcedd3da21c1c5ada08 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 20 Oct 2016 15:04:14 -0600 Subject: [PATCH 14/19] version dump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c73a87..98a1bfd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serve-https", - "version": "2.0.0", + "version": "1.99.0", "description": "Serves HTTPS using TLS (SSL) certs for localhost.daplie.com - great for testing and development.", "main": "serve.js", "scripts": { From 4defabcc5aef9e528ee89c1a394efc198a42f974 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 20 Oct 2016 15:04:21 -0600 Subject: [PATCH 15/19] v2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98a1bfd..1c73a87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serve-https", - "version": "1.99.0", + "version": "2.0.0", "description": "Serves HTTPS using TLS (SSL) certs for localhost.daplie.com - great for testing and development.", "main": "serve.js", "scripts": { From c10ecad62d57c3a73ad7735611154f3d75debecd Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 20 Oct 2016 21:41:37 -0600 Subject: [PATCH 16/19] fix links --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1c73a87..1667c9f 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ "homepage": "https://github.com/Daplie/serve-https#readme", "dependencies": { "bluebird": "^3.4.6", - "daplie-tunnel": "git+https://github.com:Daplie/daplie-cli-tunnel.git#master", - "ddns-cli": "git+https://github.com:Daplie/node-ddns-client.git#master", + "daplie-tunnel": "git+https://github.com/Daplie/daplie-cli-tunnel.git#master", + "ddns-cli": "git+https://github.com/Daplie/node-ddns-client.git#master", "finalhandler": "^0.4.0", "httpolyglot": "^0.1.1", "ipify": "^1.1.0", @@ -51,10 +51,10 @@ "livereload": "^0.6.0", "localhost.daplie.com-certificates": "^1.2.0", "minimist": "^1.1.1", - "oauth3-cli": "git+https://github.com:OAuth3/oauth3-cli.git#master", + "oauth3-cli": "git+https://github.com/OAuth3/oauth3-cli.git#master", "redirect-https": "^1.1.0", "serve-index": "^1.7.0", "serve-static": "^1.10.0", - "stunnel": "git+https://github.com:Daplie/node-tunnel-client.git#master" + "stunnel": "git+https://github.com/Daplie/node-tunnel-client.git#master" } } From 23687056c252c79c50745aacf706bbce5e7c0afc Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 20 Oct 2016 21:41:45 -0600 Subject: [PATCH 17/19] v2.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1667c9f..4d9930b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serve-https", - "version": "2.0.0", + "version": "2.0.1", "description": "Serves HTTPS using TLS (SSL) certs for localhost.daplie.com - great for testing and development.", "main": "serve.js", "scripts": { From 1dc22cfafcae4e979fb1c2a0c7fcd8ca735e8ec1 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 20 Oct 2016 22:25:31 -0600 Subject: [PATCH 18/19] use git repos for packages --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 4d9930b..778cc79 100644 --- a/package.json +++ b/package.json @@ -44,10 +44,11 @@ "finalhandler": "^0.4.0", "httpolyglot": "^0.1.1", "ipify": "^1.1.0", - "le-challenge-ddns": "^2.0.1", - "le-challenge-fs": "^2.0.5", + "le-challenge-ddns": "git+https://github.com/Daplie/le-challenge-ddns.git#master", + "le-challenge-fs": "git+https://github.com/Daplie/le-challenge-fs.git#master", "le-challenge-sni": "^2.0.1", - "letsencrypt-express": "^2.0.2", + "letsencrypt-express": "git+https://github.com/Daplie/letsencrypt-express.git#master", + "letsencrypt": "git+https://github.com/Daplie/node-letsencrypt.git#master", "livereload": "^0.6.0", "localhost.daplie.com-certificates": "^1.2.0", "minimist": "^1.1.1", From e66d04d6ee5dd12b007b2e3423e4f6adca04203a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 20 Oct 2016 22:25:39 -0600 Subject: [PATCH 19/19] v2.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 778cc79..695eb1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serve-https", - "version": "2.0.1", + "version": "2.0.2", "description": "Serves HTTPS using TLS (SSL) certs for localhost.daplie.com - great for testing and development.", "main": "serve.js", "scripts": {