diff --git a/.gitignore b/.gitignore
index 9b5d38b..7d37e47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+var/*
+
# Logs
logs
*.log
diff --git a/admin/public/index.html b/admin/public/index.html
index 9febd74..6896842 100644
--- a/admin/public/index.html
+++ b/admin/public/index.html
@@ -5,7 +5,7 @@
-
+
Initializing... {{vm.hello}}
-
+
+
+
{{vm.config}}
+
+
- Show Device Name
- Login to Daplie
diff --git a/admin/public/js/app.js b/admin/public/js/app.js
index 0dd095e..5ea17b6 100644
--- a/admin/public/js/app.js
+++ b/admin/public/js/app.js
@@ -67,10 +67,20 @@ angular.module('com.daplie.cloud', [ 'org.oauth3' ])
, tld: d.tld
};
})
+ , jwk: null // TODO publish public key
}
}).then(function (resp) {
+ // TODO resp should contain a token
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) {
console.error(err);
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);
diff --git a/bin/goldilocks.js b/bin/goldilocks.js
index 373d63a..44cbe0b 100755
--- a/bin/goldilocks.js
+++ b/bin/goldilocks.js
@@ -93,10 +93,23 @@ function createServer(port, _delete_me_, content, opts) {
return new PromiseA(function (realResolve) {
var app = require('../lib/app.js');
- var directive = { content: content, livereload: opts.livereload
- , sites: opts.sites
- , assetsPath: opts.assetsPath
- , expressApp: opts.expressApp };
+ var directive = {
+ content: content
+ , livereload: opts.livereload
+ , 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;
function resolve() {
@@ -160,7 +173,7 @@ function createServer(port, _delete_me_, content, opts) {
// Dynamic Certs
lex.httpsOptions.SNICallback(sni, cb);
};
- var server = https.createServer(opts.httpsOptions);
+ server = https.createServer(opts.httpsOptions);
server.on('error', function (err) {
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) {
opts._externalHost = false;
- opts.sites = argv.sites.split(',').map(function (name) {
+ argv.sites.split(',').map(function (name) {
var nameparts = name.split('|');
var servername = nameparts.shift();
+ var modules;
+
opts._externalHost = opts._externalHost || !/(^|\.)localhost\./.test(servername);
// TODO allow reverse proxy
- return {
- name: servername
- // there should always be a path
- , paths: nameparts.length && nameparts || [
- defaultWebRoot.replace(/(:hostname|:servername)/g, servername)
- ]
- // TODO check for existing custom path before issuing with greenlock
- , _hasCustomPath: !!nameparts.length
- };
+ if (!opts.sites[servername]) {
+ opts.sites[servername] = { paths: {} };
+ }
+
+ if (!nameparts.length) {
+ return;
+ }
+
+ 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'
- , paths: [ path.resolve(__dirname, '..', 'admin', 'public') ]
- , app: path.join(__dirname, 'admin')
- });
- opts.sites.push({
- name: 'localhost.daplie.invalid'
- , paths: [ path.join(__dirname, '..', 'admin', 'public') ]
- , app: path.join(__dirname, 'admin')
- });
+
+ opts.groups = {};
+
+ // 'packages', 'assets', 'com.daplie.caddy'
+ opts.sites['localhost.alpha.daplie.me'] = {
+ // greenlock: {}
+ paths: {
+ '/': { serve: [ path.resolve(__dirname, '..', 'admin', 'public') ] }
+ , '/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.webRoot = defaultWebRoot;
// 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).*/, '');
if (argv.p || argv.port || argv._[0]) {
diff --git a/lib/app.js b/lib/app.js
index 8245933..7a5f38b 100644
--- a/lib/app.js
+++ b/lib/app.js
@@ -1,52 +1,20 @@
'use strict';
module.exports = function (opts) {
- var finalhandler = require('finalhandler');
+ var express = require('express');
+ //var finalhandler = require('finalhandler');
var serveStatic = require('serve-static');
var serveIndex = require('serve-index');
- var assetServer = serveStatic(opts.assetsPath);
+ //var assetServer = serveStatic(opts.assetsPath);
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 pathsMap = {};
+ var serveStaticMap = {};
+ var serveIndexMap = {};
var content = opts.content;
- var server;
+ //var server;
var serveInit;
-
- 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
- };
-
- });
-
- }
+ var app;
function _reloadWrite(data, enc, cb) {
/*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) {
- if ('/api/com.daplie.caddy/init' === req.url) {
- if (!serveInit) {
- serveInit = require('../packages/apis/com.daplie.caddy').create(opts);
+ return require('../packages/apis/com.daplie.caddy').create({
+ PromiseA: PromiseA
+ , 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;
+ }));
+ }
+ , 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;
}
@@ -85,10 +106,123 @@ module.exports = function (opts) {
res.end(content);
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 serveTheStatic() {
server.serve(req, res, function (err) {
@@ -129,6 +263,6 @@ module.exports = function (opts) {
addServer(hostname);
server = hostsMap[hostname] || hostsMap[opts.sites[0].name];
serveStaticly(server);
-
- };
+ */
+ });
};
diff --git a/package.json b/package.json
index e08cfb4..f3049c0 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"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",
"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",
"greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.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",
"minimist": "^1.1.1",
"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",
+ "scmp": "git+https://github.com/freewil/scmp.git#1.x",
"serve-index": "^1.7.0",
"serve-static": "^1.10.0",
"stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#master"
diff --git a/packages/apis/com.daplie.caddy/index.js b/packages/apis/com.daplie.caddy/index.js
index 2ed7a7c..6992ff6 100644
--- a/packages/apis/com.daplie.caddy/index.js
+++ b/packages/apis/com.daplie.caddy/index.js
@@ -1,18 +1,124 @@
'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 jsonParser = bodyParser.json({
inflate: true, limit: '100kb', reviver: null, strict: true /* type, verify */
});
- return function (req, res) {
- jsonParser(req, res, function () {
-
- console.log('req.body', req.body);
-
- res.setHeader('Content-Type', 'application/json;');
- res.end(JSON.stringify({ error: { message: "Not Implemented", code: "E_NO_IMPL" } }));
+ /*
+ var owners;
+ deps.storage.owners.on('set', function (_owners) {
+ owners = _owners;
+ });
+ */
+ 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();
+ });
+ });
+ }
+ };
};
diff --git a/packages/assets/org.oauth3 b/packages/assets/org.oauth3
index eff1fd1..f179cfe 160000
--- a/packages/assets/org.oauth3
+++ b/packages/assets/org.oauth3
@@ -1 +1 @@
-Subproject commit eff1fd11272472e8f6d03ac8b897a9853810672e
+Subproject commit f179cfe3c9553718676db24f1203f67ea0427662
diff --git a/var/.gitkeep b/var/.gitkeep
new file mode 100644
index 0000000..e69de29