diff --git a/boot/master.js b/boot/master.js index 680f48b..7aec33a 100644 --- a/boot/master.js +++ b/boot/master.js @@ -10,7 +10,6 @@ console.info('platform:', process.platform); console.info('\n\n\n[MASTER] Welcome to WALNUT!'); var cluster = require('cluster'); -var path = require('path'); //var minWorkers = 2; var numCores = 2; // Math.max(minWorkers, require('os').cpus().length); var workers = []; @@ -31,10 +30,6 @@ var conf = { var state = {}; var caddy; -if (useCaddy) { - conf.caddypath = caddypath; -} - function fork() { if (workers.length < numCores) { workers.push(cluster.fork()); @@ -42,7 +37,6 @@ function fork() { } cluster.on('online', function (worker) { - var path = require('path'); // TODO XXX Should these be configurable? If so, where? var certPaths = config.certPaths; var info; @@ -66,6 +60,9 @@ cluster.on('online', function (worker) { // TODO let this load after server is listening , redirects: config.redirects , ddns: config.ddns + , 'org.oauth3.consumer': config['org.oauth3.consumer'] + , 'org.oauth3.provider': config['org.oauth3.provider'] + , keys: config.keys } }; worker.send(info); diff --git a/lib/oauth3-auth.js b/lib/oauth3-auth.js new file mode 100644 index 0000000..18e0d53 --- /dev/null +++ b/lib/oauth3-auth.js @@ -0,0 +1,95 @@ +'use strict'; + +var PromiseA = require('bluebird'); + +module.exports.inject = function (app) { + //var jwsUtils = require('./lib/jws-utils').create(signer); + var CORS = require('connect-cors'); + + // Allows CORS access to API with ?access_token= + // TODO Access-Control-Max-Age: 600 + // TODO How can we help apps handle this? token? + // TODO allow apps to configure trustedDomains, auth, etc + app.use('/', CORS({ credentials: true, headers: [ + 'X-Requested-With' + , 'X-HTTP-Method-Override' + , 'Content-Type' + , 'Accept' + , 'Authorization' + ], methods: [ "GET", "POST", "PATCH", "PUT", "DELETE" ] })); + + //function weakDecipher(secret, val) { return require('./weak-crypt').weakDecipher(val, secret); } + + // + // Generic Session / Login / Account Routes + // + function parseAccessToken(req, opts) { + var token; + var parts; + var scheme; + var credentials; + + if (req.headers && req.headers.authorization) { + parts = req.headers.authorization.split(' '); + + if (parts.length !== 2) { + return PromiseA.reject(new Error("malformed Authorization header")); + } + + scheme = parts[0]; + credentials = parts[1]; + + if (-1 !== (opts && opts.schemes || ['token', 'bearer']).indexOf(scheme.toLowerCase())) { + token = credentials; + } + } + + if (req.body && req.body.access_token) { + if (token) { PromiseA.reject(new Error("token exists in header and body")); } + token = req.body.access_token; + } + + // TODO disallow query with req.method === 'GET' + // (cookies should be used for protected static assets) + if (req.query && req.query.access_token) { + if (token) { PromiseA.reject(new Error("token already exists in either header or body and also in query")); } + token = req.query.access_token; + } + + /* + err = new Error(challenge()); + err.code = 'E_BEARER_REALM'; + + if (!token) { return PromiseA.reject(err); } + */ + + return PromiseA.resolve(token); + } + + function getToken(req, res, next) { + req.oauth3 = {}; + + parseAccessToken(req).then(function (token) { + if (!token) { + next(); + return; + } + + var jwt = require('jsonwebtoken'); + var data = jwt.decode(token); + var err; + + if (!data) { + err = new Error('not a json web token'); + err.code = 'E_NOT_JWT'; + return PromiseA.reject(err); + } + + req.oauth3.token = token; + + next(); + }); + } + + app.use('/', getToken); +}; diff --git a/lib/package-server.js b/lib/package-server.js index 2e715d4..54a5553 100644 --- a/lib/package-server.js +++ b/lib/package-server.js @@ -143,7 +143,7 @@ function getApi(pkgConf, pkgDeps, packagedApi) { if (packagedApi._apipkg.walnut) { pkgpath += '/' + packagedApi._apipkg.walnut; } - promise = require(pkgpath).create(pkgConf, pkgDeps, myApp); + promise = PromiseA.resolve(require(pkgpath).create(pkgConf, pkgDeps, myApp)); } catch(e) { reject(e); return; @@ -152,22 +152,39 @@ function getApi(pkgConf, pkgDeps, packagedApi) { promise.then(function () { // TODO give pub/priv pair for app and all public keys // packagedApi._api = require(pkgpath).create(pkgConf, pkgDeps, myApp); - packagedApi._api = require('express')(); + packagedApi._api = require('express-lazy')(); packagedApi._api_app = myApp; + + require('./oauth3-auth').inject(packagedApi._api, pkgConf, pkgDeps); + + // DEBUG + packagedApi._api.use('/', function (req, res, next) { + console.log('[DEBUG pkgsrv]', req.method, req.hostname, req.url); + next(); + }); + // TODO fix backwards compat + // /api/com.example.foo (no change) packagedApi._api.use('/', packagedApi._api_app); - // /api/com.example.foo => / - packagedApi._api.use('/api/' + packagedApi.id, function (req, res, next) { - //console.log('api mangle 2:', '/api/' + packagedApi.api.id, req.url); - packagedApi._api_app(req, res, next); - }); + // /api/com.example.foo => /api packagedApi._api.use('/', function (req, res, next) { + var priorUrl = req.url; req.url = '/api' + req.url.slice(('/api/' + packagedApi.id).length); //console.log('api mangle 3:', req.url); + packagedApi._api_app(req, res, function (err) { + req.url = priorUrl; + next(err); + }); + }); + + // /api/com.example.foo => / + packagedApi._api.use('/api/' + packagedApi.id, function (req, res, next) { + // console.log('api mangle 2:', '/api/' + packagedApi.id, req.url); packagedApi._api_app(req, res, next); }); + resolve(packagedApi._api); }, reject); }); @@ -289,7 +306,7 @@ function runApi(opts, router, req, res, next) { return; } - console.log("pkgpath", pkgConf.apipath, packagedApi.id); + console.log("[DEBUG pkgpath]", pkgConf.apipath, packagedApi.id); loadApi(pkgConf, pkgDeps, packagedApi).then(function (api) { api(req, res, next); }, function (err) { diff --git a/lib/worker.js b/lib/worker.js index 817174e..0dc29fd 100644 --- a/lib/worker.js +++ b/lib/worker.js @@ -178,6 +178,9 @@ module.exports.create = function (webserver, info, state) { , pubkey: info.conf.pubkey , redirects: info.conf.redirects , apiPrefix: '/api' + , 'org.oauth3.consumer': info.conf['org.oauth3.consumer'] + , 'org.oauth3.provider': info.conf['org.oauth3.provider'] + , keys: info.conf.keys }; var pkgDeps = { memstore: memstore @@ -198,6 +201,16 @@ module.exports.create = function (webserver, info, state) { , systemSqlFactory: systemFactory , Promise: PromiseA }); + var recase = require('connect-recase')({ + // TODO allow explicit and or default flag + explicit: false + , default: 'snake' + , prefixes: ['/api'] + // TODO allow exclude + //, exclusions: [config.oauthPrefix] + , exceptions: {} + //, cancelParam: 'camel' + }); function handlePackages(req, res, next) { // TODO move to caddy parser? @@ -221,7 +234,7 @@ module.exports.create = function (webserver, info, state) { // Generic Template API // app - .use(require('body-parser').json({ + .use('/api', require('body-parser').json({ strict: true // only objects and arrays , inflate: true // limited to due performance issues with JSON.parse and JSON.stringify @@ -243,6 +256,8 @@ module.exports.create = function (webserver, info, state) { .use(require('connect-send-error').error()) ; + app.use('/api', recase); + app.use('/', handlePackages); app.use('/', function (err, req, res, next) { console.error('[Error Handler]');