forked from coolaj86/goldilocks.js
		
	
		
			
				
	
	
		
			237 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			237 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| module.exports.dependencies = [ 'OAUTH3', 'storage.owners', 'options.device' ];
 | |
| module.exports.create = function (deps, conf) {
 | |
|   var scmp = require('scmp');
 | |
|   var crypto = require('crypto');
 | |
|   var jwt = require('jsonwebtoken');
 | |
|   var bodyParser = require('body-parser');
 | |
|   var jsonParser = bodyParser.json({
 | |
|     inflate: true, limit: '100kb', reviver: null, strict: true /* type, verify */
 | |
|   });
 | |
| 
 | |
|   var api = deps.api;
 | |
| 
 | |
|   /*
 | |
|   var owners;
 | |
|   deps.storage.owners.on('set', function (_owners) {
 | |
|     owners = _owners;
 | |
|   });
 | |
|   */
 | |
| 
 | |
|   function handleCors(req, res, methods) {
 | |
|     if (!methods) {
 | |
|       methods = ['GET', 'POST'];
 | |
|     }
 | |
|     if (!Array.isArray(methods)) {
 | |
|       methods = [ methods ];
 | |
|     }
 | |
| 
 | |
|     res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
 | |
|     res.setHeader('Access-Control-Allow-Methods', methods.join(', '));
 | |
|     res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
 | |
| 
 | |
|     if (req.method.toUpperCase() !== 'OPTIONS') {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     res.setHeader('Allow', methods.join(', '));
 | |
|     res.end();
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   function isAuthorized(req, res, fn) {
 | |
|     var auth = jwt.decode((req.headers.authorization||'').replace(/^bearer\s+/i, ''));
 | |
|     if (!auth) {
 | |
|       res.setHeader('Content-Type', 'application/json;');
 | |
|       res.end(JSON.stringify({ error: { message: "no token", code: 'E_NO_TOKEN', uri: undefined } }));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     var id = crypto.createHash('sha256').update(auth.sub).digest('hex');
 | |
|     return deps.storage.owners.exists(id).then(function (exists) {
 | |
|       if (!exists) {
 | |
|         res.setHeader('Content-Type', 'application/json;');
 | |
|         res.end(JSON.stringify({ error: { message: "not authorized", code: 'E_NO_AUTHZ', uri: undefined } }));
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       req.userId = id;
 | |
|       fn();
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     init: function (req, res) {
 | |
|       if (handleCors(req, res, 'POST')) {
 | |
|         return;
 | |
|       }
 | |
|       if (req.method !== 'POST') {
 | |
|         res.statusCode = 405;
 | |
|         res.setHeader('Content-Type', 'application/json');
 | |
|         res.end(JSON.stringify({ error: { message: 'method '+req.method+' not allowed'}}));
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       jsonParser(req, res, function () {
 | |
| 
 | |
|       return deps.PromiseA.resolve().then(function () {
 | |
|         console.log('init POST body', req.body);
 | |
| 
 | |
|         var auth = jwt.decode((req.headers.authorization||'').replace(/^bearer\s+/i, ''));
 | |
|         var token = jwt.decode(req.body.access_token);
 | |
|         var refresh = jwt.decode(req.body.refresh_token);
 | |
|         auth.sub = auth.sub || auth.acx.id;
 | |
|         token.sub = token.sub || token.acx.id;
 | |
|         refresh.sub = refresh.sub || refresh.acx.id;
 | |
| 
 | |
|         // TODO validate token with issuer, but as-is the sub is already a secret
 | |
|         var id = crypto.createHash('sha256').update(auth.sub).digest('hex');
 | |
|         var tid = crypto.createHash('sha256').update(token.sub).digest('hex');
 | |
|         var rid = crypto.createHash('sha256').update(refresh.sub).digest('hex');
 | |
|         var session = {
 | |
|           access_token: req.body.access_token
 | |
|         , token: token
 | |
|         , refresh_token: req.body.refresh_token
 | |
|         , refresh: refresh
 | |
|         };
 | |
| 
 | |
|         console.log('ids', id, tid, rid);
 | |
| 
 | |
|         if (req.body.ip_url) {
 | |
|           // TODO set options / GunDB
 | |
|           conf.ip_url = req.body.ip_url;
 | |
|         }
 | |
| 
 | |
|         return deps.storage.owners.all().then(function (results) {
 | |
|           console.log('results', results);
 | |
|           var err;
 | |
| 
 | |
|           // There is no owner yet. First come, first serve.
 | |
|           if (!results || !results.length) {
 | |
|             if (tid !== id || rid !== id) {
 | |
|               err = new Error(
 | |
|                 "When creating an owner the Authorization Bearer and Token and Refresh must all match"
 | |
|               );
 | |
|               return deps.PromiseA.reject(err);
 | |
|             }
 | |
|             console.log('no owner, creating');
 | |
|             return deps.storage.owners.set(id, session);
 | |
|           }
 | |
|           console.log('has results');
 | |
| 
 | |
|           // There are onwers. Is this one of them?
 | |
|           if (!results.some(function (token) {
 | |
|             return scmp(id, token.id);
 | |
|           })) {
 | |
|             err = new Error("Authorization token does not belong to an existing owner.");
 | |
|             return deps.PromiseA.reject(err);
 | |
|           }
 | |
|           console.log('has correct owner');
 | |
| 
 | |
|           // We're adding an owner, unless it already exists
 | |
|           if (!results.some(function (token) {
 | |
|             return scmp(tid, token.id);
 | |
|           })) {
 | |
|             console.log('adds new owner with existing owner');
 | |
|             return deps.storage.owners.set(id, session);
 | |
|           }
 | |
|         }).then(function () {
 | |
|           res.setHeader('Content-Type', 'application/json;');
 | |
|           res.end(JSON.stringify({ success: true }));
 | |
|         });
 | |
|       })
 | |
|       .catch(function (err) {
 | |
|         res.setHeader('Content-Type', 'application/json;');
 | |
|         res.end(JSON.stringify({ error: { message: err.message, code: err.code, uri: err.uri } }));
 | |
|       });
 | |
| 
 | |
|       });
 | |
|     }
 | |
|   , tunnel: function (req, res) {
 | |
|       if (handleCors(req, res)) {
 | |
|         return;
 | |
|       }
 | |
|       isAuthorized(req, res, function () {
 | |
|         if ('POST' !== req.method) {
 | |
|           res.setHeader('Content-Type', 'application/json');
 | |
|           return deps.tunneler.get(req.userId).then(function (result) {
 | |
|             res.end(JSON.stringify(result));
 | |
|           }, function (err) {
 | |
|             res.end(JSON.stringify({ error: { message: err.message, code: err.code, uri: err.uri } }));
 | |
|           });
 | |
|         }
 | |
| 
 | |
|         jsonParser(req, res, function () {
 | |
| 
 | |
|           console.log('req.body', req.body);
 | |
| 
 | |
|           return deps.storage.owners.get(req.userId).then(function (session) {
 | |
|             return api.tunnel(deps, session).then(function () {
 | |
|               res.setHeader('Content-Type', 'application/json;');
 | |
|               res.end(JSON.stringify({ success: true }));
 | |
|             }, function (err) {
 | |
|               res.setHeader('Content-Type', 'application/json;');
 | |
|               res.end(JSON.stringify({ error: { message: err.message, code: err.code, uri: err.uri } }));
 | |
|             });
 | |
|           });
 | |
|         });
 | |
|       });
 | |
|     }
 | |
|   , config: function (req, res) {
 | |
|       if (handleCors(req, res)) {
 | |
|         return;
 | |
|       }
 | |
|       isAuthorized(req, res, function () {
 | |
|         if ('POST' !== req.method) {
 | |
|           res.setHeader('Content-Type', 'application/json;');
 | |
|           res.end(JSON.stringify(deps.recase.snakeCopy(conf)));
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         jsonParser(req, res, function () {
 | |
|           console.log('config POST body', req.body);
 | |
| 
 | |
|           // Since we are sending the changes to another process we don't really
 | |
|           // have a good way of seeing if it worked, so always report success
 | |
|           deps.storage.config.save(req.body);
 | |
|           res.setHeader('Content-Type', 'application/json;');
 | |
|           res.end('{"success":true}');
 | |
|         });
 | |
|       });
 | |
|     }
 | |
|   , request: function (req, res) {
 | |
|       if (handleCors(req, res, '*')) {
 | |
|         return;
 | |
|       }
 | |
|       isAuthorized(req, res, function () {
 | |
|       jsonParser(req, res, function () {
 | |
| 
 | |
|         deps.request({
 | |
|           method: req.body.method || 'GET'
 | |
|         , url: req.body.url
 | |
|         , headers: req.body.headers
 | |
|         , body: req.body.data
 | |
|         }).then(function (resp) {
 | |
|           if (resp.body instanceof Buffer || 'string' === typeof resp.body) {
 | |
|             resp.body = JSON.parse(resp.body);
 | |
|           }
 | |
| 
 | |
|           return {
 | |
|             statusCode: resp.statusCode
 | |
|           , status: resp.status
 | |
|           , headers: resp.headers
 | |
|           , body: resp.body
 | |
|           , data: resp.data
 | |
|           };
 | |
|         }).then(function (result) {
 | |
|           res.send(result);
 | |
|         });
 | |
| 
 | |
|       });
 | |
|       });
 | |
|     }
 | |
|   , _api: api
 | |
|   };
 | |
| };
 |