| 
									
										
										
										
											2016-04-09 19:14:00 -04: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 14:05:10 -04: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 19:14:00 -04: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()) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }; |