forked from coolaj86/walnut.js
		
	enable assets subdomain with cookies
This commit is contained in:
		
							parent
							
								
									babfb6b38b
								
							
						
					
					
						commit
						a429e48977
					
				
							
								
								
									
										305
									
								
								lib/apis.js
									
									
									
									
									
								
							
							
						
						
									
										305
									
								
								lib/apis.js
									
									
									
									
									
								
							| @ -8,7 +8,7 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|   var express = require('express-lazy'); | ||||
|   var fs = PromiseA.promisifyAll(require('fs')); | ||||
|   var path = require('path'); | ||||
|   var localCache = { rests: {}, pkgs: {} }; | ||||
|   var localCache = { rests: {}, pkgs: {}, assets: {} }; | ||||
|   var promisableRequest = require('./common').promisableRequest; | ||||
|   var rejectableRequest = require('./common').rejectableRequest; | ||||
|   var crypto = require('crypto'); | ||||
| @ -32,7 +32,7 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|   } | ||||
|   */ | ||||
| 
 | ||||
|   function isThisClientAllowedToUseThisPkg(myConf, clientUrih, pkgId) { | ||||
|   function isThisClientAllowedToUseThisPkg(req, myConf, clientUrih, pkgId) { | ||||
|     var appApiGrantsPath = path.join(myConf.appApiGrantsPath, clientUrih); | ||||
| 
 | ||||
|     return fs.readFileAsync(appApiGrantsPath, 'utf8').then(function (text) { | ||||
| @ -51,12 +51,23 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|         return true; | ||||
|       } | ||||
| 
 | ||||
|       if (clientUrih === ('api.' + xconfx.setupDomain) && 'org.oauth3.consumer' === pkgId) { | ||||
|       console.log('#################################################'); | ||||
|       console.log('assets.' + xconfx.setupDomain); | ||||
|       console.log('assets.' + clientUrih); | ||||
|       console.log(req.clientAssetsUri); | ||||
|       console.log(pkgId); | ||||
| 
 | ||||
|       if (req.clientAssetsUri === ('assets.' + clientUrih) && -1 !== [ 'session', 'session@oauth3.org', 'azp@oauth3.org', 'issuer@oauth3.org' ].indexOf(pkgId)) { | ||||
|         // fallthrough
 | ||||
|         return true; | ||||
|       } else { | ||||
|         return null; | ||||
|       } | ||||
| 
 | ||||
|       if (clientUrih === ('api.' + xconfx.setupDomain) && -1 !== ['org.oauth3.consumer', 'azp@oauth3.org', 'oauth3.org'].indexOf(pkgId)) { | ||||
|         // fallthrough
 | ||||
|         return true; | ||||
|       } | ||||
| 
 | ||||
|       return null; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| @ -211,34 +222,19 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|     rejectableRequest(req, res, promise, "[walnut@daplie.com] required account (not /public)"); | ||||
|   } | ||||
| 
 | ||||
|   function loadRestHelper(myConf, clientUrih, pkgId) { | ||||
|     var pkgPath = path.join(myConf.restPath, pkgId); | ||||
|   function loadRestHelperApi(myConf, clientUrih, pkg, pkgId, pkgPath) { | ||||
|     var pkgLinks = []; | ||||
|     pkgLinks.push(pkgId); | ||||
| 
 | ||||
|     // TODO allow recursion, but catch cycles
 | ||||
|     return fs.lstatAsync(pkgPath).then(function (stat) { | ||||
|       if (!stat.isFile()) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       return fs.readFileAsync(pkgPath, 'utf8').then(function (text) { | ||||
|         pkgId = text.trim(); | ||||
|         pkgPath = path.join(myConf.restPath, pkgId); | ||||
|       }); | ||||
|     }, function () { | ||||
|       // ignore error
 | ||||
|       return; | ||||
|     }).then(function () { | ||||
|       // TODO should not require package.json. Should work with files alone.
 | ||||
|       return fs.readFileAsync(path.join(pkgPath, 'package.json'), 'utf8').then(function (text) { | ||||
|         var pkg = JSON.parse(text); | ||||
|     var pkgRestApi; | ||||
|     var pkgDeps = {}; | ||||
|     var myApp; | ||||
|     var pkgPathApi; | ||||
| 
 | ||||
|     pkgPathApi = pkgPath; | ||||
|     if (pkg.walnut) { | ||||
|           pkgPath = path.join(pkgPath, pkg.walnut); | ||||
|       pkgPathApi = path.join(pkgPath, pkg.walnut); | ||||
|     } | ||||
|     pkgRestApi = require(pkgPathApi); | ||||
| 
 | ||||
|     Object.keys(apiDeps).forEach(function (key) { | ||||
|       pkgDeps[key] = apiDeps[key]; | ||||
| @ -499,7 +495,6 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|           // (realized later)
 | ||||
|           // HAHA HAHA HAHAHAHAHA this is my own gist... so much more polite attribution
 | ||||
|           var scmp = require('scmp') | ||||
|                 , crypto = require('crypto') | ||||
|             , mailgunExpirey = 15 * 60 * 1000 | ||||
|             , mailgunHashType = 'sha256' | ||||
|             , mailgunSignatureEncoding = 'hex' | ||||
| @ -719,7 +714,7 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|     //
 | ||||
|     // TODO handle /accounts/:accountId
 | ||||
|     //
 | ||||
|         return PromiseA.resolve(require(pkgPath).create({ | ||||
|     return PromiseA.resolve(pkgRestApi.create({ | ||||
|       etcpath: xconfx.etcpath | ||||
|     }/*pkgConf*/, pkgDeps/*pkgDeps*/, myApp/*myApp*/)).then(function (handler) { | ||||
| 
 | ||||
| @ -737,6 +732,213 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
| 
 | ||||
|       return localCache.pkgs[pkgId]; | ||||
|     }); | ||||
|   } | ||||
|   function loadRestHelperAssets(myConf, clientUrih, pkg, pkgId, pkgPath) { | ||||
|     var myApp; | ||||
|     var pkgDeps = {}; | ||||
|     var pkgRestAssets = require(path.join(pkgPath, 'assets.js')); | ||||
| 
 | ||||
|     Object.keys(apiDeps).forEach(function (key) { | ||||
|       pkgDeps[key] = apiDeps[key]; | ||||
|     }); | ||||
|     Object.keys(apiFactories).forEach(function (key) { | ||||
|       pkgDeps[key] = apiFactories[key]; | ||||
|     }); | ||||
| 
 | ||||
|     // TODO pull db stuff from package.json somehow and pass allowed data models as deps
 | ||||
|     //
 | ||||
|     // how can we tell which of these would be correct?
 | ||||
|     // deps.memstore = apiFactories.memstoreFactory.create(pkgId);
 | ||||
|     // deps.memstore = apiFactories.memstoreFactory.create(req.experienceId);
 | ||||
|     // deps.memstore = apiFactories.memstoreFactory.create(req.experienceId + pkgId);
 | ||||
| 
 | ||||
|     // let's go with this one for now and the api can choose to scope or not to scope
 | ||||
|     pkgDeps.memstore = apiFactories.memstoreFactory.create(pkgId); | ||||
| 
 | ||||
|     myApp = express(); | ||||
|     myApp.handlePromise = promisableRequest; | ||||
|     myApp.handleRejection = rejectableRequest; | ||||
|     myApp.grantsRequired = function (grants) { | ||||
|       if (!Array.isArray(grants)) { | ||||
|         throw new Error("Usage: app.grantsRequired([ 'name|altname|altname2', 'othergrant' ])"); | ||||
|       } | ||||
| 
 | ||||
|       if (!grants.length) { | ||||
|         return function (req, res, next) { | ||||
|           next(); | ||||
|         }; | ||||
|       } | ||||
| 
 | ||||
|       return function (req, res, next) { | ||||
|         var tokenScopes; | ||||
| 
 | ||||
|         if (!(req.oauth3 || req.oauth3.token)) { | ||||
|           // TODO some error generator for standard messages
 | ||||
|           res.send({ error: { message: "You must be logged in", code: "E_NO_AUTHN" } }); | ||||
|           return; | ||||
|         } | ||||
|         var scope = req.oauth3.token.scope || req.oauth3.token.scp || req.oauth3.token.grants; | ||||
|         if ('string' !== typeof scope) { | ||||
|           res.send({ error: { message: "Token must contain a grants string in 'scope'", code: "E_NO_GRANTS" } }); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         tokenScopes = scope.split(/[,\s]+/mg); | ||||
|         if (-1 !== tokenScopes.indexOf('*')) { | ||||
|           // has full account access
 | ||||
|           next(); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         // every grant in the array must be present, though some grants can be satisfied
 | ||||
|         // by multiple scopes.
 | ||||
|         var missing = grants.filter(function (grant) { | ||||
|           return !grant.split('|').some(function (scp) { | ||||
|             return tokenScopes.indexOf(scp) !== -1; | ||||
|           }); | ||||
|         }); | ||||
|         if (missing.length) { | ||||
|           res.send({ error: { message: "Token missing required grants: '" + missing.join(',') + "'", code: "E_NO_GRANTS" } }); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         next(); | ||||
|       }; | ||||
|     }; | ||||
| 
 | ||||
|     myApp.use('/', require('./oauth3').cookieOauth3); | ||||
|     myApp.use('/', function (req, res, next) { | ||||
|       console.log('########################################### session ###############################'); | ||||
|       console.log('req.url', req.url); | ||||
|       console.log('req.oauth3', req.oauth3); | ||||
|       next(); | ||||
|     }); | ||||
|     myApp.post('/assets/issuer@oauth3.org/session', require('./oauth3').attachOauth3, function (req, res) { | ||||
|       console.log('get the session'); | ||||
|       console.log(req.url); | ||||
|       console.log("req.cookies:"); | ||||
|       console.log(req.cookies); | ||||
|       console.log("req.oauth3:"); | ||||
|       console.log(req.oauth3); | ||||
|       res.cookie('jwt', req.oauth3.encodedToken, { domain: req.clientAssetsUri, path: '/assets', httpOnly: true }); | ||||
|       //req.url;
 | ||||
|       res.send({ success: true }); | ||||
|     }); | ||||
| 
 | ||||
|     // TODO delete these caches when config changes
 | ||||
|     myApp.use('/', function preHandler(req, res, next) { | ||||
|       //if (xconfx.debug) { console.log('[api.js] loading handler prereqs'); }
 | ||||
|       return getSiteConfig(clientUrih).then(function (siteConfig) { | ||||
|         //if (xconfx.debug) { console.log('[api.js] loaded handler site config'); }
 | ||||
| 
 | ||||
|         Object.defineProperty(req, 'getSiteConfig', { | ||||
|           enumerable: true | ||||
|         , configurable: false | ||||
|         , writable: false | ||||
|         , value: function getSiteConfigProp(section) { | ||||
|             return PromiseA.resolve((siteConfig || {})[section]); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         Object.defineProperty(req, 'getSitePackageConfig', { | ||||
|           enumerable: true | ||||
|         , configurable: false | ||||
|         , writable: false | ||||
|         , value: function getSitePackageConfigProp() { | ||||
|             return getSitePackageConfig(clientUrih, pkgId); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         Object.defineProperty(req, 'getSiteStore', { | ||||
|           enumerable: true | ||||
|         , configurable: false | ||||
|         , writable: false | ||||
|         , value: function getSiteStoreProp() { | ||||
|             var restPath = path.join(myConf.restPath, pkgId); | ||||
|             var apiPath = path.join(myConf.apiPath, pkgId); | ||||
|             var dir; | ||||
| 
 | ||||
|             // TODO usage package.json as a falback if the standard location is not used
 | ||||
|             try { | ||||
|               dir = require(path.join(apiPath, 'models.js')); | ||||
|             } catch(e) { | ||||
|               dir = require(path.join(restPath, 'models.js')); | ||||
|             } | ||||
| 
 | ||||
|             return getSiteStore(clientUrih, pkgId, dir); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         req._walnutOriginalUrl = req.url; | ||||
|         // "/path/api/com.example/hello".replace(/.*\/api\//, '').replace(/([^\/]*\/+)/, '/') => '/hello'
 | ||||
|         req.url = req.url.replace(/\/(api|assets)\//, '').replace(/.*\/(api|assets)\//, '').replace(/([^\/]*\/+)/, '/'); | ||||
|         next(); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     myApp.use('/public', function preHandler(req, res, next) { | ||||
|       // TODO authenticate or use guest user
 | ||||
|       req.isPublic = true; | ||||
|       next(); | ||||
|     }); | ||||
|     myApp.use('/accounts/:accountId', accountRequiredById); | ||||
|     myApp.use('/acl', accountRequired); | ||||
| 
 | ||||
|     //
 | ||||
|     // TODO handle /accounts/:accountId
 | ||||
|     //
 | ||||
|     function myAppWrapper(req, res, next) { | ||||
|       return myApp(req, res, next); | ||||
|     } | ||||
|     Object.keys(myApp).forEach(function (key) { | ||||
|       myAppWrapper[key] = myApp[key]; | ||||
|     }); | ||||
|     myAppWrapper.use = function () { myApp.use.apply(myApp, arguments); }; | ||||
|     myAppWrapper.get = function () { myApp.get.apply(myApp, arguments); }; | ||||
|     myAppWrapper.post = function () { myApp.use(function (req, res, next) { next(); }); /*throw new Error("assets may not handle POST");*/ }; | ||||
|     myAppWrapper.put = function () { throw new Error("assets may not handle PUT"); }; | ||||
|     myAppWrapper.del = function () { throw new Error("assets may not handle DELETE"); }; | ||||
|     myAppWrapper.delete = function () { throw new Error("assets may not handle DELETE"); }; | ||||
|     return PromiseA.resolve(pkgRestAssets.create({ | ||||
|       etcpath: xconfx.etcpath | ||||
|     }/*pkgConf*/, pkgDeps/*pkgDeps*/, myAppWrapper)).then(function (assetsHandler) { | ||||
| 
 | ||||
|       //if (xconfx.debug) { console.log('[api.js] got handler'); }
 | ||||
|       myApp.use('/', function postHandler(req, res, next) { | ||||
|         req.url = req._walnutOriginalUrl; | ||||
|         next(); | ||||
|       }); | ||||
| 
 | ||||
|       return assetsHandler || myApp; | ||||
|     }); | ||||
|   } | ||||
|   function loadRestHelper(myConf, clientUrih, pkgId) { | ||||
|     var pkgPath = path.join(myConf.restPath, pkgId); | ||||
| 
 | ||||
|     // TODO allow recursion, but catch cycles
 | ||||
|     return fs.lstatAsync(pkgPath).then(function (stat) { | ||||
|       if (!stat.isFile()) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       return fs.readFileAsync(pkgPath, 'utf8').then(function (text) { | ||||
|         pkgId = text.trim(); | ||||
|         pkgPath = path.join(myConf.restPath, pkgId); | ||||
|       }); | ||||
|     }, function () { | ||||
|       // ignore error
 | ||||
|       return; | ||||
|     }).then(function () { | ||||
|       // TODO should not require package.json. Should work with files alone.
 | ||||
|       return fs.readFileAsync(path.join(pkgPath, 'package.json'), 'utf8').then(function (text) { | ||||
|         var pkg = JSON.parse(text); | ||||
| 
 | ||||
|         return loadRestHelperApi(myConf, clientUrih, pkg, pkgId, pkgPath).then(function (stuff) { | ||||
|           return loadRestHelperAssets(myConf, clientUrih, pkg, pkgId, pkgPath).then(function (assetsHandler) { | ||||
|             stuff.assetsHandler = assetsHandler; | ||||
|             return stuff; | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| @ -784,13 +986,14 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|       // sub.example.com/api should resolve to sub.example.com
 | ||||
|       // example.com/subapp/api should resolve to example.com#subapp
 | ||||
|       // sub.example.com/subapp/api should resolve to sub.example.com#subapp
 | ||||
|       var clientUrih = req.hostname.replace(/^api\./, '') + req.url.replace(/\/api\/.*/, '/').replace(/\/+/g, '#').replace(/#$/, ''); | ||||
|       var clientApiUri = req.hostname + req.url.replace(/\/api\/.*/, '/').replace(/\/$/, ''); | ||||
|       var clientUrih = req.hostname.replace(/^(api|assets)\./, '') + req.url.replace(/\/(api|assets)\/.*/, '/').replace(/\/+/g, '#').replace(/#$/, ''); | ||||
|       var clientApiUri = req.hostname.replace(/^(api|assets)\./, 'api.') + req.url.replace(/\/(api|assets)\/.*/, '/').replace(/\/$/, ''); | ||||
|       var clientAssetsUri = req.hostname.replace(/^(api|assets)\./, 'assets.') + req.url.replace(/\/(api|assets)\/.*/, '/').replace(/\/$/, ''); | ||||
|       // Canonical package names
 | ||||
|       // '/api/com.daplie.hello/hello' should resolve to 'com.daplie.hello'
 | ||||
|       // '/subapp/api/com.daplie.hello/hello' should also 'com.daplie.hello'
 | ||||
|       // '/subapp/api/com.daplie.hello/' may exist... must be a small api
 | ||||
|       var pkgId = req.url.replace(/.*\/api\//, '').replace(/^\//, '').replace(/\/.*/, ''); | ||||
|       var pkgId = req.url.replace(/.*\/(api|assets)\//, '').replace(/^\//, '').replace(/\/.*/, ''); | ||||
|       var now = Date.now(); | ||||
|       var hasBeenHandled = false; | ||||
| 
 | ||||
| @ -801,6 +1004,12 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|       , writable: false | ||||
|       , value: 'https://' + clientApiUri + '/api/' + pkgId | ||||
|       }); | ||||
|       Object.defineProperty(req, 'assetsUrlPrefix', { | ||||
|         enumerable: true | ||||
|       , configurable: false | ||||
|       , writable: false | ||||
|       , value: 'https://' + clientAssetsUri + '/assets/' + pkgId | ||||
|       }); | ||||
|       Object.defineProperty(req, 'experienceId', { | ||||
|         enumerable: true | ||||
|       , configurable: false | ||||
| @ -813,6 +1022,12 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|       , writable: false | ||||
|       , value: clientApiUri | ||||
|       }); | ||||
|       Object.defineProperty(req, 'clientAssetsUri', { | ||||
|         enumerable: true | ||||
|       , configurable: false | ||||
|       , writable: false | ||||
|       , value: clientAssetsUri | ||||
|       }); | ||||
|       Object.defineProperty(req, 'apiId', { | ||||
|         enumerable: true | ||||
|       , configurable: false | ||||
| @ -838,19 +1053,36 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|       }); | ||||
| 
 | ||||
|       // TODO cache permission (although the FS is already cached, NBD)
 | ||||
|       var promise = isThisClientAllowedToUseThisPkg(xconfx, clientUrih, pkgId).then(function (yes) { | ||||
|       var promise = isThisClientAllowedToUseThisPkg(req, xconfx, clientUrih, pkgId).then(function (yes) { | ||||
|         //if (xconfx.debug) { console.log('[api.js] azp is allowed?', yes); }
 | ||||
|         if (!yes) { | ||||
|           notConfigured(req, res); | ||||
|           return null; | ||||
|         } | ||||
| 
 | ||||
|         if (localCache.rests[pkgId]) { | ||||
|         function handleWithHandler() { | ||||
|           if (/\/assets\//.test(req.url) || /(^|\.)assets\./.test(req.hostname)) { | ||||
|             if (localCache.assets[pkgId]) { | ||||
|               if ('function' !== typeof localCache.assets[pkgId].handler) { console.log('localCache.assets[pkgId]'); console.log(localCache.assets[pkgId]); } | ||||
|               localCache.assets[pkgId].handler(req, res, next); | ||||
|             } else { | ||||
|               next(); | ||||
|               return true; | ||||
|             } | ||||
|           } else { | ||||
|             localCache.rests[pkgId].handler(req, res, next); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         if (localCache.rests[pkgId]) { | ||||
|           if (handleWithHandler()) { | ||||
|             return; | ||||
|           } | ||||
|           hasBeenHandled = true; | ||||
| 
 | ||||
|           if (now - localCache.rests[pkgId].createdAt > staleAfter) { | ||||
|             localCache.rests[pkgId] = null; | ||||
|             localCache.assets[pkgId] = null; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
| @ -866,13 +1098,16 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | ||||
|             } | ||||
| 
 | ||||
|             localCache.rests[pkgId] = { handler: myHandler.handler, createdAt: now }; | ||||
|             localCache.assets[pkgId] = { handler: myHandler.assetsHandler, createdAt: now }; | ||||
|             if (!hasBeenHandled) { | ||||
|               //if (xconfx.debug) { console.log('[api.js] not configured'); }
 | ||||
|               myHandler.handler(req, res, next); | ||||
|               if (handleWithHandler()) { | ||||
|                 return; | ||||
|               } | ||||
|             } | ||||
|           }); | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       rejectableRequest(req, res, promise, "[walnut@daplie.com] load api package"); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
							
								
								
									
										26
									
								
								lib/main.js
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								lib/main.js
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports.create = function (app, xconfx, apiFactories, apiDeps, errorIfApi) { | ||||
| module.exports.create = function (app, xconfx, apiFactories, apiDeps, errorIfApi, errorIfAssets) { | ||||
|   var PromiseA = require('bluebird'); | ||||
|   var path = require('path'); | ||||
|   var fs = PromiseA.promisifyAll(require('fs')); | ||||
| @ -293,10 +293,27 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps, errorIfApi | ||||
|   // TODO handle assets.example.com/sub/assets/com.example.xyz/
 | ||||
| 
 | ||||
|   app.use('/api', require('connect-send-error').error()); | ||||
|   app.use('/assets', require('connect-send-error').error()); | ||||
|   app.use('/', function (req, res, next) { | ||||
|     // If this doesn't look like an API we can move along
 | ||||
|     if (!/\/api(\/|$)/.test(req.url)) { | ||||
|       // /^api\./.test(req.hostname) &&
 | ||||
|     // If this doesn't look like an API or assets we can move along
 | ||||
| 
 | ||||
|     /* | ||||
|     console.log('.'); | ||||
|     console.log('[main.js] req.url, req.hostname'); | ||||
|     console.log(req.url); | ||||
|     console.log(req.hostname); | ||||
|     console.log('.'); | ||||
|     */ | ||||
| 
 | ||||
|     if (!/\/(api|assets)(\/|$)/.test(req.url)) { | ||||
|       //console.log('[main.js] api|assets');
 | ||||
|       next(); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // keep https://assets.example.com/assets but skip https://example.com/assets
 | ||||
|     if (/\/assets(\/|$)/.test(req.url) && !/(^|\.)(api|assets)(\.)/.test(req.hostname) && !/^[0-9\.]+$/.test(req.hostname)) { | ||||
|       //console.log('[main.js] skip');
 | ||||
|       next(); | ||||
|       return; | ||||
|     } | ||||
| @ -325,6 +342,7 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps, errorIfApi | ||||
|     return; | ||||
|   }); | ||||
|   app.use('/', errorIfApi); | ||||
|   app.use('/', errorIfAssets); | ||||
|   app.use('/', serveStatic); | ||||
|   app.use('/', serveApps); | ||||
| 
 | ||||
|  | ||||
| @ -181,6 +181,50 @@ function deepFreeze(obj) { | ||||
|   Object.freeze(obj); | ||||
| } | ||||
| 
 | ||||
| function cookieOauth3(req, res, next) { | ||||
|   req.oauth3 = {}; | ||||
| 
 | ||||
|   var token = req.cookies.jwt; | ||||
| 
 | ||||
|   req.oauth3.encodedToken = token; | ||||
|   req.oauth3.verifyAsync = function (jwt) { | ||||
|     return verifyToken(jwt || token); | ||||
|   }; | ||||
| 
 | ||||
|   return verifyToken(token).then(function  (decoded) { | ||||
|     req.oauth3.token = decoded; | ||||
|     if (!decoded) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     var ppid = decoded.sub || decoded.ppid || decoded.appScopedId; | ||||
|     req.oauth3.ppid = ppid; | ||||
|     req.oauth3.accountIdx = ppid+'@'+decoded.iss; | ||||
| 
 | ||||
|     var hash = require('crypto').createHash('sha256').update(req.oauth3.accountIdx).digest('base64'); | ||||
|     hash = hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+/g, ''); | ||||
|     req.oauth3.accountHash = hash; | ||||
| 
 | ||||
|     req.oauth3.rescope = function (sub) { | ||||
|       // TODO: this function is supposed to convert PPIDs of different parties to some account
 | ||||
|       // ID that allows application to keep track of permisions and what-not.
 | ||||
|       return PromiseA.resolve(sub || hash); | ||||
|     }; | ||||
|   }).then(function () { | ||||
|     deepFreeze(req.oauth3); | ||||
|     //Object.defineProperty(req, 'oauth3', {configurable: false, writable: false});
 | ||||
|     next(); | ||||
|   }, function (err) { | ||||
|     if ('E_NO_TOKEN' === err.code) { | ||||
|       next(); | ||||
|       return; | ||||
|     } | ||||
|     console.error('[walnut] cookie lib/oauth3 error:'); | ||||
|     console.error(err); | ||||
|     res.send(err); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function attachOauth3(req, res, next) { | ||||
|   req.oauth3 = {}; | ||||
| 
 | ||||
| @ -215,14 +259,15 @@ function attachOauth3(req, res, next) { | ||||
|     }; | ||||
|   }).then(function () { | ||||
|     deepFreeze(req.oauth3); | ||||
|     Object.defineProperty(req, 'oauth3', {configurable: false, writable: false}); | ||||
|     //Object.defineProperty(req, 'oauth3', {configurable: false, writable: false});
 | ||||
|     next(); | ||||
|   }, function (err) { | ||||
|     console.error('[walnut] lib/oauth3 error:'); | ||||
|     console.error('[walnut] JWT lib/oauth3 error:'); | ||||
|     console.error(err); | ||||
|     res.send(err); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| module.exports.attachOauth3 = attachOauth3; | ||||
| module.exports.cookieOauth3 = cookieOauth3; | ||||
| module.exports.verifyToken = verifyToken; | ||||
|  | ||||
| @ -150,6 +150,10 @@ module.exports.create = function (webserver, xconfx, state) { | ||||
|             models: models | ||||
|             // TODO don't let packages use this directly
 | ||||
|           , Promise: PromiseA | ||||
|           , dns: PromiseA.promisifyAll(require('dns')) | ||||
|           , crypto: PromiseA.promisifyAll(require('crypto')) | ||||
|           , fs: PromiseA.promisifyAll(require('fs')) | ||||
|           , path: require('path') | ||||
|           }; | ||||
|           var apiFactories = { | ||||
|             memstoreFactory: { create: scopeMemstore } | ||||
| @ -180,7 +184,7 @@ module.exports.create = function (webserver, xconfx, state) { | ||||
|           function setupMain() { | ||||
|             if (xconfx.debug) { console.log('[main] setup'); } | ||||
|             mainApp = express(); | ||||
|             require('./main').create(mainApp, xconfx, apiFactories, apiDeps, errorIfApi).then(function () { | ||||
|             require('./main').create(mainApp, xconfx, apiFactories, apiDeps, errorIfApi, errorIfAssets).then(function () { | ||||
|               if (xconfx.debug) { console.log('[main] ready'); } | ||||
|               // TODO process.send({});
 | ||||
|             }); | ||||
| @ -225,6 +229,24 @@ module.exports.create = function (webserver, xconfx, state) { | ||||
|             next(); | ||||
|           } | ||||
| 
 | ||||
|           function errorIfNotAssets(req, res, next) { | ||||
|             var hostname = req.hostname || req.headers.host; | ||||
| 
 | ||||
|             if (!/^assets\.[a-z0-9\-]+/.test(hostname)) { | ||||
|               res.send({ error: | ||||
|                { message: "['" + hostname + req.url + "'] protected asset access is restricted to proper 'asset'-prefixed lowercase subdomains." | ||||
|                    + " The HTTP 'Host' header must exist and must begin with 'assets.' as in 'assets.example.com'." | ||||
|                    + " For development you may test with assets.localhost.daplie.me (or any domain by modifying your /etc/hosts)" | ||||
|                , code: 'E_NOT_API' | ||||
|                , _hostname: hostname | ||||
|                } | ||||
|               }); | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             next(); | ||||
|           } | ||||
| 
 | ||||
|           function errorIfApi(req, res, next) { | ||||
|             if (!/^api\./.test(req.headers.host)) { | ||||
|               next(); | ||||
| @ -240,7 +262,25 @@ module.exports.create = function (webserver, xconfx, state) { | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             res.send({ error: { code: 'E_NO_IMPL', message: "not implemented" } }); | ||||
|             res.send({ error: { code: 'E_NO_IMPL', message: "API not implemented" } }); | ||||
|           } | ||||
| 
 | ||||
|           function errorIfAssets(req, res, next) { | ||||
|             if (!/^assets\./.test(req.headers.host)) { | ||||
|               next(); | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             // has api. hostname prefix
 | ||||
| 
 | ||||
|             // doesn't have /api url prefix
 | ||||
|             if (!/^\/assets\//.test(req.url)) { | ||||
|               console.log('[walnut/worker assets] req.url', req.url); | ||||
|               res.send({ error: { message: "missing /assets/ url prefix" } }); | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             res.send({ error: { code: 'E_NO_IMPL', message: "assets handler not implemented" } }); | ||||
|           } | ||||
| 
 | ||||
|           app.disable('x-powered-by'); | ||||
| @ -258,8 +298,11 @@ module.exports.create = function (webserver, xconfx, state) { | ||||
|           })); | ||||
|           app.use('/api', recase); | ||||
| 
 | ||||
|           var cookieParser = require('cookie-parser'); // signing is done in JWT
 | ||||
| 
 | ||||
|           app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); | ||||
|           app.use('/api', errorIfNotApi); | ||||
|           app.use('/assets', /*errorIfNotAssets,*/ cookieParser()); // serializer { path: '/assets', httpOnly: true, sameSite: true/*, domain: assets.example.com*/ }
 | ||||
|           app.use('/', function (req, res) { | ||||
|             if (!(req.encrypted || req.secure)) { | ||||
|               // did not come from https
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user