'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.statusCode = 401; 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.statusCode = 401; 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" ); err.statusCode = 400; 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."); err.statusCode = 401; 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(tid, 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.statusCode = err.statusCode || 500; 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.tunnelClients.get(req.userId).then(function (result) { res.end(JSON.stringify(result)); }, function (err) { res.statusCode = 500; res.end(JSON.stringify({ error: { message: err.message, code: err.code, uri: err.uri } })); }); } return deps.storage.owners.get(req.userId).then(function (session) { return deps.tunnelClients.start(session).then(function () { res.setHeader('Content-Type', 'application/json;'); res.end(JSON.stringify({ success: true })); }, function (err) { res.setHeader('Content-Type', 'application/json;'); res.statusCode = 500; 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 }; };