more comprehensive data model

This commit is contained in:
AJ ONeal 2017-03-02 00:58:45 -07:00
parent 1cae332c9c
commit 22b7a1b880
9 changed files with 396 additions and 90 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
var/*
# Logs # Logs
logs logs
*.log *.log

View File

@ -5,7 +5,7 @@
</head> </head>
<body class="fade" ng-class="[ 'in' ]"> <body class="fade" ng-class="[ 'in' ]">
<div ng-controller="LoginController as vm" ng-init="vm.setSimple()"> <div class="container" ng-controller="LoginController as vm" ng-init="vm.setSimple()">
<h1 ng-if="!vm.authnUpdated">Initializing... {{vm.hello}}</h1> <h1 ng-if="!vm.authnUpdated">Initializing... {{vm.hello}}</h1>
<div ng-if="!vm.authnUpdated"> <div ng-if="!vm.authnUpdated">
<button <button
@ -32,8 +32,25 @@
ng-click="vm.authenticate()" ng-click="vm.authenticate()"
>Login</button> >Login</button>
</div> </div>
</div>
<div ng-if="vm.config">
<div class="input-group" ng-repeat="(sitename, siteconf) in vm.config.sites">
<label>Hostname:</label> <input class="form-control" ng-model="sitename" />
<br/>
<div ng-repeat="(pathname, modules) in siteconf.paths">Pathname:
<input class="form-control" ng-model="pathname" />
<div ng-repeat="(modulename, module) in modules">Modulename: {{modulename}}
<div ng-repeat="target in modules">Target:
<input class="form-control" ng-model="target" />
</div>
</div>
</div>
</div>
</div>
<pre><code ng-bind="vm.config | json">{{vm.config}}</code></pre>
</div>
<ul> <ul>
<li>Show Device Name</li> <li>Show Device Name</li>
<li>Login to Daplie</li> <li>Login to Daplie</li>

View File

@ -67,10 +67,20 @@ angular.module('com.daplie.cloud', [ 'org.oauth3' ])
, tld: d.tld , tld: d.tld
}; };
}) })
, jwk: null // TODO publish public key
} }
}).then(function (resp) { }).then(function (resp) {
// TODO resp should contain a token
console.info('Initialized Goldilocks', resp); console.info('Initialized Goldilocks', resp);
return resp; return OAUTH3.request({
method: 'GET'
, url: 'https://' + vm.clientUri + '/api/com.daplie.caddy/config'
, session: session
}).then(function (configResp) {
console.log('config', configResp.data);
vm.config = configResp.data;
return resp;
});
}, function (err) { }, function (err) {
console.error(err); console.error(err);
window.alert("Initialization failed:" + err.message); window.alert("Initialization failed:" + err.message);
@ -82,6 +92,9 @@ angular.module('com.daplie.cloud', [ 'org.oauth3' ])
}); });
}; };
oauth3.checkSession().then(function (session) {
console.log('hasSession?', session);
});
/* /*
console.log('OAUTH3.PromiseA', OAUTH3.PromiseA); console.log('OAUTH3.PromiseA', OAUTH3.PromiseA);

View File

@ -93,10 +93,23 @@ function createServer(port, _delete_me_, content, opts) {
return new PromiseA(function (realResolve) { return new PromiseA(function (realResolve) {
var app = require('../lib/app.js'); var app = require('../lib/app.js');
var directive = { content: content, livereload: opts.livereload var directive = {
, sites: opts.sites content: content
, assetsPath: opts.assetsPath , livereload: opts.livereload
, expressApp: opts.expressApp }; , global: {
greenlock: { email: opts.email, tos: opts.tos }
, rvpn: { email: opts.email, tos: opts.tos }
, paths: {
'/assets/': { serve: [ opts.assetsPath ] }
// TODO figure this b out
, '/.well-known/': { serve: [ path.resolve(opts.assetsPath, 'well-known') ] }
, '/': { serve: [ opts.webRoot ], indexes: [ opts.webRoot ] }
}
}
, sites: opts.sites
, expressApp: opts.expressApp
};
var server;
var insecureServer; var insecureServer;
function resolve() { function resolve() {
@ -160,7 +173,7 @@ function createServer(port, _delete_me_, content, opts) {
// Dynamic Certs // Dynamic Certs
lex.httpsOptions.SNICallback(sni, cb); lex.httpsOptions.SNICallback(sni, cb);
}; };
var server = https.createServer(opts.httpsOptions); server = https.createServer(opts.httpsOptions);
server.on('error', function (err) { server.on('error', function (err) {
if (opts.errorPort || opts.manualPort) { if (opts.errorPort || opts.manualPort) {
@ -369,39 +382,57 @@ function run() {
} }
opts.sites = [ { name: defaultServername , path: '.' } ]; opts.cwd = process.cwd();
opts.sites = {};
if (argv.sites) { if (argv.sites) {
opts._externalHost = false; opts._externalHost = false;
opts.sites = argv.sites.split(',').map(function (name) { argv.sites.split(',').map(function (name) {
var nameparts = name.split('|'); var nameparts = name.split('|');
var servername = nameparts.shift(); var servername = nameparts.shift();
var modules;
opts._externalHost = opts._externalHost || !/(^|\.)localhost\./.test(servername); opts._externalHost = opts._externalHost || !/(^|\.)localhost\./.test(servername);
// TODO allow reverse proxy // TODO allow reverse proxy
return { if (!opts.sites[servername]) {
name: servername opts.sites[servername] = { paths: {} };
// there should always be a path }
, paths: nameparts.length && nameparts || [
defaultWebRoot.replace(/(:hostname|:servername)/g, servername) if (!nameparts.length) {
] return;
// TODO check for existing custom path before issuing with greenlock }
, _hasCustomPath: !!nameparts.length
}; if (!opts.sites[servername].paths['/']) {
opts.sites[servername].paths['/'] = {};
}
modules = opts.sites[servername].paths['/'];
modules.serve = nameparts;
modules.indexes = nameparts;
}); });
} }
opts.sites.push({
name: 'localhost.alpha.daplie.me' opts.groups = {};
, paths: [ path.resolve(__dirname, '..', 'admin', 'public') ]
, app: path.join(__dirname, 'admin') // 'packages', 'assets', 'com.daplie.caddy'
}); opts.sites['localhost.alpha.daplie.me'] = {
opts.sites.push({ // greenlock: {}
name: 'localhost.daplie.invalid' paths: {
, paths: [ path.join(__dirname, '..', 'admin', 'public') ] '/': { serve: [ path.resolve(__dirname, '..', 'admin', 'public') ] }
, app: path.join(__dirname, 'admin') , '/api/': { app: path.join(__dirname, 'admin') }
}); }
};
opts.sites['localhost.daplie.invalid'] = {
paths: {
'/': { serve: [ path.resolve(__dirname, '..', 'admin', 'public') ] }
, '/api/': { app: path.join(__dirname, 'admin') }
}
};
opts.assetsPath = path.join(__dirname, '..', 'packages', 'assets'); opts.assetsPath = path.join(__dirname, '..', 'packages', 'assets');
opts.webRoot = defaultWebRoot;
// TODO use arrays in all things // TODO use arrays in all things
opts._old_server_name = opts.sites[0].name; opts._old_server_name = Object.keys(opts.sites)[0];
opts.pubdir = defaultWebRoot.replace(/(:hostname|:servername).*/, ''); opts.pubdir = defaultWebRoot.replace(/(:hostname|:servername).*/, '');
if (argv.p || argv.port || argv._[0]) { if (argv.p || argv.port || argv._[0]) {

View File

@ -1,52 +1,20 @@
'use strict'; 'use strict';
module.exports = function (opts) { module.exports = function (opts) {
var finalhandler = require('finalhandler'); var express = require('express');
//var finalhandler = require('finalhandler');
var serveStatic = require('serve-static'); var serveStatic = require('serve-static');
var serveIndex = require('serve-index'); var serveIndex = require('serve-index');
var assetServer = serveStatic(opts.assetsPath); //var assetServer = serveStatic(opts.assetsPath);
var path = require('path'); var path = require('path');
var wellKnownServer = serveStatic(path.join(opts.assetsPath, 'well-known')); //var wellKnownServer = serveStatic(path.join(opts.assetsPath, 'well-known'));
var hostsMap = {}; var serveStaticMap = {};
var pathsMap = {}; var serveIndexMap = {};
var content = opts.content; var content = opts.content;
var server; //var server;
var serveInit; var serveInit;
var app;
function addServer(hostname) {
if (hostsMap[hostname]) {
return hostsMap[hostname];
}
opts.sites.forEach(function (site) {
if (hostname !== site.name) {
return;
}
// path should exist before it gets to this point
site.path = site.path || site.paths[0];
if (!pathsMap[site.path]) {
console.log('site:', site);
pathsMap[site.path] = {
serve: serveStatic(site.path)
// TODO option for dotfiles
, index: serveIndex(site.path)
};
}
hostsMap[hostname] = {
serve: pathsMap[site.path].serve
, index: pathsMap[site.path].index
, app: site.app
};
});
}
function _reloadWrite(data, enc, cb) { function _reloadWrite(data, enc, cb) {
/*jshint validthis: true */ /*jshint validthis: true */
@ -69,14 +37,67 @@ module.exports = function (opts) {
} }
addServer(opts.sites[0].name); function createServeInit() {
var PromiseA = require('bluebird');
var fs = PromiseA.promisifyAll(require('fs'));
var ownersPath = path.join(__dirname, '..', 'var', 'owners.json');
return function (req, res) { return require('../packages/apis/com.daplie.caddy').create({
if ('/api/com.daplie.caddy/init' === req.url) { PromiseA: PromiseA
if (!serveInit) { , storage: {
serveInit = require('../packages/apis/com.daplie.caddy').create(opts); 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;
}));
}
, 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');
}
}
} }
serveInit(req, res); , recase: require('recase').create({})
, options: opts
});
}
app = express();
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/config' === req.url) {
serveInit.config(req, res);
return; return;
} }
@ -85,10 +106,123 @@ module.exports = function (opts) {
res.end(content); res.end(content);
return; return;
} }
var done = finalhandler(req, res);
var host = req.headers.host;
var hostname = (host||'').split(':')[0] || opts.sites[0].name;
//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 || {}, opts.sites[hostname] || {}, opts.defer || {} ];
var loadables = {
serve: function (config, hostname, pathname, req, res, next) {
config = config.slice(0);
var originalUrl = req.url;
function nextServe() {
var dirname = config.pop();
console.log('[serve]', req.url, hostname, pathname, dirname);
if (!dirname) {
req.url = originalUrl;
next();
return;
}
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) {
config = config.slice(0);
var originalUrl = req.url;
function nextIndex() {
var dirname = config.pop();
console.log('[indexes]', req.url, hostname, pathname, dirname);
if (!dirname) {
req.url = originalUrl;
next();
return;
}
if (!serveStaticMap[dirname]) {
serveIndexMap[dirname] = serveIndex(dirname);
}
serveIndexMap[dirname](req, res, nextIndex);
}
req.url = req.url.substr(pathname.length - 1);
nextIndex();
}
};
function runModule(config, hostname, pathname, modulename, req, res, next) {
if (!loadables[modulename]) {
next(new Error("no module '" + modulename + "' found"));
return;
}
loadables[modulename](config, hostname, pathname, req, res, next);
}
function iterModules(modules, hostname, pathname, req, res, next) {
var modulenames = Object.keys(modules);
function nextModule() {
var modulename = modulenames.pop();
if (!modulename) {
next();
return;
}
console.log('modules', modules);
runModule(modules[modulename], hostname, pathname, modulename, req, res, nextModule);
}
nextModule();
}
function iterPaths(site, hostname, req, res, next) {
var pathnames = Object.keys(site.paths || {});
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;
});
function nextPath() {
var pathname = pathnames.pop();
if (!pathname) {
next();
return;
}
console.log('iterPaths', hostname, pathname, req.url);
iterModules(site.paths[pathname], hostname, pathname, req, res, nextPath);
}
nextPath();
}
function nextSite() {
var site = sites.pop();
if (!site) {
next(); // 404
return;
}
iterPaths(site, hostname, req, res, nextSite);
}
nextSite();
/*
function serveStaticly(server) { function serveStaticly(server) {
function serveTheStatic() { function serveTheStatic() {
server.serve(req, res, function (err) { server.serve(req, res, function (err) {
@ -129,6 +263,6 @@ module.exports = function (opts) {
addServer(hostname); addServer(hostname);
server = hostsMap[hostname] || hostsMap[opts.sites[0].name]; server = hostsMap[hostname] || hostsMap[opts.sites[0].name];
serveStaticly(server); serveStaticly(server);
*/
}; });
}; };

