239 lines
7.7 KiB
JavaScript
239 lines
7.7 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.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(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.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 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.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
|
|
};
|
|
};
|