merge with letsencrypt branch
This commit is contained in:
commit
28db03ae23
65
bin/walnut
65
bin/walnut
|
@ -1,65 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
require('../walnut.js');
|
|
||||||
/*
|
|
||||||
var c = require('console-plus');
|
|
||||||
console.log = c.log;
|
|
||||||
console.error = c.error;
|
|
||||||
*/
|
|
||||||
|
|
||||||
function eagerLoad() {
|
|
||||||
var PromiseA = require('bluebird').Promise;
|
|
||||||
var promise = PromiseA.resolve();
|
|
||||||
|
|
||||||
[ 'express'
|
|
||||||
, 'request'
|
|
||||||
, 'sqlite3'
|
|
||||||
, 'body-parser'
|
|
||||||
, 'urlrouter'
|
|
||||||
, 'express-lazy'
|
|
||||||
, 'connect-send-error'
|
|
||||||
, 'underscore.string'
|
|
||||||
, 'secret-utils'
|
|
||||||
, 'connect-cors'
|
|
||||||
, 'uuid'
|
|
||||||
, 'connect-recase'
|
|
||||||
, 'escape-string-regexp'
|
|
||||||
, 'connect-query'
|
|
||||||
, 'recase'
|
|
||||||
].forEach(function (name/*, i*/) {
|
|
||||||
promise = promise.then(function () {
|
|
||||||
return new PromiseA(function (resolve/*, reject*/) {
|
|
||||||
setTimeout(function () {
|
|
||||||
require(name);
|
|
||||||
resolve();
|
|
||||||
}, 4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
[ function () {
|
|
||||||
require('body-parser').json();
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
// do not use urlencoded as it enables csrf
|
|
||||||
, function () {
|
|
||||||
require('body-parser').urlencoded();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
].forEach(function (fn) {
|
|
||||||
promise = promise.then(function (thing) {
|
|
||||||
return new PromiseA(function (resolve) {
|
|
||||||
setTimeout(function () {
|
|
||||||
resolve(fn(thing));
|
|
||||||
}, 4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.then(function () {
|
|
||||||
console.log('Eager Loading Complete');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(eagerLoad, 100);
|
|
|
@ -0,0 +1 @@
|
||||||
|
walnut.js
|
|
@ -1 +0,0 @@
|
||||||
walnut
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('../walnut.js');
|
||||||
|
/*
|
||||||
|
var c = require('console-plus');
|
||||||
|
console.log = c.log;
|
||||||
|
console.error = c.error;
|
||||||
|
*/
|
||||||
|
|
||||||
|
function eagerLoad() {
|
||||||
|
var PromiseA = require('bluebird').Promise;
|
||||||
|
var promise = PromiseA.resolve();
|
||||||
|
|
||||||
|
[ 'express'
|
||||||
|
, 'request'
|
||||||
|
, 'sqlite3'
|
||||||
|
, 'body-parser'
|
||||||
|
, 'urlrouter'
|
||||||
|
, 'express-lazy'
|
||||||
|
, 'connect-send-error'
|
||||||
|
, 'underscore.string'
|
||||||
|
, 'secret-utils'
|
||||||
|
, 'connect-cors'
|
||||||
|
, 'uuid'
|
||||||
|
, 'connect-recase'
|
||||||
|
, 'escape-string-regexp'
|
||||||
|
, 'connect-query'
|
||||||
|
, 'recase'
|
||||||
|
].forEach(function (name/*, i*/) {
|
||||||
|
promise = promise.then(function () {
|
||||||
|
return new PromiseA(function (resolve/*, reject*/) {
|
||||||
|
setTimeout(function () {
|
||||||
|
require(name);
|
||||||
|
resolve();
|
||||||
|
}, 4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[ function () {
|
||||||
|
require('body-parser').json();
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
// do not use urlencoded as it enables csrf
|
||||||
|
, function () {
|
||||||
|
require('body-parser').urlencoded();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
].forEach(function (fn) {
|
||||||
|
promise = promise.then(function (thing) {
|
||||||
|
return new PromiseA(function (resolve) {
|
||||||
|
setTimeout(function () {
|
||||||
|
resolve(fn(thing));
|
||||||
|
}, 4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.then(function () {
|
||||||
|
console.log('Eager Loading Complete');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(eagerLoad, 100);
|
|
@ -9,12 +9,46 @@ console.info('arch:', process.arch);
|
||||||
console.info('platform:', process.platform);
|
console.info('platform:', process.platform);
|
||||||
console.info('\n\n\n[MASTER] Welcome to WALNUT!');
|
console.info('\n\n\n[MASTER] Welcome to WALNUT!');
|
||||||
|
|
||||||
|
function tryConf(pathname, def) {
|
||||||
|
try {
|
||||||
|
return require(pathname);
|
||||||
|
} catch(e) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var cluster = require('cluster');
|
var cluster = require('cluster');
|
||||||
//var minWorkers = 2;
|
//var minWorkers = 2;
|
||||||
var numCores = 2; // Math.max(minWorkers, require('os').cpus().length);
|
var numCores = 2; // Math.max(minWorkers, require('os').cpus().length);
|
||||||
var workers = [];
|
var workers = [];
|
||||||
var state = { firstRun: true };
|
var state = { firstRun: true };
|
||||||
|
// TODO Should these be configurable? If so, where?
|
||||||
|
// TODO communicate config with environment vars?
|
||||||
|
var caddy = tryConf(
|
||||||
|
path.join('..', '..', 'config.caddy.json')
|
||||||
|
, { conf: null // __dirname + '/Caddyfile'
|
||||||
|
, bin: null // '/usr/local/bin/caddy'
|
||||||
|
, sitespath: null // path.join(__dirname, 'sites-enabled')
|
||||||
|
, locked: false // true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var useCaddy = require('fs').existsSync(caddy.bin);
|
||||||
|
var info = {
|
||||||
|
type: 'walnut.init'
|
||||||
|
, conf: {
|
||||||
|
protocol: useCaddy ? 'http' : 'https'
|
||||||
|
, externalPort: 443
|
||||||
|
, externalPortInsecure: 80 // TODO externalInsecurePort
|
||||||
|
, localPort: process.argv[2] || (useCaddy ? 4080 : 443) // system / local network
|
||||||
|
, insecurePort: process.argv[3] || (useCaddy ? 80 : 80) // meh
|
||||||
|
, certPaths: useCaddy ? null : [
|
||||||
|
path.join(__dirname, '..', '..', 'certs', 'live')
|
||||||
|
, path.join(__dirname, '..', '..', 'letsencrypt', 'live')
|
||||||
|
]
|
||||||
|
, trustProxy: useCaddy ? true : false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function fork() {
|
function fork() {
|
||||||
if (workers.length < numCores) {
|
if (workers.length < numCores) {
|
||||||
|
@ -26,53 +60,15 @@ cluster.on('online', function (worker) {
|
||||||
console.info('[MASTER] Worker ' + worker.process.pid + ' is online');
|
console.info('[MASTER] Worker ' + worker.process.pid + ' is online');
|
||||||
fork();
|
fork();
|
||||||
|
|
||||||
var config = {
|
|
||||||
externalPort: 443 // world accessible
|
|
||||||
, externalPortInsecure: 80 // world accessible
|
|
||||||
// TODO externalInsecurePort?
|
|
||||||
, locked: false // TODO XXX
|
|
||||||
// XXX
|
|
||||||
// TODO needs mappings from db
|
|
||||||
// TODO autoconfig Caddy caddy
|
|
||||||
// XXX
|
|
||||||
, caddy: {
|
|
||||||
conf: __dirname + '/Caddyfile'
|
|
||||||
, bin: '/usr/local/bin/caddy'
|
|
||||||
, sitespath: path.join(__dirname, 'sites-enabled')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var useCaddy = require('fs').existsSync(config.caddy.bin);
|
|
||||||
var caddy;
|
|
||||||
|
|
||||||
config.localPort = process.argv[2] || (useCaddy ? 4080 : 443); // system / local network
|
|
||||||
config.insecurePort = process.argv[3] || (useCaddy ? 80 : 80); // meh
|
|
||||||
if (state.firstRun) {
|
if (state.firstRun) {
|
||||||
state.firstRun = false;
|
state.firstRun = false;
|
||||||
if (useCaddy) {
|
if (useCaddy) {
|
||||||
caddy = require('../lib/spawn-caddy').create(config);
|
caddy = require('../lib/spawn-caddy').create(caddy);
|
||||||
// relies on { localPort, locked }
|
// relies on { localPort, locked }
|
||||||
caddy.spawn(config);
|
caddy.spawn(caddy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO XXX Should these be configurable? If so, where?
|
|
||||||
var certPaths = [
|
|
||||||
path.join(__dirname, '..', '..', 'certs', 'live')
|
|
||||||
, path.join(__dirname, '..', '..', 'letsencrypt', 'live')
|
|
||||||
];
|
|
||||||
// TODO communicate config with environment vars?
|
|
||||||
var info = {
|
|
||||||
type: 'walnut.init'
|
|
||||||
, conf: {
|
|
||||||
protocol: useCaddy ? 'http' : 'https'
|
|
||||||
, externalPort: config.externalPort
|
|
||||||
, localPort: config.localPort
|
|
||||||
, insecurePort: config.insecurePort
|
|
||||||
, certPaths: useCaddy ? null : certPaths
|
|
||||||
, trustProxy: useCaddy ? true : false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function touchMaster(msg) {
|
function touchMaster(msg) {
|
||||||
if ('walnut.webserver.listening' !== msg.type) {
|
if ('walnut.webserver.listening' !== msg.type) {
|
||||||
console.warn('[MASTER] received unexpected message from worker');
|
console.warn('[MASTER] received unexpected message from worker');
|
||||||
|
@ -83,19 +79,19 @@ cluster.on('online', function (worker) {
|
||||||
// calls init if init has not been called
|
// calls init if init has not been called
|
||||||
state.caddy = caddy;
|
state.caddy = caddy;
|
||||||
state.workers = workers;
|
state.workers = workers;
|
||||||
require('../lib/master').touch(config, state).then(function (results) {
|
require('../lib/master').touch(info.conf, state).then(function (results) {
|
||||||
//var memstore = results.memstore;
|
//var memstore = results.memstore;
|
||||||
var sqlstore = results.sqlstore;
|
var sqlstore = results.sqlstore;
|
||||||
info.type = 'walnut.webserver.onrequest';
|
info.type = 'walnut.webserver.onrequest';
|
||||||
// TODO let this load after server is listening
|
// TODO let this load after server is listening
|
||||||
info.conf['org.oauth3.consumer'] = config['org.oauth3.consumer'];
|
info.conf['org.oauth3.consumer'] = results['org.oauth3.consumer'];
|
||||||
info.conf['org.oauth3.provider'] = config['org.oauth3.provider'];
|
info.conf['org.oauth3.provider'] = results['org.oauth3.provider'];
|
||||||
info.conf.keys = config.keys;
|
info.conf.keys = results.keys;
|
||||||
info.conf.memstoreSock = config.memstoreSock;
|
//info.conf.memstoreSock = config.memstoreSock;
|
||||||
info.conf.sqlite3Sock = config.sqlite3Sock;
|
//info.conf.sqlite3Sock = config.sqlite3Sock;
|
||||||
// TODO get this from db config instead
|
// TODO get this from db config instead
|
||||||
info.conf.privkey = config.privkey;
|
//info.conf.privkey = config.privkey;
|
||||||
info.conf.pubkey = config.pubkey;
|
//info.conf.pubkey = config.pubkey;
|
||||||
info.conf.redirects = [
|
info.conf.redirects = [
|
||||||
{ "ip": false, "id": "*", "value": false } // default no-www
|
{ "ip": false, "id": "*", "value": false } // default no-www
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ module.exports.create = function (opts) {
|
||||||
var id = '0';
|
var id = '0';
|
||||||
var promiseApp;
|
var promiseApp;
|
||||||
|
|
||||||
function createAndBindInsecure(message, cb) {
|
function createAndBindInsecure(lex, message, cb) {
|
||||||
// TODO conditional if 80 is being served by caddy
|
// TODO conditional if 80 is being served by caddy
|
||||||
require('../lib/insecure-server').create(message.conf.externalPort, message.conf.insecurePort, message, function (err, webserver) {
|
require('../lib/insecure-server').create(lex, message.conf.externalPort, message.conf.insecurePort, message, function (err, webserver) {
|
||||||
console.info("#" + id + " Listening on http://" + webserver.address().address + ":" + webserver.address().port, '\n');
|
console.info("#" + id + " Listening on http://" + webserver.address().address + ":" + webserver.address().port, '\n');
|
||||||
|
|
||||||
// we are returning the promise result to the caller
|
// we are returning the promise result to the caller
|
||||||
|
@ -14,9 +14,48 @@ module.exports.create = function (opts) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createLe(conf) {
|
||||||
|
var LEX = require('letsencrypt-express');
|
||||||
|
var lex = LEX.create({
|
||||||
|
configDir: conf.letsencrypt.configDir // i.e. __dirname + '/letsencrypt.config'
|
||||||
|
, approveRegistration: function (hostname, cb) {
|
||||||
|
cb(null, {
|
||||||
|
domains: [hostname] // TODO handle www and bare on the same cert
|
||||||
|
, email: conf.letsencrypt.email
|
||||||
|
, agreeTos: conf.letsencrypt.agreeTos
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
letsencrypt.getConfig({ domains: [domain] }, function (err, config) {
|
||||||
|
if (!(config && config.checkpoints >= 0)) {
|
||||||
|
cb(err, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, {
|
||||||
|
email: config.email
|
||||||
|
// can't remember which it is, but the pyconf is different that the regular variable
|
||||||
|
, agreeTos: config.tos || config.agree || config.agreeTos
|
||||||
|
, server: config.server || LE.productionServerUrl
|
||||||
|
, domains: config.domains || [domain]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//var letsencrypt = lex.letsencrypt;
|
||||||
|
|
||||||
|
return lex;
|
||||||
|
}
|
||||||
|
|
||||||
function createAndBindServers(message, cb) {
|
function createAndBindServers(message, cb) {
|
||||||
|
var lex;
|
||||||
|
|
||||||
|
if (message.conf.letsencrypt) {
|
||||||
|
lex = createLe(message.conf);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE that message.conf[x] will be overwritten when the next message comes in
|
// NOTE that message.conf[x] will be overwritten when the next message comes in
|
||||||
require('../lib/local-server').create(message.conf.certPaths, message.conf.localPort, message, function (err, webserver) {
|
require('../lib/local-server').create(lex, message.conf.certPaths, message.conf.localPort, message, function (err, webserver) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('[ERROR] worker.js');
|
console.error('[ERROR] worker.js');
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
|
@ -27,7 +66,7 @@ module.exports.create = function (opts) {
|
||||||
|
|
||||||
// we don't need time to pass, just to be able to return
|
// we don't need time to pass, just to be able to return
|
||||||
process.nextTick(function () {
|
process.nextTick(function () {
|
||||||
createAndBindInsecure(message, cb);
|
createAndBindInsecure(lex, message, cb);
|
||||||
});
|
});
|
||||||
|
|
||||||
// we are returning the promise result to the caller
|
// we are returning the promise result to the caller
|
||||||
|
@ -67,7 +106,7 @@ module.exports.create = function (opts) {
|
||||||
|
|
||||||
process.removeListener('message', initWebServer);
|
process.removeListener('message', initWebServer);
|
||||||
|
|
||||||
resolve(require('../lib/worker').create(webserver, srvmsg));
|
resolve(require('../lib/worker').create(webserver, srvmsg.conf));
|
||||||
}
|
}
|
||||||
|
|
||||||
process.send({ type: 'walnut.webserver.listening' });
|
process.send({ type: 'walnut.webserver.listening' });
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module.exports.create = function (securePort, insecurePort, info, serverCallback) {
|
module.exports.create = function (lex, securePort, insecurePort, info, serverCallback) {
|
||||||
var PromiseA = require('bluebird').Promise;
|
var PromiseA = require('bluebird').Promise;
|
||||||
var appPromise;
|
var appPromise;
|
||||||
//var app;
|
//var app;
|
||||||
|
@ -101,7 +101,13 @@ module.exports.create = function (securePort, insecurePort, info, serverCallback
|
||||||
appPromise = serverCallback(null, insecureServer);
|
appPromise = serverCallback(null, insecureServer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (lex) {
|
||||||
|
var LEX = require('letsencrypt-express');
|
||||||
|
insecureServer.on('request', LEX.createAcmeResponder(lex, redirectHttps));
|
||||||
|
} else {
|
||||||
insecureServer.on('request', redirectHttps);
|
insecureServer.on('request', redirectHttps);
|
||||||
|
}
|
||||||
|
|
||||||
return PromiseA.resolve(insecureServer);
|
return PromiseA.resolve(insecureServer);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
// Note the odd use of callbacks (instead of promises) here
|
// Note the odd use of callbacks (instead of promises) here
|
||||||
// It's to avoid loading bluebird yet (see sni-server.js for explanation)
|
// It's to avoid loading bluebird yet (see sni-server.js for explanation)
|
||||||
module.exports.create = function (certPaths, port, info, serverCallback) {
|
module.exports.create = function (lex, certPaths, port, info, serverCallback) {
|
||||||
function initServer(err, server) {
|
function initServer(err, server) {
|
||||||
var app;
|
var app;
|
||||||
var promiseApp;
|
var promiseApp;
|
||||||
|
@ -29,7 +29,7 @@ module.exports.create = function (certPaths, port, info, serverCallback) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Get up and listening as absolutely quickly as possible
|
// Get up and listening as absolutely quickly as possible
|
||||||
server.on('request', function (req, res) {
|
function onRequest(req, res) {
|
||||||
// this is a hot piece of code, so we cache the result
|
// this is a hot piece of code, so we cache the result
|
||||||
if (app) {
|
if (app) {
|
||||||
app(req, res);
|
app(req, res);
|
||||||
|
@ -41,11 +41,18 @@ module.exports.create = function (certPaths, port, info, serverCallback) {
|
||||||
app = _app;
|
app = _app;
|
||||||
app(req, res);
|
app(req, res);
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (lex) {
|
||||||
|
var LEX = require('letsencrypt-express');
|
||||||
|
server.on('request', LEX.createAcmeResponder(lex, onRequest));
|
||||||
|
} else {
|
||||||
|
server.on('request', onRequest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (certPaths) {
|
if (certPaths) {
|
||||||
require('./sni-server').create(certPaths, initServer);
|
require('./sni-server').create(lex, certPaths, initServer);
|
||||||
} else {
|
} else {
|
||||||
initServer(null, require('http').createServer());
|
initServer(null, require('http').createServer());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,407 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var PromiseA = require('bluebird');
|
|
||||||
|
|
||||||
module.exports.inject = function (conf, app, pkgConf, pkgDeps) {
|
|
||||||
var scoper = require('app-scoped-ids');
|
|
||||||
var inProcessCache = {};
|
|
||||||
var createClientFactory = require('sqlite3-cluster/client').createClientFactory;
|
|
||||||
var dir = [
|
|
||||||
{ tablename: 'codes'
|
|
||||||
, idname: 'uuid'
|
|
||||||
, indices: ['createdAt']
|
|
||||||
}
|
|
||||||
, { tablename: 'logins' // coolaj86, coolaj86@gmail.com, +1-317-426-6525
|
|
||||||
, idname: 'hashId'
|
|
||||||
//, relations: [{ tablename: 'secrets', id: 'hashid', fk: 'loginId' }]
|
|
||||||
, indices: ['createdAt', 'type', 'node']
|
|
||||||
//, immutable: false
|
|
||||||
}
|
|
||||||
, { tablename: 'verifications'
|
|
||||||
, idname: 'hashId' // hash(date + node)
|
|
||||||
//, relations: [{ tablename: 'secrets', id: 'hashid', fk: 'loginId' }]
|
|
||||||
, indices: ['createdAt', 'nodeId']
|
|
||||||
//, immutable: true
|
|
||||||
}
|
|
||||||
, { tablename: 'secrets'
|
|
||||||
, idname: 'hashId' // hash(node + secret)
|
|
||||||
, indices: ['createdAt']
|
|
||||||
//, immutable: true
|
|
||||||
}
|
|
||||||
, { tablename: 'recoveryNodes' // just for 1st-party logins
|
|
||||||
, idname: 'hashId' //
|
|
||||||
// TODO how transmit that something should be deleted / disabled?
|
|
||||||
, indices: ['createdAt', 'updatedAt', 'loginHash', 'recoveryNode', 'deleted']
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Accounts
|
|
||||||
//
|
|
||||||
, { tablename: 'accounts_logins'
|
|
||||||
, idname: 'id' // hash(accountId + loginId)
|
|
||||||
, indices: ['createdAt', 'revokedAt', 'loginId', 'accountId']
|
|
||||||
}
|
|
||||||
, { tablename: 'accounts'
|
|
||||||
, idname: 'id' // crypto random id? or hash(name) ?
|
|
||||||
, unique: ['name']
|
|
||||||
, indices: ['createdAt', 'updatedAt', 'deletedAt', 'name', 'displayName']
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// OAuth3
|
|
||||||
//
|
|
||||||
, { tablename: 'private_key'
|
|
||||||
, idname: 'id'
|
|
||||||
, indices: ['createdAt']
|
|
||||||
}
|
|
||||||
, { tablename: 'oauth_clients'
|
|
||||||
, idname: 'id'
|
|
||||||
, indices: ['createdAt', 'updatedAt', 'accountId']
|
|
||||||
, hasMany: ['apiKeys'] // TODO
|
|
||||||
, belongsTo: ['account']
|
|
||||||
, schema: function () {
|
|
||||||
return {
|
|
||||||
test: true
|
|
||||||
, insecure: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, { tablename: 'api_keys'
|
|
||||||
, idname: 'id'
|
|
||||||
, indices: ['createdAt', 'updatedAt', 'oauthClientId']
|
|
||||||
, belongsTo: ['oauthClient'] // TODO pluralization
|
|
||||||
, schema: function () {
|
|
||||||
return {
|
|
||||||
test: true
|
|
||||||
, insecure: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, { tablename: 'tokens' // note that a token functions as a session
|
|
||||||
, idname: 'id'
|
|
||||||
, indices: ['createdAt', 'updatedAt', 'expiresAt', 'revokedAt', 'oauthClientId', 'loginId', 'accountId']
|
|
||||||
}
|
|
||||||
, { tablename: 'grants'
|
|
||||||
, idname: 'id' // sha256(scope + oauthClientId + (accountId || loginId))
|
|
||||||
, indices: ['createdAt', 'updatedAt', 'oauthClientId', 'loginId', 'accountId']
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
function getAppScopedControllers(experienceId) {
|
|
||||||
if (inProcessCache[experienceId]) {
|
|
||||||
return PromiseA.resolve(inProcessCache[experienceId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var mq = require('masterquest');
|
|
||||||
var path = require('path');
|
|
||||||
// TODO how can we encrypt this?
|
|
||||||
var systemFactory = createClientFactory({
|
|
||||||
// TODO only complain if the values are different
|
|
||||||
algorithm: 'aes'
|
|
||||||
, bits: 128
|
|
||||||
, mode: 'cbc'
|
|
||||||
, dirname: path.join(__dirname, '..', '..', 'var') // TODO info.conf
|
|
||||||
//, prefix: appname.replace(/\//g, ':') // 'com.example.'
|
|
||||||
//, dbname: 'cluster'
|
|
||||||
, suffix: ''
|
|
||||||
, ext: '.sqlcipher'
|
|
||||||
, sock: conf.sqlite3Sock
|
|
||||||
, ipcKey: conf.ipcKey
|
|
||||||
});
|
|
||||||
var clientFactory = createClientFactory({
|
|
||||||
// TODO only complain if the values are different
|
|
||||||
dirname: path.join(__dirname, '..', '..', 'var') // TODO info.conf
|
|
||||||
, prefix: 'com.oauth3' // 'com.example.'
|
|
||||||
//, dbname: 'config'
|
|
||||||
, suffix: ''
|
|
||||||
, ext: '.sqlite3'
|
|
||||||
, sock: conf.sqlite3Sock
|
|
||||||
, ipcKey: conf.ipcKey
|
|
||||||
});
|
|
||||||
|
|
||||||
inProcessCache[experienceId] = systemFactory.create({
|
|
||||||
init: true
|
|
||||||
//, key: '00000000000000000000000000000000'
|
|
||||||
, dbname: experienceId // 'com.example.'
|
|
||||||
}).then(function (sqlStore) {
|
|
||||||
//var db = factory.
|
|
||||||
return mq.wrap(sqlStore, dir).then(function (models) {
|
|
||||||
return require('./oauthclient-microservice/lib/sign-token').create(models.PrivateKey).init().then(function (signer) {
|
|
||||||
var CodesCtrl = require('authcodes').create(models.Codes);
|
|
||||||
/* models = { Logins, Verifications } */
|
|
||||||
var LoginsCtrl = require('./authentication-microservice/lib/logins').create({}, CodesCtrl, models);
|
|
||||||
/* models = { ApiKeys, OauthClients } */
|
|
||||||
var ClientsCtrl = require('./oauthclient-microservice/lib/oauthclients').createController({}, models, signer);
|
|
||||||
|
|
||||||
return {
|
|
||||||
Codes: CodesCtrl
|
|
||||||
, Logins: LoginsCtrl
|
|
||||||
, Clients: ClientsCtrl
|
|
||||||
, SqlFactory: clientFactory
|
|
||||||
, models: models
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}).then(function (ctrls) {
|
|
||||||
inProcessCache[experienceId] = ctrls;
|
|
||||||
return ctrls;
|
|
||||||
});
|
|
||||||
|
|
||||||
return inProcessCache[experienceId];
|
|
||||||
}
|
|
||||||
|
|
||||||
//var jwsUtils = require('./lib/jws-utils').create(signer);
|
|
||||||
var CORS = require('connect-cors');
|
|
||||||
var cors = CORS({ credentials: true, headers: [
|
|
||||||
'X-Requested-With'
|
|
||||||
, 'X-HTTP-Method-Override'
|
|
||||||
, 'Content-Type'
|
|
||||||
, 'Accept'
|
|
||||||
, 'Authorization'
|
|
||||||
], methods: [ "GET", "POST", "PATCH", "PUT", "DELETE" ] });
|
|
||||||
|
|
||||||
// Allows CORS access to API with ?access_token=
|
|
||||||
// TODO Access-Control-Max-Age: 600
|
|
||||||
// TODO How can we help apps handle this? token?
|
|
||||||
// TODO allow apps to configure trustedDomains, auth, etc
|
|
||||||
|
|
||||||
//function weakDecipher(secret, val) { return require('./weak-crypt').weakDecipher(val, secret); }
|
|
||||||
|
|
||||||
//
|
|
||||||
// Generic Session / Login / Account Routes
|
|
||||||
//
|
|
||||||
function parseAccessToken(req, opts) {
|
|
||||||
var token;
|
|
||||||
var parts;
|
|
||||||
var scheme;
|
|
||||||
var credentials;
|
|
||||||
|
|
||||||
if (req.headers && req.headers.authorization) {
|
|
||||||
parts = req.headers.authorization.split(' ');
|
|
||||||
|
|
||||||
if (parts.length !== 2) {
|
|
||||||
return PromiseA.reject(new Error("malformed Authorization header"));
|
|
||||||
}
|
|
||||||
|
|
||||||
scheme = parts[0];
|
|
||||||
credentials = parts[1];
|
|
||||||
|
|
||||||
if (-1 !== (opts && opts.schemes || ['token', 'bearer']).indexOf(scheme.toLowerCase())) {
|
|
||||||
token = credentials;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.body && req.body.access_token) {
|
|
||||||
if (token) { PromiseA.reject(new Error("token exists in header and body")); }
|
|
||||||
token = req.body.access_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO disallow query with req.method === 'GET'
|
|
||||||
// (cookies should be used for protected static assets)
|
|
||||||
if (req.query && req.query.access_token) {
|
|
||||||
if (token) { PromiseA.reject(new Error("token already exists in either header or body and also in query")); }
|
|
||||||
token = req.query.access_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
err = new Error(challenge());
|
|
||||||
err.code = 'E_BEARER_REALM';
|
|
||||||
|
|
||||||
if (!token) { return PromiseA.reject(err); }
|
|
||||||
*/
|
|
||||||
|
|
||||||
return PromiseA.resolve(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getClient(req, token, priv, Controllers) {
|
|
||||||
if (!token) {
|
|
||||||
token = req.oauth3.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheId = '_' + token.k + 'Client';
|
|
||||||
|
|
||||||
if (priv[cacheId]) {
|
|
||||||
return PromiseA.resolve(priv[cacheId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO could get client directly by token.app (id of client)
|
|
||||||
priv[cacheId] = Controllers.Clients.login(null, token.k).then(function (apiKey) {
|
|
||||||
if (!apiKey) {
|
|
||||||
return PromiseA.reject(new Error("Client no longer valid"));
|
|
||||||
}
|
|
||||||
|
|
||||||
priv[cacheId + 'Key'] = apiKey;
|
|
||||||
priv[cacheId] = apiKey.oauthClient;
|
|
||||||
|
|
||||||
return apiKey.oauthClient;
|
|
||||||
});
|
|
||||||
|
|
||||||
return priv[cacheId];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAccountsByLogin(req, token, priv, Controllers, loginId, decrypt) {
|
|
||||||
return getClient(req, req.oauth.token, priv).then(function (oauthClient) {
|
|
||||||
if (decrypt) {
|
|
||||||
loginId = scoper.unscope(loginId, oauthClient.secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Controllers.models.AccountsLogins.find({ loginId: loginId }).then(function (accounts) {
|
|
||||||
return PromiseA.all(accounts.map(function (obj) {
|
|
||||||
return Controllers.models.Accounts.get(obj.accountId)/*.then(function (account) {
|
|
||||||
account.appScopedId = weakCipher(oauthClient.secret, account.id);
|
|
||||||
return account;
|
|
||||||
})*/;
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAccountsByArray(req, Controllers, arr) {
|
|
||||||
return PromiseA.all(arr.map(function (accountId) {
|
|
||||||
return Controllers.models.Accounts.get(accountId.id || accountId);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAccounts(req, token, priv, Controllers) {
|
|
||||||
if (!token) {
|
|
||||||
token = req.oauth3.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
var err;
|
|
||||||
|
|
||||||
if (priv._accounts) {
|
|
||||||
return PromiseA.resolve(priv._accounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((req.oauth3.token.idx || req.oauth3.token.usr) && ('password' === req.oauth3.token.grt || 'login' === req.oauth3.token.as)) {
|
|
||||||
priv._accounts = getAccountsByLogin(req, req.oauth3.token, priv, Controllers, (req.oauth3.token.idx || req.oauth3.token.usr), !!req.oauth3.token.idx);
|
|
||||||
} else if (req.oauth3.token.axs && req.oauth3.token.axs.length || req.oauth3.token.acx) {
|
|
||||||
req.oauth3._accounts = getAccountsByArray(req, Controllers, req.oauth3.token.axs && req.oauth3.token.axs.length && req.oauth3.token.axs || [req.oauth3.token.acx]);
|
|
||||||
} else {
|
|
||||||
err = new Error("neither login nor accounts were specified");
|
|
||||||
err.code = "E_NO_AUTHZ";
|
|
||||||
req.oauth3._accounts = PromiseA.reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
req.oauth3._accounts.then(function (accounts) {
|
|
||||||
req.oauth3._accounts = accounts;
|
|
||||||
|
|
||||||
return accounts;
|
|
||||||
});
|
|
||||||
|
|
||||||
return req.oauth3._accounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLoginId(req, token, priv/*, Controllers*/) {
|
|
||||||
if (!token) {
|
|
||||||
token = req.oauth3.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheId = '_' + token.idx + 'LoginId';
|
|
||||||
|
|
||||||
if (priv[cacheId]) {
|
|
||||||
return PromiseA.resolve(priv[cacheId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// this ends up defeating part of the purpose of JWT (few database calls)
|
|
||||||
// perhaps the oauthClient secret should be sent, encrypted with a master key,
|
|
||||||
// with the request? Or just mash the oauthClient secret with the loginId
|
|
||||||
// and encrypt with the master key?
|
|
||||||
priv._loginId = getClient(req, token, priv).then(function (oauthClient) {
|
|
||||||
var loginId;
|
|
||||||
|
|
||||||
if (token.idx) {
|
|
||||||
loginId = scoper.unscope(token.idx, oauthClient.secret);
|
|
||||||
} else {
|
|
||||||
loginId = token.usr;
|
|
||||||
}
|
|
||||||
|
|
||||||
priv[cacheId] = loginId;
|
|
||||||
|
|
||||||
return loginId;
|
|
||||||
});
|
|
||||||
|
|
||||||
return priv[cacheId];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLogin(req, token, priv, Controllers) {
|
|
||||||
if (!token) {
|
|
||||||
token = req.oauth3.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheId = '_' + token.idx + 'Login';
|
|
||||||
|
|
||||||
if (priv[cacheId]) {
|
|
||||||
return PromiseA.resolve(priv[cacheId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
priv[cacheId] = getLoginId(req, token, priv).then(function (loginId) {
|
|
||||||
// DB.Logins.get(hashId)
|
|
||||||
return Controllers.Logins.rawGet(loginId).then(function (login) {
|
|
||||||
priv[cacheId] = login;
|
|
||||||
|
|
||||||
return login;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return priv[cacheId];
|
|
||||||
}
|
|
||||||
|
|
||||||
function attachOauth3(req, res, next) {
|
|
||||||
var privs = {};
|
|
||||||
req.oauth3 = {};
|
|
||||||
|
|
||||||
getAppScopedControllers(req.experienceId).then(function (Controllers) {
|
|
||||||
|
|
||||||
return parseAccessToken(req).then(function (token) {
|
|
||||||
if (!token) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var jwt = require('jsonwebtoken');
|
|
||||||
var data = jwt.decode(token);
|
|
||||||
var err;
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
err = new Error('not a json web token');
|
|
||||||
err.code = 'E_NOT_JWT';
|
|
||||||
res.send({
|
|
||||||
error: err.code
|
|
||||||
, error_description: err.message
|
|
||||||
, error_url: 'https://oauth3.org/docs/errors#' + (err.code || 'E_UNKNOWN_EXCEPTION')
|
|
||||||
});
|
|
||||||
// PromiseA.reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
req.oauth3.token = token;
|
|
||||||
|
|
||||||
req.oauth3.getLoginId = function (token) {
|
|
||||||
getLoginId(req, token || req.oauth3.token, privs, Controllers);
|
|
||||||
};
|
|
||||||
|
|
||||||
req.oauth3.getLogin = function (token) {
|
|
||||||
getLogin(req, token || req.oauth3.token, privs, Controllers);
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO modify prototypes?
|
|
||||||
req.oauth3.getClient = function (token) {
|
|
||||||
getClient(req, token || req.oauth3.token, privs, Controllers);
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO req.oauth3.getAccountIds
|
|
||||||
req.oauth3.getAccounts = function (token) {
|
|
||||||
getAccounts(req, token || req.oauth3.token, privs, Controllers);
|
|
||||||
};
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use('/', cors);
|
|
||||||
|
|
||||||
app.use('/', attachOauth3);
|
|
||||||
};
|
|
|
@ -110,7 +110,7 @@ function loadPages(pkgConf, packagedPage, req, res, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getApi(conf, pkgConf, pkgDeps, packagedApi) {
|
function getApi(conf, pkgConf, pkgDeps, packagedApi) {
|
||||||
var PromiseA = require('bluebird');
|
var PromiseA = pkgDeps.Promise;
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var pkgpath = path.join(pkgConf.apipath, packagedApi.id/*, (packagedApi.api.version || '')*/);
|
var pkgpath = path.join(pkgConf.apipath, packagedApi.id/*, (packagedApi.api.version || '')*/);
|
||||||
|
|
||||||
|
@ -162,6 +162,9 @@ function getApi(conf, pkgConf, pkgDeps, packagedApi) {
|
||||||
packagedApi._api_app = myApp;
|
packagedApi._api_app = myApp;
|
||||||
|
|
||||||
//require('./oauth3-auth').inject(conf, packagedApi._api, pkgConf, pkgDeps);
|
//require('./oauth3-auth').inject(conf, packagedApi._api, pkgConf, pkgDeps);
|
||||||
|
pkgDeps.getOauth3Controllers =
|
||||||
|
packagedApi._getOauth3Controllers = require('oauthcommon/example-oauthmodels').create(conf).getControllers;
|
||||||
|
require('oauthcommon').inject(packagedApi._getOauth3Controllers, packagedApi._api, pkgConf, pkgDeps);
|
||||||
|
|
||||||
// DEBUG
|
// DEBUG
|
||||||
//
|
//
|
||||||
|
@ -251,16 +254,18 @@ function layerItUp(pkgConf, router, req, res, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function runApi(opts, router, req, res, next) {
|
function runApi(opts, router, req, res, next) {
|
||||||
|
var path = require('path');
|
||||||
var pkgConf = opts.config;
|
var pkgConf = opts.config;
|
||||||
var pkgDeps = opts.deps;
|
var pkgDeps = opts.deps;
|
||||||
//var Services = opts.Services;
|
//var Services = opts.Services;
|
||||||
var packagedApi;
|
var packagedApi;
|
||||||
|
var pathname;
|
||||||
|
|
||||||
// TODO compile packagesMap
|
// TODO compile packagesMap
|
||||||
// TODO people may want to use the framework in a non-framework way (i.e. to conceal the module name)
|
// TODO people may want to use the framework in a non-framework way (i.e. to conceal the module name)
|
||||||
router.packagedApis.some(function (_packagedApi) {
|
router.packagedApis.some(function (_packagedApi) {
|
||||||
// console.log('[DEBUG _packagedApi.id]', _packagedApi.id);
|
// console.log('[DEBUG _packagedApi.id]', _packagedApi.id);
|
||||||
var pathname = router.pathname;
|
pathname = router.pathname;
|
||||||
if ('/' === pathname) {
|
if ('/' === pathname) {
|
||||||
pathname = '';
|
pathname = '';
|
||||||
}
|
}
|
||||||
|
@ -290,7 +295,7 @@ function runApi(opts, router, req, res, next) {
|
||||||
// TODO this identifier may need to be non-deterministic as to transfer if a domain name changes but is still the "same" app
|
// TODO this identifier may need to be non-deterministic as to transfer if a domain name changes but is still the "same" app
|
||||||
// (i.e. a company name change. maybe auto vs manual register - just like oauth3?)
|
// (i.e. a company name change. maybe auto vs manual register - just like oauth3?)
|
||||||
// NOTE: probably best to alias the name logically
|
// NOTE: probably best to alias the name logically
|
||||||
, value: (req.hostname + req.pathname).replace(/\/$/, '')
|
, value: (path.join(req.hostname, pathname || '')).replace(/\/$/, '')
|
||||||
});
|
});
|
||||||
Object.defineProperty(req, 'escapedExperienceId', {
|
Object.defineProperty(req, 'escapedExperienceId', {
|
||||||
enumerable: true
|
enumerable: true
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
// require everything as lazily as possible until our server
|
// require everything as lazily as possible until our server
|
||||||
// is actually listening on the socket. Bluebird is heavy.
|
// is actually listening on the socket. Bluebird is heavy.
|
||||||
// Even the built-in modules can take dozens of milliseconds to require
|
// Even the built-in modules can take dozens of milliseconds to require
|
||||||
module.exports.create = function (certPaths, serverCallback) {
|
module.exports.create = function (lex, certPaths, serverCallback) {
|
||||||
// Recognize that this secureContexts cache is local to this CPU core
|
// Recognize that this secureContexts cache is local to this CPU core
|
||||||
var secureContexts = {};
|
var secureContexts = {};
|
||||||
|
var ciphers = 'ECDH+AESGCM:DH+AESGCM:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS:!AES256';
|
||||||
|
|
||||||
function createSecureServer() {
|
function createSecureServer() {
|
||||||
var domainname = 'www.example.com';
|
var domainname = 'www.example.com';
|
||||||
|
@ -21,7 +22,7 @@ module.exports.create = function (certPaths, serverCallback) {
|
||||||
// https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
|
// https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
|
||||||
// https://nodejs.org/api/tls.html
|
// https://nodejs.org/api/tls.html
|
||||||
// removed :ECDH+AES256:DH+AES256 and added :!AES256 because AES-256 wastes CPU
|
// removed :ECDH+AES256:DH+AES256 and added :!AES256 because AES-256 wastes CPU
|
||||||
, ciphers: 'ECDH+AESGCM:DH+AESGCM:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS:!AES256'
|
, ciphers: ciphers
|
||||||
, honorCipherOrder: true
|
, honorCipherOrder: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,5 +44,15 @@ module.exports.create = function (certPaths, serverCallback) {
|
||||||
serverCallback(null, require('https').createServer(secureOpts));
|
serverCallback(null, require('https').createServer(secureOpts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createLeServer() {
|
||||||
|
lex.httpsOptions.ciphers = ciphers;
|
||||||
|
lex.httpsOptions.honorCipherOrder = true;
|
||||||
|
serverCallback(null, require('https').createServer(lex.httpsOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lex) {
|
||||||
|
createLeServer();
|
||||||
|
} else {
|
||||||
createSecureServer();
|
createSecureServer();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function tplCaddyfile(conf) {
|
function tplCaddyfile(caddyConf) {
|
||||||
var contents = [];
|
var contents = [];
|
||||||
|
|
||||||
conf.caddy.domains.forEach(function (hostname) {
|
caddyConf.domains.forEach(function (hostname) {
|
||||||
var content = "";
|
var content = "";
|
||||||
var pagesname = hostname;
|
var pagesname = hostname;
|
||||||
|
|
||||||
|
@ -15,14 +15,14 @@ function tplCaddyfile(conf) {
|
||||||
+ "/srv/walnut/certs/live/" + hostname + "/privkey.pem\n"
|
+ "/srv/walnut/certs/live/" + hostname + "/privkey.pem\n"
|
||||||
;
|
;
|
||||||
|
|
||||||
if (conf.locked) {
|
if (caddyConf.locked) {
|
||||||
content += " root /srv/walnut/init.public/\n";
|
content += " root /srv/walnut/init.public/\n";
|
||||||
} else {
|
} else {
|
||||||
content += " root " + conf.caddy.sitespath + "/" + pagesname + "/\n";
|
content += " root " + caddyConf.sitespath + "/" + pagesname + "/\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
content +=
|
content +=
|
||||||
" proxy /api http://localhost:" + conf.localPort.toString() + " {\n"
|
" proxy /api http://localhost:" + caddyConf.localPort.toString() + " {\n"
|
||||||
+ " proxy_header Host {host}\n"
|
+ " proxy_header Host {host}\n"
|
||||||
+ " proxy_header X-Forwarded-Host {host}\n"
|
+ " proxy_header X-Forwarded-Host {host}\n"
|
||||||
+ " proxy_header X-Forwarded-Proto {scheme}\n"
|
+ " proxy_header X-Forwarded-Proto {scheme}\n"
|
||||||
|
@ -37,10 +37,10 @@ function tplCaddyfile(conf) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.tplCaddyfile = tplCaddyfile;
|
module.exports.tplCaddyfile = tplCaddyfile;
|
||||||
module.exports.create = function (config) {
|
module.exports.create = function (caddyConf) {
|
||||||
var spawn = require('child_process').spawn;
|
var spawn = require('child_process').spawn;
|
||||||
var caddyBin = config.caddy.bin;
|
var caddyBin = caddyConf.bin;
|
||||||
var caddyConf = config.caddy.conf;
|
var caddyfilePath = caddyConf.conf;
|
||||||
// TODO put up a booting / lock screen on boot
|
// TODO put up a booting / lock screen on boot
|
||||||
// and wait for all to be grabbed from db
|
// and wait for all to be grabbed from db
|
||||||
// NOTE caddy cannot yet support multiple roots
|
// NOTE caddy cannot yet support multiple roots
|
||||||
|
@ -49,8 +49,8 @@ module.exports.create = function (config) {
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
|
||||||
// TODO this should be expanded to include proxies a la proxydyn
|
// TODO this should be expanded to include proxies a la proxydyn
|
||||||
function writeCaddyfile(conf, cb) {
|
function writeCaddyfile(caddyConf, cb) {
|
||||||
fs.readdir(config.caddy.sitespath, function (err, nodes) {
|
fs.readdir(caddyConf.sitespath, function (err, nodes) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (cb) {
|
if (cb) {
|
||||||
cb(err);
|
cb(err);
|
||||||
|
@ -61,12 +61,12 @@ module.exports.create = function (config) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
conf.caddy.domains = nodes.filter(function (node) {
|
caddyConf.domains = nodes.filter(function (node) {
|
||||||
return /\./.test(node) && !/(^\.)|([\/\:\\])/.test(node);
|
return /\./.test(node) && !/(^\.)|([\/\:\\])/.test(node);
|
||||||
});
|
});
|
||||||
|
|
||||||
var contents = tplCaddyfile(conf);
|
var contents = tplCaddyfile(caddyConf);
|
||||||
fs.writeFile(caddyConf, contents, 'utf8', function (err) {
|
fs.writeFile(caddyfilePath, contents, 'utf8', function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (cb) {
|
if (cb) {
|
||||||
cb(err);
|
cb(err);
|
||||||
|
@ -82,9 +82,9 @@ module.exports.create = function (config) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnCaddy(conf, cb) {
|
function spawnCaddy(caddyConf, cb) {
|
||||||
console.log('[CADDY] start');
|
console.log('[CADDY] start');
|
||||||
writeCaddyfile(conf, function (err) {
|
writeCaddyfile(caddyfilePath, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('[writeCaddyfile]');
|
console.error('[writeCaddyfile]');
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
|
@ -108,7 +108,7 @@ module.exports.create = function (config) {
|
||||||
// Command failed: killall caddy
|
// Command failed: killall caddy
|
||||||
// caddy: no process found
|
// caddy: no process found
|
||||||
}
|
}
|
||||||
caddy = spawn(caddyBin, ['-conf', caddyConf], { stdio: ['ignore', 'pipe', 'pipe'] });
|
caddy = spawn(caddyBin, ['-conf', caddyfilePath], { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||||
caddy.stdout.on('data', function (str) {
|
caddy.stdout.on('data', function (str) {
|
||||||
console.error('[Caddy]', str.toString('utf8'));
|
console.error('[Caddy]', str.toString('utf8'));
|
||||||
});
|
});
|
||||||
|
@ -123,7 +123,7 @@ module.exports.create = function (config) {
|
||||||
console.log(code, signal);
|
console.log(code, signal);
|
||||||
caddy = null;
|
caddy = null;
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
spawnCaddy(conf);
|
spawnCaddy(caddyConf);
|
||||||
}, 1 * 1000);
|
}, 1 * 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -151,8 +151,8 @@ module.exports.create = function (config) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
spawn: spawnCaddy
|
spawn: spawnCaddy
|
||||||
, update: function (conf) {
|
, update: function (caddyConf) {
|
||||||
return writeCaddyfile(conf, sighup);
|
return writeCaddyfile(caddyConf, sighup);
|
||||||
}
|
}
|
||||||
, sighup: sighup
|
, sighup: sighup
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module.exports.create = function (webserver, info, state) {
|
module.exports.create = function (webserver, conf, state) {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
state = {};
|
state = {};
|
||||||
}
|
}
|
||||||
|
@ -14,31 +14,31 @@ module.exports.create = function (webserver, info, state) {
|
||||||
var sqlstores = {};
|
var sqlstores = {};
|
||||||
var models = {};
|
var models = {};
|
||||||
var systemFactory = require('sqlite3-cluster/client').createClientFactory({
|
var systemFactory = require('sqlite3-cluster/client').createClientFactory({
|
||||||
dirname: path.join(__dirname, '..', '..', 'var') // TODO info.conf
|
dirname: path.join(__dirname, '..', '..', 'var') // TODO conf
|
||||||
, prefix: 'com.example.'
|
, prefix: 'com.example.'
|
||||||
//, dbname: 'config'
|
//, dbname: 'config'
|
||||||
, suffix: ''
|
, suffix: ''
|
||||||
, ext: '.sqlite3'
|
, ext: '.sqlite3'
|
||||||
, sock: info.conf.sqlite3Sock
|
, sock: conf.sqlite3Sock
|
||||||
, ipcKey: info.conf.ipcKey
|
, ipcKey: conf.ipcKey
|
||||||
});
|
});
|
||||||
var clientFactory = require('sqlite3-cluster/client').createClientFactory({
|
var clientFactory = require('sqlite3-cluster/client').createClientFactory({
|
||||||
algorithm: 'aes'
|
algorithm: 'aes'
|
||||||
, bits: 128
|
, bits: 128
|
||||||
, mode: 'cbc'
|
, mode: 'cbc'
|
||||||
, dirname: path.join(__dirname, '..', '..', 'var') // TODO info.conf
|
, dirname: path.join(__dirname, '..', '..', 'var') // TODO conf
|
||||||
, prefix: 'com.example.'
|
, prefix: 'com.example.'
|
||||||
//, dbname: 'cluster'
|
//, dbname: 'cluster'
|
||||||
, suffix: ''
|
, suffix: ''
|
||||||
, ext: '.sqlcipher'
|
, ext: '.sqlcipher'
|
||||||
, sock: info.conf.sqlite3Sock
|
, sock: conf.sqlite3Sock
|
||||||
, ipcKey: info.conf.ipcKey
|
, ipcKey: conf.ipcKey
|
||||||
});
|
});
|
||||||
var cstore = require('cluster-store');
|
var cstore = require('cluster-store');
|
||||||
var redirectives;
|
var redirectives;
|
||||||
|
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
if (info.conf.trustProxy) {
|
if (conf.trustProxy) {
|
||||||
console.info('[Trust Proxy]');
|
console.info('[Trust Proxy]');
|
||||||
app.set('trust proxy', ['loopback']);
|
app.set('trust proxy', ['loopback']);
|
||||||
//app.set('trust proxy', function (ip) { console.log('[ip]', ip); return true; });
|
//app.set('trust proxy', function (ip) { console.log('[ip]', ip); return true; });
|
||||||
|
@ -87,8 +87,8 @@ module.exports.create = function (webserver, info, state) {
|
||||||
host = host.toLowerCase();
|
host = host.toLowerCase();
|
||||||
|
|
||||||
// TODO this should be hot loadable / changeable
|
// TODO this should be hot loadable / changeable
|
||||||
if (!redirectives && info.conf.redirects) {
|
if (!redirectives && conf.redirects) {
|
||||||
redirectives = require('./hostname-redirects').compile(info.conf.redirects);
|
redirectives = require('./hostname-redirects').compile(conf.redirects);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!/^www\./.test(host) && !redirectives) {
|
if (!/^www\./.test(host) && !redirectives) {
|
||||||
|
@ -126,10 +126,10 @@ module.exports.create = function (webserver, info, state) {
|
||||||
// TODO security on memstore
|
// TODO security on memstore
|
||||||
// TODO memstoreFactory.create
|
// TODO memstoreFactory.create
|
||||||
cstore.create({
|
cstore.create({
|
||||||
sock: info.conf.memstoreSock
|
sock: conf.memstoreSock
|
||||||
, connect: info.conf.memstoreSock
|
, connect: conf.memstoreSock
|
||||||
// TODO implement
|
// TODO implement
|
||||||
, key: info.conf.ipcKey
|
, key: conf.ipcKey
|
||||||
}).then(function (_memstore) {
|
}).then(function (_memstore) {
|
||||||
memstore = _memstore;
|
memstore = _memstore;
|
||||||
return memstore;
|
return memstore;
|
||||||
|
@ -171,14 +171,14 @@ module.exports.create = function (webserver, info, state) {
|
||||||
, vhostsMap: vhostsMap
|
, vhostsMap: vhostsMap
|
||||||
, vhostPatterns: null
|
, vhostPatterns: null
|
||||||
, server: webserver
|
, server: webserver
|
||||||
, externalPort: info.conf.externalPort
|
, externalPort: conf.externalPort
|
||||||
, privkey: info.conf.privkey
|
, privkey: conf.privkey
|
||||||
, pubkey: info.conf.pubkey
|
, pubkey: conf.pubkey
|
||||||
, redirects: info.conf.redirects
|
, redirects: conf.redirects
|
||||||
, apiPrefix: '/api'
|
, apiPrefix: '/api'
|
||||||
, 'org.oauth3.consumer': info.conf['org.oauth3.consumer']
|
, 'org.oauth3.consumer': conf['org.oauth3.consumer']
|
||||||
, 'org.oauth3.provider': info.conf['org.oauth3.provider']
|
, 'org.oauth3.provider': conf['org.oauth3.provider']
|
||||||
, keys: info.conf.keys
|
, keys: conf.keys
|
||||||
};
|
};
|
||||||
var pkgDeps = {
|
var pkgDeps = {
|
||||||
memstore: memstore
|
memstore: memstore
|
||||||
|
@ -187,10 +187,11 @@ module.exports.create = function (webserver, info, state) {
|
||||||
, systemSqlFactory: systemFactory
|
, systemSqlFactory: systemFactory
|
||||||
//, handlePromise: require('./lib/common').promisableRequest;
|
//, handlePromise: require('./lib/common').promisableRequest;
|
||||||
//, handleRejection: require('./lib/common').rejectableRequest;
|
//, handleRejection: require('./lib/common').rejectableRequest;
|
||||||
//, localPort: info.conf.localPort
|
//, localPort: conf.localPort
|
||||||
, Promise: PromiseA
|
, Promise: PromiseA
|
||||||
, express: express
|
, express: express
|
||||||
, app: app
|
, app: app
|
||||||
|
//, oauthmodels: require('oauthcommon/example-oauthmodels').create(conf)
|
||||||
};
|
};
|
||||||
var Services = require('./services-loader').create(pkgConf, {
|
var Services = require('./services-loader').create(pkgConf, {
|
||||||
memstore: memstore
|
memstore: memstore
|
||||||
|
@ -223,7 +224,7 @@ module.exports.create = function (webserver, info, state) {
|
||||||
config: pkgConf
|
config: pkgConf
|
||||||
, deps: pkgDeps
|
, deps: pkgDeps
|
||||||
, services: Services
|
, services: Services
|
||||||
, conf: info.conf
|
, conf: conf
|
||||||
}, req, res, next);
|
}, req, res, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
"cookie-session": "1.x",
|
"cookie-session": "1.x",
|
||||||
"cookie-signature": "^1.0.6",
|
"cookie-signature": "^1.0.6",
|
||||||
"crc": "^3.2.1",
|
"crc": "^3.2.1",
|
||||||
|
"ddns-cli": "^1.2.1",
|
||||||
"debug": "^2.1.3",
|
"debug": "^2.1.3",
|
||||||
"depd": "^1.0.0",
|
"depd": "^1.0.0",
|
||||||
"destroy": "^1.0.3",
|
"destroy": "^1.0.3",
|
||||||
|
@ -81,6 +82,7 @@
|
||||||
"json-storage": "2.x",
|
"json-storage": "2.x",
|
||||||
"jsonwebtoken": "^5.4.0",
|
"jsonwebtoken": "^5.4.0",
|
||||||
"lodash": "2.x",
|
"lodash": "2.x",
|
||||||
|
"letsencrypt-express": "1.1.x",
|
||||||
"masterquest-sqlite3": "git://github.com/coolaj86/node-masterquest-sqlite3.git",
|
"masterquest-sqlite3": "git://github.com/coolaj86/node-masterquest-sqlite3.git",
|
||||||
"media-typer": "^0.3.0",
|
"media-typer": "^0.3.0",
|
||||||
"methods": "^1.1.1",
|
"methods": "^1.1.1",
|
||||||
|
@ -105,7 +107,7 @@
|
||||||
"request": "2.44.0",
|
"request": "2.44.0",
|
||||||
"request-ip": "^1.1.1",
|
"request-ip": "^1.1.1",
|
||||||
"scmp": "1.x",
|
"scmp": "1.x",
|
||||||
"secret-utils": "1.x",
|
"secret-utils": "^2.0.0",
|
||||||
"semver": "^4.3.1",
|
"semver": "^4.3.1",
|
||||||
"send": "^0.12.2",
|
"send": "^0.12.2",
|
||||||
"serve-favicon": "2.x",
|
"serve-favicon": "2.x",
|
||||||
|
|
Loading…
Reference in New Issue