View File

@ -42,6 +42,7 @@
"body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1", "body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1",
"daplie-tunnel": "git+https://git.daplie.com/Daplie/daplie-cli-tunnel.git#master", "daplie-tunnel": "git+https://git.daplie.com/Daplie/daplie-cli-tunnel.git#master",
"ddns-cli": "git+https://git.daplie.com/Daplie/node-ddns-client.git#master", "ddns-cli": "git+https://git.daplie.com/Daplie/node-ddns-client.git#master",
"express": "git+https://github.com/expressjs/express.git#4.x",
"finalhandler": "^0.4.0", "finalhandler": "^0.4.0",
"greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master", "greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master",
"greenlock-express": "git+https://git.daplie.com/Daplie/greenlock-express.git#master", "greenlock-express": "git+https://git.daplie.com/Daplie/greenlock-express.git#master",
@ -55,7 +56,9 @@
"localhost.daplie.me-certificates": "^1.3.0", "localhost.daplie.me-certificates": "^1.3.0",
"minimist": "^1.1.1", "minimist": "^1.1.1",
"oauth3-cli": "git+https://git.daplie.com/OAuth3/oauth3-cli.git#master", "oauth3-cli": "git+https://git.daplie.com/OAuth3/oauth3-cli.git#master",
"recase": "git+https://git.daplie.com/coolaj86/recase-js.git#v1.0.4",
"redirect-https": "^1.1.0", "redirect-https": "^1.1.0",
"scmp": "git+https://github.com/freewil/scmp.git#1.x",
"serve-index": "^1.7.0", "serve-index": "^1.7.0",
"serve-static": "^1.10.0", "serve-static": "^1.10.0",
"stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#master" "stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#master"

