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-20 04:51:44 +00:00
|
|
|
var msg = "api package '" + req.pkgId + "' not configured for client uri '" + req.experienceId + "'"
|
|
|
|
+ ". To configure it place a new line '" + req.pkgId + "' in the file '/srv/walnut/packages/client-api-grants/" + req.experienceId + "'"
|
|
|
|
;
|
|
|
|
|
|
|
|
res.send({ error: { message: msg } });
|
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) {
|
2017-05-20 00:09:48 +00:00
|
|
|
console.log('sanity', text);
|
2017-05-19 23:37:28 +00:00
|
|
|
return text.trim().split(/\n/);
|
2017-05-20 04:51:44 +00:00
|
|
|
}, function (err) {
|
|
|
|
if ('ENOENT' !== err.code) {
|
|
|
|
console.error(err);
|
|
|
|
}
|
2017-05-19 23:37:28 +00:00
|
|
|
return [];
|
|
|
|
}).then(function (apis) {
|
2017-05-20 00:09:48 +00:00
|
|
|
if (apis.some(function (api) {
|
2017-05-19 23:37:28 +00:00
|
|
|
if (api === pkgId) {
|
2017-05-20 00:09:48 +00:00
|
|
|
console.log(api, pkgId, api === pkgId);
|
2017-05-19 23:37:28 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
})) {
|
2017-05-20 00:09:48 +00:00
|
|
|
return true;
|
|
|
|
}
|
2017-05-19 23:37:28 +00:00
|
|
|
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-20 05:16:01 +00:00
|
|
|
// TODO should not require package.json. Should work with files alone.
|
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();
|
2017-05-20 05:16:01 +00:00
|
|
|
myApp.use('/', function preHandler(req, res, next) {
|
|
|
|
req.originalUrl = req.originalUrl || req.url;
|
|
|
|
// "/path/api/com.example/hello".replace(/.*\/api\//, '').replace(/([^\/]*\/+)/, '/') => '/hello'
|
|
|
|
req.url = req.url.replace(/\/api\//, '').replace(/.*\/api\//, '').replace(/([^\/]*\/+)/, '/');
|
|
|
|
console.log('[prehandler] req.url', req.url);
|
|
|
|
next();
|
|
|
|
});
|
2017-05-19 23:37:28 +00:00
|
|
|
//
|
|
|
|
// 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-20 00:09:48 +00:00
|
|
|
console.log('[sanity check]', req.url);
|
2017-05-20 04:51:44 +00:00
|
|
|
// Canonical client names
|
|
|
|
// example.com should use api.example.com/api for all requests
|
|
|
|
// sub.example.com/api should resolve to sub.example.com
|
|
|
|
// example.com/subpath/api should resolve to example.com#subapp
|
|
|
|
// sub.example.com/subpath/api should resolve to sub.example.com#subapp
|
2017-05-20 00:09:48 +00:00
|
|
|
var clientUrih = req.hostname.replace(/^api\./, '') + req.url.replace(/\/api\/.*/, '/').replace(/\/+/g, '#').replace(/#$/, '');
|
2017-05-20 04:51:44 +00:00
|
|
|
// 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(/\/.*/, '');
|
2017-05-19 23:37:28 +00:00
|
|
|
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
|
|
|
|
});
|
2017-05-20 00:09:48 +00:00
|
|
|
Object.defineProperty(req, 'apiId', {
|
2017-05-19 23:37:28 +00:00
|
|
|
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;
|
|
|
|
|
2017-05-20 00:09:48 +00:00
|
|
|
if (now - localCache.rests[pkgId].createdAt > staleAfter) {
|
|
|
|
localCache.rests[pkgId] = null;
|
|
|
|
}
|
2017-05-19 23:37:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!localCache.rests[pkgId]) {
|
|
|
|
//return doesThisPkgExist
|
|
|
|
|
|
|
|
return loadRestHandler(xconfx, pkgId).then(function (myHandler) {
|
|
|
|
if (!myHandler) {
|
|
|
|
notConfigured(req, res);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-05-20 00:09:48 +00:00
|
|
|
localCache.rests[pkgId] = { handler: myHandler.handler, createdAt: now };
|
2017-05-19 23:37:28 +00:00
|
|
|
if (!hasBeenHandled) {
|
2017-05-20 00:09:48 +00:00
|
|
|
myHandler.handler(req, res, next);
|
2017-05-19 23:37:28 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2016-06-08 18:05:10 +00:00
|
|
|
});
|
2016-04-09 23:14:00 +00:00
|
|
|
};
|
|
|
|
};
|