goldilocks.js/lib/app.js

493 lines
15 KiB
JavaScript

'use strict';
module.exports = function (opts) {
var express = require('express');
//var finalhandler = require('finalhandler');
var serveStatic = require('serve-static');
var serveIndex = require('serve-index');
//var assetServer = serveStatic(opts.assetsPath);
var path = require('path');
//var wellKnownServer = serveStatic(path.join(opts.assetsPath, 'well-known'));
var serveStaticMap = {};
var serveIndexMap = {};
var content = opts.content;
//var server;
var serveInit;
var app;
var tun;
var request;
/*
function _reloadWrite(data, enc, cb) {
// /*jshint validthis: true */ /*
if (this.headersSent) {
this.__write(data, enc, cb);
return;
}
if (!/html/i.test(this.getHeader('Content-Type'))) {
this.__write(data, enc, cb);
return;
}
if (this.getHeader('Content-Length')) {
this.setHeader('Content-Length', this.getHeader('Content-Length') + this.__my_addLen);
}
this.__write(this.__my_livereload);
this.__write(data, enc, cb);
}
*/
function createServeInit() {
var PromiseA = require('bluebird');
var stunnel = require('stunnel');
var OAUTH3 = require('../packages/assets/org.oauth3');
require('../packages/assets/org.oauth3/oauth3.domains.js');
require('../packages/assets/org.oauth3/oauth3.dns.js');
require('../packages/assets/org.oauth3/oauth3.tunnel.js');
OAUTH3._hooks = require('../packages/assets/org.oauth3/oauth3.node.storage.js');
var fs = PromiseA.promisifyAll(require('fs'));
var ownersPath = path.join(__dirname, '..', 'var', 'owners.json');
var scmp = require('scmp');
request = request || PromiseA.promisify(require('request'));
return require('../packages/apis/com.daplie.caddy').create({
PromiseA: PromiseA
, OAUTH3: OAUTH3
, storage: {
owners: {
all: function () {
var owners;
try {
owners = require(ownersPath);
} catch(e) {
owners = {};
}
return PromiseA.resolve(Object.keys(owners).map(function (key) {
var owner = owners[key];
owner.id = key;
return owner;
}));
}
, get: function (id) {
var me = this;
return me.all().then(function (owners) {
return owners.filter(function (owner) {
return scmp(id, owner.id);
})[0];
});
}
, exists: function (id) {
var me = this;
return me.get(id).then(function (owner) {
return !!owner;
});
}
, set: function (id, obj) {
var owners;
try {
owners = require(ownersPath);
} catch(e) {
owners = {};
}
obj.id = id;
owners[id] = obj;
return fs.writeFileAsync(ownersPath, JSON.stringify(owners), 'utf8');
}
}
}
, recase: require('recase').create({})
, request: request
, options: opts
, api: {
// TODO move loopback to oauth3.api('tunnel:loopback')
loopback: function (deps, session, opts2) {
var crypto = require('crypto');
var token = crypto.randomBytes(16).toString('hex');
var keyAuthorization = crypto.randomBytes(16).toString('hex');
var nonce = crypto.randomBytes(16).toString('hex');
// TODO set token and keyAuthorization to /.well-known/cloud-challenge/:token
return request({
method: 'POST'
, url: 'https://oauth3.org/api/org.oauth3.tunnel/loopback'
, json: {
address: opts2.address
, port: opts2.port
, token: token
, keyAuthorization: keyAuthorization
, servername: opts2.servername
, nonce: nonce
, scheme: 'https'
, iat: Date.now()
}
}).then(function (result) {
// TODO this will always fail at the moment
console.log('loopback result:');
return result;
});
}
, tunnel: function (deps, session) {
// TODO save session to config and turn tunnel on
var OAUTH3 = deps.OAUTH3;
var url = require('url');
var providerUri = session.token.aud;
var urlObj = url.parse(OAUTH3.url.normalize(session.token.azp));
var oauth3 = OAUTH3.create(urlObj, {
providerUri: providerUri
, session: session
});
//var crypto = require('crypto');
//var id = crypto.createHash('sha256').update(session.token.sub).digest('hex');
return oauth3.setProvider(providerUri).then(function () {
/*
return oauth3.api('domains.list').then(function (domains) {
var domainsMap = {};
domains.forEach(function (d) {
if (!d.device) {
return;
}
if (d.device !== deps.options.device.hostname) {
return;
}
domainsMap[d.name] = true;
});
*/
//console.log('domains matching hostname', Object.keys(domainsMap));
//console.log('device', deps.options.device);
return oauth3.api('tunnel.token', {
data: {
// filter to all domains that are on this device
//domains: Object.keys(domainsMap)
device: {
hostname: deps.options.device.hostname
, id: deps.options.device.uid || deps.options.device.id
}
}
}).then(function (result) {
console.log('got a token from the tunnel server?');
console.log(result);
if (!result.tunnelUrl) {
result.tunnelUrl = ('wss://' + (new Buffer(result.jwt.split('.')[1], 'base64').toString('ascii')).aud + '/');
}
var opts3 = {
token: result.jwt
, stunneld: result.tunnelUrl
// we'll provide faux networking and pipe as we please
, services: { https: { '*': 443 }, http: { '*': 80 }, smtp: { '*': 25}, smtps: { '*': 587 /*also 465/starttls*/ } /*, ssh: { '*': 22 }*/ }
, net: opts.net
};
if (tun) {
if (tun.append) {
tun.append(result.jwt);
}
else if (tun.end) {
tun.end();
tun = null;
}
}
if (!tun) {
tun = stunnel.connect(opts3);
opts.tun = true;
}
});
/*
});
*/
});
//, { token: token, refresh: refresh });
}
}
});
}
app = express();
if (!opts.sites) {
opts.sites = [];
}
opts.sites._map = {};
opts.sites.forEach(function (site) {
if (!opts.sites._map[site.$id]) {
opts.sites._map[site.$id] = site;
}
if (!site.paths) {
site.paths = [];
}
if (!site.paths._map) {
site.paths._map = {};
}
site.paths.forEach(function (path) {
site.paths._map[path.$id] = path;
if (!path.modules) {
path.modules = [];
}
if (!path.modules._map) {
path.modules._map = {};
}
path.modules.forEach(function (module) {
path.modules._map[module.$id] = module;
});
});
});
function mapMap(el, i, arr) {
arr._map[el.$id] = el;
}
opts.global.modules._map = {};
opts.global.modules.forEach(mapMap);
opts.global.paths._map = {};
opts.global.paths.forEach(function (path, i, arr) {
mapMap(path, i, arr);
//opts.global.paths._map[path.$id] = path;
path.modules._map = {};
path.modules.forEach(mapMap);
});
opts.sites.forEach(function (site) {
site.paths._map = {};
site.paths.forEach(function (path, i, arr) {
mapMap(path, i, arr);
//site.paths._map[path.$id] = path;
path.modules._map = {};
path.modules.forEach(mapMap);
});
});
opts.defaults.modules._map = {};
opts.defaults.modules.forEach(mapMap);
opts.defaults.paths._map = {};
opts.defaults.paths.forEach(function (path, i, arr) {
mapMap(path, i, arr);
//opts.global.paths._map[path.$id] = path;
path.modules._map = {};
path.modules.forEach(mapMap);
});
return app.use('/', function (req, res, next) {
if (!req.headers.host) {
next(new Error('missing HTTP Host header'));
return;
}
if (0 === req.url.indexOf('/api/com.daplie.caddy/')) {
if (!serveInit) {
serveInit = createServeInit();
}
}
if ('/api/com.daplie.caddy/init' === req.url) {
serveInit.init(req, res);
return;
}
if ('/api/com.daplie.caddy/tunnel' === req.url) {
serveInit.tunnel(req, res);
return;
}
if ('/api/com.daplie.caddy/config' === req.url) {
serveInit.config(req, res);
return;
}
if ('/api/com.daplie.caddy/request' === req.url) {
serveInit.request(req, res);
return;
}
if (content && '/' === req.url) {
// res.setHeader('Content-Type', 'application/octet-stream');
res.end(content);
return;
}
//var done = finalhandler(req, res);
var host = req.headers.host;
var hostname = (host||'').split(':')[0].toLowerCase();
console.log('opts.global', opts.global);
var sites = [ opts.global || null, opts.sites._map[hostname] || null, opts.defaults || null ];
var loadables = {
serve: function (config, hostname, pathname, req, res, next) {
var originalUrl = req.url;
var dirpaths = config.paths.slice(0);
function nextServe() {
var dirname = dirpaths.pop();
if (!dirname) {
req.url = originalUrl;
next();
return;
}
console.log('[serve]', req.url, hostname, pathname, dirname);
dirname = path.resolve(opts.cwd, dirname.replace(/:hostname/, hostname));
if (!serveStaticMap[dirname]) {
serveStaticMap[dirname] = serveStatic(dirname);
}
serveStaticMap[dirname](req, res, nextServe);
}
req.url = req.url.substr(pathname.length - 1);
nextServe();
}
, indexes: function (config, hostname, pathname, req, res, next) {
var originalUrl = req.url;
var dirpaths = config.paths.slice(0);
function nextIndex() {
var dirname = dirpaths.pop();
if (!dirname) {
req.url = originalUrl;
next();
return;
}
console.log('[indexes]', req.url, hostname, pathname, dirname);
dirname = path.resolve(opts.cwd, dirname.replace(/:hostname/, hostname));
if (!serveStaticMap[dirname]) {
serveIndexMap[dirname] = serveIndex(dirname);
}
serveIndexMap[dirname](req, res, nextIndex);
}
req.url = req.url.substr(pathname.length - 1);
nextIndex();
}
, app: function (config, hostname, pathname, req, res, next) {
//var appfile = path.resolve(/*process.cwd(), */config.path.replace(/:hostname/, hostname));
var appfile = config.path.replace(/:hostname/, hostname);
var app = require(appfile);
app(req, res, next);
}
};
function runModule(module, hostname, pathname, modulename, req, res, next) {
if (!loadables[modulename]) {
next(new Error("no module '" + modulename + "' found"));
return;
}
loadables[modulename](module, hostname, pathname, req, res, next);
}
function iterModules(modules, hostname, pathname, req, res, next) {
console.log('modules');
console.log(modules);
var modulenames = Object.keys(modules._map);
function nextModule() {
var modulename = modulenames.pop();
if (!modulename) {
next();
return;
}
console.log('modules', modules);
runModule(modules._map[modulename], hostname, pathname, modulename, req, res, nextModule);
}
nextModule();
}
function iterPaths(site, hostname, req, res, next) {
console.log('site', hostname);
console.log(site);
var pathnames = Object.keys(site.paths._map);
console.log('pathnames', pathnames);
pathnames = pathnames.filter(function (pathname) {
// TODO ensure that pathname has trailing /
return (0 === req.url.indexOf(pathname));
//return req.url.match(pathname);
});
pathnames.sort(function (a, b) {
return b.length - a.length;
});
console.log('pathnames', pathnames);
function nextPath() {
var pathname = pathnames.shift();
if (!pathname) {
next();
return;
}
console.log('iterPaths', hostname, pathname, req.url);
iterModules(site.paths._map[pathname].modules, hostname, pathname, req, res, nextPath);
}
nextPath();
}
function nextSite() {
console.log('hostname', hostname, sites);
var site;
if (!sites.length) {
next(); // 404
return;
}
site = sites.shift();
if (!site) {
nextSite();
return;
}
iterPaths(site, hostname, req, res, nextSite);
}
nextSite();
/*
function serveStaticly(server) {
function serveTheStatic() {
server.serve(req, res, function (err) {
if (err) { return done(err); }
server.index(req, res, function (err) {
if (err) { return done(err); }
req.url = req.url.replace(/\/assets/, '');
assetServer(req, res, function () {
if (err) { return done(err); }
req.url = req.url.replace(/\/\.well-known/, '');
wellKnownServer(req, res, done);
});
});
});
}
if (server.expressApp) {
server.expressApp(req, res, serveTheStatic);
return;
}
serveTheStatic();
}
if (opts.livereload) {
res.__my_livereload = '<script src="//'
+ (host || opts.sites[0].name).split(':')[0]
+ ':35729/livereload.js?snipver=1"></script>';
res.__my_addLen = res.__my_livereload.length;
// TODO modify prototype instead of each instance?
res.__write = res.write;
res.write = _reloadWrite;
}
console.log('hostname:', hostname, opts.sites[0].paths);
addServer(hostname);
server = hostsMap[hostname] || hostsMap[opts.sites[0].name];
serveStaticly(server);
*/
});
};