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…
Reference in New Issue