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 express = require('express-lazy'); | ||||||
|   var fs = PromiseA.promisifyAll(require('fs')); |   var fs = PromiseA.promisifyAll(require('fs')); | ||||||
|   var path = require('path'); |   var path = require('path'); | ||||||
|   var localCache = { rests: {}, pkgs: {} }; |   var localCache = { rests: {}, pkgs: {}, assets: {} }; | ||||||
|   var promisableRequest = require('./common').promisableRequest; |   var promisableRequest = require('./common').promisableRequest; | ||||||
|   var rejectableRequest = require('./common').rejectableRequest; |   var rejectableRequest = require('./common').rejectableRequest; | ||||||
|   var crypto = require('crypto'); |   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); |     var appApiGrantsPath = path.join(myConf.appApiGrantsPath, clientUrih); | ||||||
| 
 | 
 | ||||||
|     return fs.readFileAsync(appApiGrantsPath, 'utf8').then(function (text) { |     return fs.readFileAsync(appApiGrantsPath, 'utf8').then(function (text) { | ||||||
| @ -51,12 +51,23 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|         return true; |         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
 |         // fallthrough
 | ||||||
|         return true; |         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)"); |     rejectableRequest(req, res, promise, "[walnut@daplie.com] required account (not /public)"); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function loadRestHelper(myConf, clientUrih, pkgId) { |   function loadRestHelperApi(myConf, clientUrih, pkg, pkgId, pkgPath) { | ||||||
|     var pkgPath = path.join(myConf.restPath, pkgId); |  | ||||||
|     var pkgLinks = []; |     var pkgLinks = []; | ||||||
|     pkgLinks.push(pkgId); |     pkgLinks.push(pkgId); | ||||||
| 
 |     var pkgRestApi; | ||||||
|     // 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 pkgDeps = {}; |     var pkgDeps = {}; | ||||||
|     var myApp; |     var myApp; | ||||||
|  |     var pkgPathApi; | ||||||
| 
 | 
 | ||||||
|  |     pkgPathApi = pkgPath; | ||||||
|     if (pkg.walnut) { |     if (pkg.walnut) { | ||||||
|           pkgPath = path.join(pkgPath, pkg.walnut); |       pkgPathApi = path.join(pkgPath, pkg.walnut); | ||||||
|     } |     } | ||||||
|  |     pkgRestApi = require(pkgPathApi); | ||||||
| 
 | 
 | ||||||
|     Object.keys(apiDeps).forEach(function (key) { |     Object.keys(apiDeps).forEach(function (key) { | ||||||
|       pkgDeps[key] = apiDeps[key]; |       pkgDeps[key] = apiDeps[key]; | ||||||
| @ -499,7 +495,6 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|           // (realized later)
 |           // (realized later)
 | ||||||
|           // HAHA HAHA HAHAHAHAHA this is my own gist... so much more polite attribution
 |           // HAHA HAHA HAHAHAHAHA this is my own gist... so much more polite attribution
 | ||||||
|           var scmp = require('scmp') |           var scmp = require('scmp') | ||||||
|                 , crypto = require('crypto') |  | ||||||
|             , mailgunExpirey = 15 * 60 * 1000 |             , mailgunExpirey = 15 * 60 * 1000 | ||||||
|             , mailgunHashType = 'sha256' |             , mailgunHashType = 'sha256' | ||||||
|             , mailgunSignatureEncoding = 'hex' |             , mailgunSignatureEncoding = 'hex' | ||||||
| @ -719,7 +714,7 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|     //
 |     //
 | ||||||
|     // TODO handle /accounts/:accountId
 |     // TODO handle /accounts/:accountId
 | ||||||
|     //
 |     //
 | ||||||
|         return PromiseA.resolve(require(pkgPath).create({ |     return PromiseA.resolve(pkgRestApi.create({ | ||||||
|       etcpath: xconfx.etcpath |       etcpath: xconfx.etcpath | ||||||
|     }/*pkgConf*/, pkgDeps/*pkgDeps*/, myApp/*myApp*/)).then(function (handler) { |     }/*pkgConf*/, pkgDeps/*pkgDeps*/, myApp/*myApp*/)).then(function (handler) { | ||||||
| 
 | 
 | ||||||
| @ -737,6 +732,213 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
| 
 | 
 | ||||||
|       return localCache.pkgs[pkgId]; |       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
 |       // sub.example.com/api should resolve to sub.example.com
 | ||||||
|       // example.com/subapp/api should resolve to example.com#subapp
 |       // example.com/subapp/api should resolve to example.com#subapp
 | ||||||
|       // sub.example.com/subapp/api should resolve to sub.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 clientUrih = req.hostname.replace(/^(api|assets)\./, '') + req.url.replace(/\/(api|assets)\/.*/, '/').replace(/\/+/g, '#').replace(/#$/, ''); | ||||||
|       var clientApiUri = req.hostname + req.url.replace(/\/api\/.*/, '/').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
 |       // Canonical package names
 | ||||||
|       // '/api/com.daplie.hello/hello' should resolve to 'com.daplie.hello'
 |       // '/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/hello' should also 'com.daplie.hello'
 | ||||||
|       // '/subapp/api/com.daplie.hello/' may exist... must be a small api
 |       // '/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 now = Date.now(); | ||||||
|       var hasBeenHandled = false; |       var hasBeenHandled = false; | ||||||
| 
 | 
 | ||||||
| @ -801,6 +1004,12 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|       , writable: false |       , writable: false | ||||||
|       , value: 'https://' + clientApiUri + '/api/' + pkgId |       , value: 'https://' + clientApiUri + '/api/' + pkgId | ||||||
|       }); |       }); | ||||||
|  |       Object.defineProperty(req, 'assetsUrlPrefix', { | ||||||
|  |         enumerable: true | ||||||
|  |       , configurable: false | ||||||
|  |       , writable: false | ||||||
|  |       , value: 'https://' + clientAssetsUri + '/assets/' + pkgId | ||||||
|  |       }); | ||||||
|       Object.defineProperty(req, 'experienceId', { |       Object.defineProperty(req, 'experienceId', { | ||||||
|         enumerable: true |         enumerable: true | ||||||
|       , configurable: false |       , configurable: false | ||||||
| @ -813,6 +1022,12 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|       , writable: false |       , writable: false | ||||||
|       , value: clientApiUri |       , value: clientApiUri | ||||||
|       }); |       }); | ||||||
|  |       Object.defineProperty(req, 'clientAssetsUri', { | ||||||
|  |         enumerable: true | ||||||
|  |       , configurable: false | ||||||
|  |       , writable: false | ||||||
|  |       , value: clientAssetsUri | ||||||
|  |       }); | ||||||
|       Object.defineProperty(req, 'apiId', { |       Object.defineProperty(req, 'apiId', { | ||||||
|         enumerable: true |         enumerable: true | ||||||
|       , configurable: false |       , configurable: false | ||||||
| @ -838,19 +1053,36 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { | |||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       // TODO cache permission (although the FS is already cached, NBD)
 |       // 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 (xconfx.debug) { console.log('[api.js] azp is allowed?', yes); }
 | ||||||
|         if (!yes) { |         if (!yes) { | ||||||
|           notConfigured(req, res); |           notConfigured(req, res); | ||||||
|           return null; |           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); |             localCache.rests[pkgId].handler(req, res, next); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (localCache.rests[pkgId]) { | ||||||
|  |           if (handleWithHandler()) { | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|           hasBeenHandled = true; |           hasBeenHandled = true; | ||||||
| 
 | 
 | ||||||
|           if (now - localCache.rests[pkgId].createdAt > staleAfter) { |           if (now - localCache.rests[pkgId].createdAt > staleAfter) { | ||||||
|             localCache.rests[pkgId] = null; |             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.rests[pkgId] = { handler: myHandler.handler, createdAt: now }; | ||||||
|  |             localCache.assets[pkgId] = { handler: myHandler.assetsHandler, createdAt: now }; | ||||||
|             if (!hasBeenHandled) { |             if (!hasBeenHandled) { | ||||||
|               //if (xconfx.debug) { console.log('[api.js] not configured'); }
 |               if (handleWithHandler()) { | ||||||
|               myHandler.handler(req, res, next); |                 return; | ||||||
|  |               } | ||||||
|             } |             } | ||||||
|           }); |           }); | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
|  | 
 | ||||||
|       rejectableRequest(req, res, promise, "[walnut@daplie.com] load api package"); |       rejectableRequest(req, res, promise, "[walnut@daplie.com] load api package"); | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
|  | |||||||
							
								
								
									
										26
									
								
								lib/main.js
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								lib/main.js
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| 'use strict'; | '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 PromiseA = require('bluebird'); | ||||||
|   var path = require('path'); |   var path = require('path'); | ||||||
|   var fs = PromiseA.promisifyAll(require('fs')); |   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/
 |   // TODO handle assets.example.com/sub/assets/com.example.xyz/
 | ||||||
| 
 | 
 | ||||||
|   app.use('/api', require('connect-send-error').error()); |   app.use('/api', require('connect-send-error').error()); | ||||||
|  |   app.use('/assets', require('connect-send-error').error()); | ||||||
|   app.use('/', function (req, res, next) { |   app.use('/', function (req, res, next) { | ||||||
|     // If this doesn't look like an API we can move along
 |     // If this doesn't look like an API or assets we can move along
 | ||||||
|     if (!/\/api(\/|$)/.test(req.url)) { | 
 | ||||||
|       // /^api\./.test(req.hostname) &&
 |     /* | ||||||
|  |     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(); |       next(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| @ -325,6 +342,7 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps, errorIfApi | |||||||
|     return; |     return; | ||||||
|   }); |   }); | ||||||
|   app.use('/', errorIfApi); |   app.use('/', errorIfApi); | ||||||
|  |   app.use('/', errorIfAssets); | ||||||
|   app.use('/', serveStatic); |   app.use('/', serveStatic); | ||||||
|   app.use('/', serveApps); |   app.use('/', serveApps); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -181,6 +181,50 @@ function deepFreeze(obj) { | |||||||
|   Object.freeze(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) { | function attachOauth3(req, res, next) { | ||||||
|   req.oauth3 = {}; |   req.oauth3 = {}; | ||||||
| 
 | 
 | ||||||
| @ -215,14 +259,15 @@ function attachOauth3(req, res, next) { | |||||||
|     }; |     }; | ||||||
|   }).then(function () { |   }).then(function () { | ||||||
|     deepFreeze(req.oauth3); |     deepFreeze(req.oauth3); | ||||||
|     Object.defineProperty(req, 'oauth3', {configurable: false, writable: false}); |     //Object.defineProperty(req, 'oauth3', {configurable: false, writable: false});
 | ||||||
|     next(); |     next(); | ||||||
|   }, function (err) { |   }, function (err) { | ||||||
|     console.error('[walnut] lib/oauth3 error:'); |     console.error('[walnut] JWT lib/oauth3 error:'); | ||||||
|     console.error(err); |     console.error(err); | ||||||
|     res.send(err); |     res.send(err); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports.attachOauth3 = attachOauth3; | module.exports.attachOauth3 = attachOauth3; | ||||||
|  | module.exports.cookieOauth3 = cookieOauth3; | ||||||
| module.exports.verifyToken = verifyToken; | module.exports.verifyToken = verifyToken; | ||||||
|  | |||||||
| @ -150,6 +150,10 @@ module.exports.create = function (webserver, xconfx, state) { | |||||||
|             models: models |             models: models | ||||||
|             // TODO don't let packages use this directly
 |             // TODO don't let packages use this directly
 | ||||||
|           , Promise: PromiseA |           , Promise: PromiseA | ||||||
|  |           , dns: PromiseA.promisifyAll(require('dns')) | ||||||
|  |           , crypto: PromiseA.promisifyAll(require('crypto')) | ||||||
|  |           , fs: PromiseA.promisifyAll(require('fs')) | ||||||
|  |           , path: require('path') | ||||||
|           }; |           }; | ||||||
|           var apiFactories = { |           var apiFactories = { | ||||||
|             memstoreFactory: { create: scopeMemstore } |             memstoreFactory: { create: scopeMemstore } | ||||||
| @ -180,7 +184,7 @@ module.exports.create = function (webserver, xconfx, state) { | |||||||
|           function setupMain() { |           function setupMain() { | ||||||
|             if (xconfx.debug) { console.log('[main] setup'); } |             if (xconfx.debug) { console.log('[main] setup'); } | ||||||
|             mainApp = express(); |             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'); } |               if (xconfx.debug) { console.log('[main] ready'); } | ||||||
|               // TODO process.send({});
 |               // TODO process.send({});
 | ||||||
|             }); |             }); | ||||||
| @ -225,6 +229,24 @@ module.exports.create = function (webserver, xconfx, state) { | |||||||
|             next(); |             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) { |           function errorIfApi(req, res, next) { | ||||||
|             if (!/^api\./.test(req.headers.host)) { |             if (!/^api\./.test(req.headers.host)) { | ||||||
|               next(); |               next(); | ||||||
| @ -240,7 +262,25 @@ module.exports.create = function (webserver, xconfx, state) { | |||||||
|               return; |               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'); |           app.disable('x-powered-by'); | ||||||
| @ -258,8 +298,11 @@ module.exports.create = function (webserver, xconfx, state) { | |||||||
|           })); |           })); | ||||||
|           app.use('/api', recase); |           app.use('/api', recase); | ||||||
| 
 | 
 | ||||||
|  |           var cookieParser = require('cookie-parser'); // signing is done in JWT
 | ||||||
|  | 
 | ||||||
|           app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); |           app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); | ||||||
|           app.use('/api', errorIfNotApi); |           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) { |           app.use('/', function (req, res) { | ||||||
|             if (!(req.encrypted || req.secure)) { |             if (!(req.encrypted || req.secure)) { | ||||||
|               // did not come from https
 |               // did not come from https
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user