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'));
|
|
|
|
|
|
|
|
function isInitialized() {
|
|
|
|
// TODO read from file only, not db
|
|
|
|
return models.ComDaplieWalnutConfig.get('config').then(function (conf) {
|
|
|
|
if (!conf || !conf.primaryDomain || !conf.primaryEmail) {
|
|
|
|
console.log('DEBUG incomplete conf', conf);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
xconfx.primaryDomain = xconfx.primaryDomain || conf.primaryDomain;
|
|
|
|
|
|
|
|
var configname = conf.primaryDomain + '.json';
|
|
|
|
var configpath = path.join(__dirname, '..', '..', 'config', configname);
|
|
|
|
|
|
|
|
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) {
|
|
|
|
if (!data || !data.email || !data.agreeTos) {
|
|
|
|
console.log('DEBUG incomplete leconf', data);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function initialize() {
|
|
|
|
var express = require('express');
|
|
|
|
var getIpAddresses = require('./ip-checker').getExternalAddresses;
|
|
|
|
var resolve;
|
|
|
|
|
|
|
|
function errorIfNotApi(req, res, next) {
|
|
|
|
// if it's not an ip address
|
2016-06-08 18:05:10 +00:00
|
|
|
if (/[a-z]+/.test(req.hostname || req.headers.host)) {
|
|
|
|
if (!/^api\./.test(req.hostname || req.headers.host)) {
|
|
|
|
console.warn('not API req.headers.host:', req.hostname || req.headers.host);
|
2016-04-09 23:14:00 +00:00
|
|
|
res.send({ error: { message: "no api. subdomain prefix" } });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
var config = req.body;
|
|
|
|
var results = {};
|
|
|
|
|
|
|
|
return PromiseA.resolve().then(function () {
|
|
|
|
if (!config.agreeTos && !config.tls) {
|
|
|
|
return PromiseA.reject(new Error("To enable encryption you must agree to the LetsEncrypt terms of service"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!config.domain) {
|
|
|
|
return PromiseA.reject(new Error("You must specify a valid domain name"));
|
|
|
|
}
|
|
|
|
config.domain = config.domain.replace(/^www\./, '');
|
|
|
|
|
|
|
|
return getIpAddresses().then(function (inet) {
|
|
|
|
if (!inet.addresses.length) {
|
|
|
|
return PromiseA.reject(new Error("no ip addresses"));
|
|
|
|
}
|
|
|
|
|
|
|
|
results.inets = inet.addresses.map(function (a) {
|
|
|
|
a.time = undefined;
|
|
|
|
return a;
|
|
|
|
});
|
|
|
|
|
|
|
|
results.resolutions = [];
|
|
|
|
return PromiseA.all([
|
|
|
|
// for static content
|
|
|
|
verifyIps(inet.addresses, config.domain).then(function (ips) {
|
|
|
|
results.resolutions.push({ hostname: config.domain, ips: ips });
|
|
|
|
})
|
|
|
|
// for redirects
|
|
|
|
, verifyIps(inet.addresses, 'www.' + config.domain).then(function (ips) {
|
|
|
|
results.resolutions.push({ hostname: 'www.' + config.domain, ips: ips });
|
|
|
|
})
|
|
|
|
// for api
|
|
|
|
, verifyIps(inet.addresses, 'api.' + config.domain).then(function (ips) {
|
|
|
|
results.resolutions.push({ hostname: 'api.' + config.domain, ips: ips });
|
|
|
|
})
|
|
|
|
// for protected assets
|
|
|
|
, verifyIps(inet.addresses, 'assets.' + config.domain).then(function (ips) {
|
|
|
|
results.resolutions.push({ hostname: 'assets.' + config.domain, ips: ips });
|
|
|
|
})
|
|
|
|
// for the cloud management
|
|
|
|
, verifyIps(inet.addresses, 'cloud.' + config.domain).then(function (ips) {
|
|
|
|
results.resolutions.push({ hostname: 'cloud.' + config.domain, ips: ips });
|
|
|
|
})
|
|
|
|
, verifyIps(inet.addresses, 'api.cloud.' + config.domain).then(function (ips) {
|
|
|
|
results.resolutions.push({ hostname: 'api.cloud.' + config.domain, ips: ips });
|
|
|
|
})
|
|
|
|
]).then(function () {
|
|
|
|
if (!results.resolutions[0].ips.length) {
|
|
|
|
results.error = { message: "bare domain could not be resolved to this device" };
|
|
|
|
}
|
|
|
|
else if (!results.resolutions[2].ips.length) {
|
|
|
|
results.error = { message: "api subdomain could not be resolved to this device" };
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
else if (!results.resolutions[1].ips.length) {
|
|
|
|
results.error = { message: "" }
|
|
|
|
}
|
|
|
|
else if (!results.resolutions[3].ips.length) {
|
|
|
|
results.error = { message: "" }
|
|
|
|
}
|
|
|
|
else if (!results.resolutions[4].ips.length || !results.resolutions[4].ips.length) {
|
|
|
|
results.error = { message: "cloud and api.cloud subdomains should be set up" };
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}).then(function () {
|
|
|
|
if (results.error) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var configname = config.domain + '.json';
|
|
|
|
var configpath = path.join(__dirname, '..', '..', 'config', configname);
|
|
|
|
var leAuth = {
|
|
|
|
agreeTos: true
|
|
|
|
, email: config.email // TODO check email
|
|
|
|
, domain: config.domain
|
|
|
|
, createdAt: Date.now()
|
|
|
|
};
|
|
|
|
|
|
|
|
return dns.resolveMxAsync(config.email.replace(/.*@/, '')).then(function (/*addrs*/) {
|
|
|
|
// TODO allow private key to be uploaded
|
|
|
|
return fs.writeFileAsync(configpath, JSON.stringify(leAuth, null, ' '), 'utf8').then(function () {
|
|
|
|
return models.ComDaplieWalnutConfig.upsert('config', {
|
|
|
|
letsencrypt: leAuth
|
|
|
|
, primaryDomain: config.domain
|
|
|
|
, primaryEmail: config.email
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}, function () {
|
|
|
|
return PromiseA.reject(new Error("invalid email address (MX record lookup failed)"));
|
|
|
|
});
|
|
|
|
}).then(function () {
|
|
|
|
if (!results.error && results.inets && resolve) {
|
|
|
|
resolve();
|
|
|
|
resolve = null;
|
|
|
|
}
|
|
|
|
res.send(results);
|
|
|
|
}, 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) {
|
|
|
|
return isInitialized().then(function (initialized) {
|
|
|
|
if (!initialized) {
|
|
|
|
next();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve(true);
|
|
|
|
|
|
|
|
// force page refresh
|
|
|
|
// TODO goto top of routes?
|
|
|
|
res.statusCode = 302;
|
|
|
|
res.setHeader('Location', req.url);
|
|
|
|
res.end();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
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);
|
|
|
|
app.use('/', express.static(path.join(__dirname, '..', '..', 'packages', 'pages', 'com.daplie.walnut.init')));
|
|
|
|
|
|
|
|
return new PromiseA(function (_resolve) {
|
|
|
|
resolve = _resolve;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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())
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|