View File

@ -1,18 +1,124 @@
'use strict'; 'use strict';
module.exports.create = function (opts) { module.exports.dependencies = [ 'storage.owners' ];
module.exports.create = function (deps) {
var scmp = require('scmp');
var crypto = require('crypto');
var jwt = require('jsonwebtoken');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var jsonParser = bodyParser.json({ var jsonParser = bodyParser.json({
inflate: true, limit: '100kb', reviver: null, strict: true /* type, verify */ inflate: true, limit: '100kb', reviver: null, strict: true /* type, verify */
}); });
return function (req, res) { /*
jsonParser(req, res, function () { var owners;
deps.storage.owners.on('set', function (_owners) {
console.log('req.body', req.body); owners = _owners;
});
res.setHeader('Content-Type', 'application/json;'); */
res.end(JSON.stringify({ error: { message: "Not Implemented", code: "E_NO_IMPL" } })); deps.storage.owners.exists = function (id) {
return deps.storage.owners.all().then(function (owners) {
return owners.some(function (owner) {
return scmp(id, owner.id);
});
}); });
}; };
function isAuthorized(req, res, fn) {
var auth = jwt.decode((req.headers.authorization||'').replace(/^bearer\s+/i, ''));
if (!auth) {
res.setHeader('Content-Type', 'application/json;');
res.end(JSON.stringify({ error: { message: "no token", code: 'E_NO_TOKEN', uri: undefined } }));
return;
}
var id = crypto.createHash('sha256').update(auth.sub).digest('hex');
return deps.storage.owners.exists(id).then(function (exists) {
if (!exists) {
res.setHeader('Content-Type', 'application/json;');
res.end(JSON.stringify({ error: { message: "not authorized", code: 'E_NO_AUTHZ', uri: undefined } }));
return;
}
fn();
});
}
return {
init: function (req, res) {
jsonParser(req, res, function () {
console.log('req.body', req.body);
var auth = jwt.decode((req.headers.authorization||'').replace(/^bearer\s+/i, ''));
var token = jwt.decode(req.body.access_token);
var refresh = jwt.decode(req.body.refresh_token);
auth.sub = auth.sub || auth.acx.id;
token.sub = token.sub || token.acx.id;
refresh.sub = refresh.sub || refresh.acx.id;
// TODO validate token with issuer, but as-is the sub is already a secret
var id = crypto.createHash('sha256').update(auth.sub).digest('hex');
var tid = crypto.createHash('sha256').update(token.sub).digest('hex');
var rid = crypto.createHash('sha256').update(refresh.sub).digest('hex');
console.log('ids', id, tid, rid);
return deps.storage.owners.all().then(function (results) {
console.log('results', results);
var err;
// There is no owner yet. First come, first serve.
if (!results || !results.length) {
if (tid !== id || rid !== id) {
err = new Error("When creating an owner the Authorization Bearer and Token and Refresh must all match");
return deps.PromiseA.reject(err);
}
console.log('no owner, creating');
return deps.storage.owners.set(id, { token: token, refresh: refresh });
}
console.log('has results');
// There are onwers. Is this one of them?
if (!results.some(function (token) {
return scmp(id, token.id);
})) {
err = new Error("Authorization token does not belong to an existing owner.");
return deps.PromiseA.reject(err);
}
console.log('has correct owner');
// We're adding an owner, unless it already exists
if (!results.some(function (token) {
return scmp(tid, token.id);
})) {
console.log('adds new owner with existing owner');
return deps.storage.owners.set(id, { token: token, refresh: refresh });
}
}).then(function () {
res.setHeader('Content-Type', 'application/json;');
res.end(JSON.stringify({ success: true }));
}, function (err) {
res.setHeader('Content-Type', 'application/json;');
res.end(JSON.stringify({ error: { message: err.message, code: err.code, uri: err.uri } }));
});
});
}
, config: function (req, res) {
isAuthorized(req, res, function () {
if ('POST' !== req.method) {
res.setHeader('Content-Type', 'application/json;');
res.end(JSON.stringify(deps.recase.snakeCopy(deps.options)));
return;
}
jsonParser(req, res, function () {
console.log('req.body', req.body);
deps.storage.config.merge(req.body);
deps.storage.config.save();
});
});
}
};
}; };

@ -1 +1 @@
Subproject commit eff1fd11272472e8f6d03ac8b897a9853810672e Subproject commit f179cfe3c9553718676db24f1203f67ea0427662

0
var/.gitkeep Normal file
View File