walnut.js/lib/apis.js

196 lines
6.1 KiB
JavaScript
Raw Normal View History

2016-04-09 23:14:00 +00:00
'use strict';
module.exports.create = function (xconfx, apiFactories, apiDeps) {
var PromiseA = apiDeps.Promise;
var express = require('express');
var fs = PromiseA.promisifyAll(require('fs'));
var path = require('path');
2017-05-19 23:37:28 +00:00
var localCache = { rests: {}, pkgs: {} };
2016-04-09 23:14:00 +00:00
// TODO xconfx.apispath
2017-05-19 23:37:28 +00:00
xconfx.restPath = path.join(__dirname, '..', '..', 'packages', 'rest');
xconfx.appApiGrantsPath = path.join(__dirname, '..', '..', 'packages', 'client-api-grants');
2016-04-09 23:14:00 +00:00
function notConfigured(req, res) {
2017-05-19 23:37:28 +00:00
res.send({ error: { message: "api '" + req.pkgId + "' not configured for domain '" + req.experienceId + "'" } });
2016-04-09 23:14:00 +00:00
}
2017-05-19 23:37:28 +00:00
/*
function isThisPkgInstalled(myConf, pkgId) {
2016-04-09 23:14:00 +00:00
}
2017-05-19 23:37:28 +00:00
*/
function isThisClientAllowedToUseThisPkg(myConf, clientUrih, pkgId) {
var appApiGrantsPath = path.join(myConf.appApiGrantsPath, clientUrih);
return fs.readFileAsync(appApiGrantsPath, 'utf8').then(function (text) {
return text.trim().split(/\n/);
}, function (/*err*/) {
return [];
}).then(function (apis) {
if (!apis.some(function (api) {
if (api === pkgId) {
return true;
}
})) {
if (clientUrih === ('api.' + xconfx.setupDomain) && 'org.oauth3.consumer' === pkgId) {
// fallthrough
return true;
} else {
return null;
2016-04-09 23:14:00 +00:00
}
}
2017-05-19 23:37:28 +00:00
});
}
2016-04-09 23:14:00 +00:00
2017-05-19 23:37:28 +00:00
function loadRestHelper(myConf, pkgId) {
var pkgPath = path.join(myConf.restPath, pkgId);
2016-04-09 23:14:00 +00:00
2017-05-19 23:37:28 +00:00
return fs.readFileAsync(path.join(pkgPath, 'package.json'), 'utf8').then(function (text) {
var pkg = JSON.parse(text);
var deps = {};
var myApp;
2016-04-09 23:14:00 +00:00
2017-05-19 23:37:28 +00:00
if (pkg.walnut) {
pkgPath = path.join(pkgPath, pkg.walnut);
}
2016-04-09 23:14:00 +00:00
2017-05-19 23:37:28 +00:00
Object.keys(apiDeps).forEach(function (key) {
deps[key] = apiDeps[key];
});
Object.keys(apiFactories).forEach(function (key) {
deps[key] = apiFactories[key];
});
2016-04-09 23:14:00 +00:00
2017-05-19 23:37:28 +00:00
// 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
deps.memstore = apiFactories.memstoreFactory.create(pkgId);
console.log('DEBUG pkgPath', pkgPath);
myApp = express();
//
// TODO handle /accounts/:accountId
//
return PromiseA.resolve(require(pkgPath).create({
etcpath: xconfx.etcpath
}/*pkgConf*/, deps/*pkgDeps*/, myApp/*myApp*/)).then(function (handler) {
localCache.pkgs[pkgId] = { pkg: pkg, handler: handler || myApp, createdAt: Date.now() };
return localCache.pkgs[pkgId];
});
2016-04-09 23:14:00 +00:00
});
}
// Read packages/apis/sub.sld.tld (forward dns) to find list of apis as tld.sld.sub (reverse dns)
// TODO packages/allowed_apis/sub.sld.tld (?)
// TODO auto-register org.oauth3.consumer for primaryDomain (and all sites?)
2017-05-19 23:37:28 +00:00
function loadRestHandler(myConf, pkgId) {
return PromiseA.resolve().then(function () {
if (!localCache.pkgs[pkgId]) {
return loadRestHelper(myConf, pkgId);
}
2016-04-09 23:14:00 +00:00
2017-05-19 23:37:28 +00:00
return localCache.pkgs[pkgId];
// TODO expire require cache
/*
if (Date.now() - localCache.pkgs[pkgId].createdAt < (5 * 60 * 1000)) {
return;
}
*/
}, function (/*err*/) {
// TODO what kind of errors might we want to handle?
return null;
}).then(function (restPkg) {
return restPkg;
});
2016-04-09 23:14:00 +00:00
}
2016-06-08 18:05:10 +00:00
var CORS = require('connect-cors');
var cors = CORS({ credentials: true, headers: [
'X-Requested-With'
, 'X-HTTP-Method-Override'
, 'Content-Type'
, 'Accept'
, 'Authorization'
], methods: [ "GET", "POST", "PATCH", "PUT", "DELETE" ] });
2017-05-19 23:37:28 +00:00
var staleAfter = (5 * 60 * 1000);
2016-06-08 18:05:10 +00:00
2016-04-09 23:14:00 +00:00
return function (req, res, next) {
2016-06-08 18:05:10 +00:00
cors(req, res, function () {
2017-05-19 23:37:28 +00:00
var clientUrih = req.hostname + req.url.replace(/\/api\/.*/, '/').replace(/\/+/g, '#').replace(/#$/, '');
var pkgId = req.url.replace(/.*\/api\//, '').replace(/\/.*/, '');
var now = Date.now();
var hasBeenHandled = false;
2016-06-08 18:05:10 +00:00
2017-05-19 23:37:28 +00:00
// Existing (Deprecated)
2016-06-08 18:05:10 +00:00
Object.defineProperty(req, 'experienceId', {
enumerable: true
, configurable: false
2017-05-19 23:37:28 +00:00
, writable: false
, value: clientUrih
});
Object.defineProperty(req, 'pkgId', {
enumerable: true
, configurable: false
, writable: false
, value: pkgId
});
// New
Object.defineProperty(req, 'clientUrih', {
enumerable: true
, configurable: false
2016-06-08 18:05:10 +00:00
, writable: false
// TODO this identifier may need to be non-deterministic as to transfer if a domain name changes but is still the "same" app
// (i.e. a company name change. maybe auto vs manual register - just like oauth3?)
// NOTE: probably best to alias the name logically
2017-05-19 23:37:28 +00:00
, value: clientUrih
2016-06-08 18:05:10 +00:00
});
2017-05-19 23:37:28 +00:00
Object.defineProperty(req, 'pkgId', {
2016-06-08 18:05:10 +00:00
enumerable: true
, configurable: false
, writable: false
2017-05-19 23:37:28 +00:00
, value: pkgId
2016-06-08 18:05:10 +00:00
});
2016-04-09 23:14:00 +00:00
2017-05-19 23:37:28 +00:00
// TODO cache permission (although the FS is already cached, NBD)
return isThisClientAllowedToUseThisPkg(xconfx, clientUrih, pkgId).then(function (yes) {
if (!yes) {
notConfigured(req, res);
return null;
}
2016-04-09 23:14:00 +00:00
2017-05-19 23:37:28 +00:00
if (localCache.rests[pkgId]) {
localCache.rests[pkgId].handler(req, res, next);
hasBeenHandled = true;
}
if (now - localCache.rests[pkgId].createdAt > staleAfter) {
localCache.rests[pkgId] = null;
}
if (!localCache.rests[pkgId]) {
//return doesThisPkgExist
return loadRestHandler(xconfx, pkgId).then(function (myHandler) {
if (!myHandler) {
notConfigured(req, res);
return;
}
localCache.rests[pkgId] = { handler: myHandler.handle, createdAt: now };
if (!hasBeenHandled) {
myHandler.handle(req, res, next);
}
});
}
});
2016-06-08 18:05:10 +00:00
});
2016-04-09 23:14:00 +00:00
};
};