'use strict'; var https = require('https'); var http = require('http'); var PromiseA = require('bluebird').Promise; var forEachAsync = require('foreachasync').forEachAsync.create(PromiseA); var fs = require('fs'); var path = require('path'); var crypto = require('crypto'); var connect = require('connect'); var vhost = require('vhost'); var escapeRe = require('escape-string-regexp'); // connect / express app var app = connect(); // SSL Server var secureContexts = {}; var secureOpts; var secureServer; var securePort = process.argv[2] || 443; // force SSL upgrade server var insecureServer; var insecurePort = process.argv[3] || 80; // the ssl domains I have // TODO read vhosts minus var domains = fs.readdirSync(path.join(__dirname, 'vhosts')).filter(function (node) { // not a hidden or private file return '.' !== node[0] && '_' !== node[0]; }).map(function (apppath) { var parts = apppath.split(/[#%]+/); var hostname = parts.shift(); var pathname = parts.join('/').replace(/\/+/g, '/').replace(/^\//, ''); return { hostname: hostname , pathname: pathname , dirname: apppath , isRoot: apppath === hostname }; }).sort(function (a, b) { var hlen = b.hostname.length - a.hostname.length; var plen = b.pathname.length - a.pathname.length; // A directory could be named example.com, example.com# example.com## // to indicate order of preference (for API addons, for example) var dlen = b.dirname.length - a.dirname.length; if (!hlen) { if (!plen) { return dlen; } return plen; } return plen; }); var rootDomains = domains.filter(function (domaininfo) { return domaininfo.isRoot; }); var domainMergeMap = {}; var domainMerged = []; require('ssl-root-cas') .inject() ; function getDummyAppContext(err, msg) { if (err) { console.error(err); } return connect().use(function (req, res) { res.end('{ "error": { "message": "' + msg.replace(/"/g, '\\"') + '" } }'); }); } function getAppContext(domaininfo) { var localApp; try { localApp = require(path.join(__dirname, 'vhosts', domaininfo.dirname, 'app.js')); if (localApp.create) { // TODO read local config.yml and pass it in // TODO pass in websocket localApp = localApp.create(/*config*/); if (!localApp) { return getDummyAppContext(null, "[ERROR] no app was returned by app.js for " + domaininfo.driname); } } if (!localApp.then) { localApp = PromiseA.resolve(localApp); } else { return localApp.catch(function (e) { return getDummyAppContext(e, "[ERROR] initialization failed during create() for " + domaininfo.dirname); }); } } catch(e) { localApp = getDummyAppContext(e, "[ERROR] could not load app.js for " + domaininfo.dirname); localApp = PromiseA.resolve(localApp); return localApp; } return localApp; } function loadDummyCerts() { var certsPath = path.join(__dirname, 'certs'); var certs = { key: fs.readFileSync(path.join(certsPath, 'server', 'dummy-server.key.pem')) , cert: fs.readFileSync(path.join(certsPath, 'server', 'dummy-server.crt.pem')) , ca: fs.readdirSync(path.join(certsPath, 'ca')).map(function (node) { return fs.readFileSync(path.join(certsPath, 'ca', node)); }) }; secureContexts.dummy = crypto.createCredentials(certs).context; secureContexts.dummy.certs = certs; } loadDummyCerts(); function loadCerts(domaininfo) { var certsPath = path.join(__dirname, 'vhosts', domaininfo.dirname, 'certs'); try { var nodes = fs.readdirSync(path.join(certsPath, 'server')); var keyNode = nodes.filter(function (node) { return /\.key\.pem$/.test(node); })[0]; var crtNode = nodes.filter(function (node) { return /\.crt\.pem$/.test(node); })[0]; secureContexts[domaininfo.hostname] = crypto.createCredentials({ key: fs.readFileSync(path.join(certsPath, 'server', keyNode)) , cert: fs.readFileSync(path.join(certsPath, 'server', crtNode)) , ca: fs.readdirSync(path.join(certsPath, 'ca')).map(function (node) { return fs.readFileSync(path.join(certsPath, 'ca', node)); }) }).context; } catch(err) { // TODO Let's Encrypt / ACME HTTPS console.error("[ERROR] Couldn't load HTTPS certs from '" + certsPath + "':"); console.error(err); secureContexts[domaininfo.hostname] = secureContexts.dummy; } } forEachAsync(rootDomains, loadCerts).then(function () { // fallback / default domain /* app.use('/', function (req, res) { res.statusCode = 404; res.end("
You requested an insecure resource. Please use this instead: \n' + ' ' + newLocation + '
\n' + '\n' + '\n' ; // DO NOT HTTP REDIRECT /* res.setHeader('Location', newLocation); res.statusCode = 302; */ // BAD NEWS BEARS // // When people are experimenting with the API and posting tutorials // they'll use cURL and they'll forget to prefix with https:// // If we allow that, then many users will be sending private tokens // and such with POSTs in clear text and, worse, it will work! // To minimize this, we give browser users a mostly optimal experience, // but people experimenting with the API get a message letting them know // that they're doing it wrong and thus forces them to ensure they encrypt. res.setHeader('Content-Type', 'text/html'); res.end(metaRedirect); }); insecureServer.listen(insecurePort, function(){ console.log("\nRedirecting all http traffic to https\n"); }); }