walnut.js/lib/bootstrap.js

243 lines
7.3 KiB
JavaScript
Raw Normal View History

2016-04-09 23:14:00 +00:00
'use strict';
//
// IMPORTANT !!!
//
// None of this is authenticated or encrypted
//
module.exports.create = function (app, xconfx, models) {
var PromiseA = require('bluebird');
var path = require('path');
var fs = PromiseA.promisifyAll(require('fs'));
var dns = PromiseA.promisifyAll(require('dns'));
2017-05-19 01:00:17 +00:00
function isInitialized () {
2016-04-09 23:14:00 +00:00
// TODO read from file only, not db
return models.ComDaplieWalnutConfig.get('config').then(function (conf) {
2017-05-19 01:00:17 +00:00
if (!conf || !conf.primaryDomain/* || !conf.primaryEmail*/) {
2016-04-09 23:14:00 +00:00
console.log('DEBUG incomplete conf', conf);
return false;
}
xconfx.primaryDomain = xconfx.primaryDomain || conf.primaryDomain;
2017-05-19 01:00:17 +00:00
var configpath = path.join(__dirname, '..', '..', 'config', conf.primaryDomain + '.json');
2016-04-09 23:14:00 +00:00
return fs.readFileAsync(configpath, 'utf8').then(function (text) {
return JSON.parse(text);
}, function (/*err*/) {
console.log('DEBUG not exists leconf', configpath);
return false;
}).then(function (data) {
return true;
});
});
}
function initialize() {
var express = require('express');
var getIpAddresses = require('./ip-checker').getExternalAddresses;
2017-05-19 01:00:17 +00:00
var resolveInit;
2016-04-09 23:14:00 +00:00
function errorIfNotApi(req, res, next) {
var hostname = req.hostname || req.headers.host;
if (!/^api\.[a-z0-9\-]+/.test(hostname)) {
res.send({ error:
{ message: "API access is restricted to proper 'api'-prefixed lowercase subdomains."
+ " The HTTP 'Host' header must exist and must begin with 'api.' as in 'api.example.com'."
+ " For development you may test with api.localhost.daplie.me (or any domain by modifying your /etc/hosts)"
, code: 'E_NOT_API'
, _hostname: hostname
}
});
return;
2016-04-09 23:14:00 +00:00
}
next();
}
function errorIfApi(req, res, next) {
if (!/^api\./.test(req.headers.host)) {
next();
return;
}
// has api. hostname prefix
// doesn't have /api url prefix
if (!/^\/api\//.test(req.url)) {
res.send({ error: { message: "missing /api/ url prefix" } });
return;
}
res.send({ error: { code: 'E_NO_IMPL', message: "not implemented" } });
}
function getConfig(req, res) {
getIpAddresses().then(function (inets) {
var results = {
hostname: require('os').hostname()
, inets: inets.addresses.map(function (a) {
a.time = undefined;
return a;
})
};
//res.send({ inets: require('os').networkInterfaces() });
res.send(results);
});
}
function verifyIps(inets, hostname) {
var map = {};
var arr = [];
inets.forEach(function (addr) {
if (!map[addr.family]) {
map[addr.family] = true;
if (4 === addr.family) {
arr.push(dns.resolve4Async(hostname).then(function (arr) {
return arr;
}, function (/*err*/) {
return [];
}));
}
if (6 === addr.family) {
arr.push(dns.resolve6Async(hostname).then(function (arr) {
return arr;
}, function (/*err*/) {
return [];
}));
}
}
});
return PromiseA.all(arr).then(function (fams) {
console.log('DEBUG hostname', hostname);
var ips = [];
fams.forEach(function (addrs) {
console.log('DEBUG ipv46');
console.log(addrs);
addrs.forEach(function (addr) {
inets.forEach(function (a) {
if (a.address === addr) {
a.time = undefined;
ips.push(a);
}
});
});
console.log('');
});
return ips;
});
}
function setConfig(req, res) {
return PromiseA.resolve().then(function () {
2017-05-19 01:00:17 +00:00
// TODO expect authenticated oauth3 user
var config = req.body;
var safeConfig = {};
2016-04-09 23:14:00 +00:00
2017-05-19 01:00:17 +00:00
if ('string' !== typeof config.domain) {
return PromiseA.reject(new Error("'domain' should be a string specifying a valid domain name"));
2016-04-09 23:14:00 +00:00
}
2017-05-19 01:00:17 +00:00
config.domain = (config.domain||'').replace(/^www\./, '');
2016-04-09 23:14:00 +00:00
2017-05-19 01:00:17 +00:00
// TODO url-testing lib
if (!/\w+\.\w+/.test(config.domain)) {
return PromiseA.reject(new Error("'domain' should be a string specifying a valid domain name"));
2016-04-09 23:14:00 +00:00
}
2017-05-19 01:00:17 +00:00
var configpath = path.join(__dirname, '..', '..', 'config', config.domain + '.json');
safeConfig = { primaryDomain: config.domain };
return fs.writeFileAsync(configpath, JSON.stringify(safeConfig, null, ' '), 'utf8').then(function () {
// TODO nix SQL
return models.ComDaplieWalnutConfig.upsert('config', safeConfig);
2016-04-09 23:14:00 +00:00
});
}).then(function () {
2017-05-19 01:00:17 +00:00
if (resolveInit) {
resolveInit();
resolveInit = null;
2016-04-09 23:14:00 +00:00
}
2017-05-19 01:00:17 +00:00
res.send({ success: true });
2016-04-09 23:14:00 +00:00
}, function (err) {
console.error('Error lib/bootstrap.js');
console.error(err.stack || err);
res.send({ error: { message: err.message || err.toString() } });
});
}
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" ] });
app.use('/', function (req, res, next) {
2017-05-19 01:00:17 +00:00
console.log('[lib/bootstrap.js] req.url', req.url);
2016-04-09 23:14:00 +00:00
return isInitialized().then(function (initialized) {
if (!initialized) {
next();
return;
}
2017-05-19 01:00:17 +00:00
// init is always considered to be
resolveInit(true);
2016-04-09 23:14:00 +00:00
2017-05-19 01:00:17 +00:00
// TODO feed this request back through the route stack from the top to avoid forced refresh?
2016-04-09 23:14:00 +00:00
res.statusCode = 302;
res.setHeader('Location', req.url);
2017-05-19 01:00:17 +00:00
res.end("<!-- App bootstraping complete, but you got here somehow anyway. Let's redirect you so you get to the main app. -->");
2016-04-09 23:14:00 +00:00
});
});
app.use('/api', errorIfNotApi);
// NOTE 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
app.use('/api', cors);
app.get('/api/com.daplie.walnut.init', getConfig);
app.post('/api/com.daplie.walnut.init', setConfig);
app.use('/', errorIfApi);
2017-05-19 01:00:17 +00:00
// TODO use package loader
//app.use('/', express.static(path.join(__dirname, '..', '..', 'packages', 'pages', 'com.daplie.walnut.init')));
app.use('/', express.static(path.join(__dirname, 'com.daplie.walnut.init')));
app.use('/', function (req, res, next) {
res.statusCode = 404;
res.end('Walnut Bootstrap Not Found. Mising com.daplie.walnut.init');
});
2016-04-09 23:14:00 +00:00
return new PromiseA(function (_resolve) {
2017-05-19 01:00:17 +00:00
resolveInit = _resolve;
2016-04-09 23:14:00 +00:00
});
}
return isInitialized().then(function (initialized) {
if (initialized) {
return true;
}
return initialize();
}, function (err) {
console.error('FATAL ERROR:');
console.error(err.stack || err);
app.use('/', function (req, res) {
res.send({
error: {
message: "Unrecoverable Error Requires manual server update: " + (err.message || err.toString())
}
});
});
});
};