WIP implementing semi-proper accounts
This commit is contained in:
parent
1099a75509
commit
64281e4c93
|
@ -3,8 +3,9 @@
|
||||||
<title>Telebit Account</title>
|
<title>Telebit Account</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<h1>Login</h1>
|
||||||
<form class="js-auth-form">
|
<form class="js-auth-form">
|
||||||
<input class="js-auth-subject" type="email"/>
|
<input class="js-auth-subject" placeholder="email" type="email"/>
|
||||||
<button class="js-auth-submit" type="submit">Login</button>
|
<button class="js-auth-submit" type="submit">Login</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -19,37 +20,44 @@
|
||||||
});
|
});
|
||||||
var $ = function () { return document.querySelector.apply(document, arguments); }
|
var $ = function () { return document.querySelector.apply(document, arguments); }
|
||||||
|
|
||||||
function onChangeProvider(providerUri) {
|
function onChangeProvider(providerUri) {
|
||||||
// example https://oauth3.org
|
// example https://oauth3.org
|
||||||
return oauth3.setIdentityProvider(providerUri);
|
return oauth3.setIdentityProvider(providerUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This opens up the login window for the specified provider
|
// This opens up the login window for the specified provider
|
||||||
//
|
//
|
||||||
function onClickLogin(ev) {
|
function onClickLogin(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
var email = $('.js-auth-subject').value;
|
||||||
|
|
||||||
// TODO check subject for provider viability
|
// TODO check subject for provider viability
|
||||||
return oauth3.authenticate({
|
return oauth3.authenticate({
|
||||||
subject: $('.js-auth-subject').value
|
subject: email
|
||||||
|
, scope: 'email@oauth3.org'
|
||||||
}).then(function (session) {
|
}).then(function (session) {
|
||||||
|
|
||||||
console.info('Authentication was Successful:');
|
console.info('Authentication was Successful:');
|
||||||
console.log(session);
|
console.log(session);
|
||||||
|
|
||||||
// You can use the PPID (or preferably a hash of it) as the login for your app
|
// You can use the PPID (or preferably a hash of it) as the login for your app
|
||||||
// (it securely functions as both username and password which is known only by your app)
|
// (it securely functions as both username and password which is known only by your app)
|
||||||
// If you use a hash of it as an ID, you can also use the PPID itself as a decryption key
|
// If you use a hash of it as an ID, you can also use the PPID itself as a decryption key
|
||||||
//
|
//
|
||||||
console.info('Secure PPID (aka subject):', session.token.sub);
|
console.info('Secure PPID (aka subject):', session.token.sub);
|
||||||
|
|
||||||
return oauth3.request({
|
function listStuff() {
|
||||||
url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid.json'
|
window.alert("TODO: show authorized devices, domains, and connectivity information");
|
||||||
|
}
|
||||||
|
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid.json'
|
||||||
.replace(/:sub/g, session.token.sub)
|
.replace(/:sub/g, session.token.sub)
|
||||||
.replace(/:kid/g, session.token.iss)
|
.replace(/:kid/g, session.token.iss)
|
||||||
, session: session
|
, session: session
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
console.info("Public Key:");
|
console.info("Public Key:");
|
||||||
console.log(resp.data);
|
console.log(resp.data);
|
||||||
|
|
||||||
|
@ -62,25 +70,44 @@
|
||||||
console.log(resp.data);
|
console.log(resp.data);
|
||||||
|
|
||||||
return oauth3.request({
|
return oauth3.request({
|
||||||
url: 'https://api.telebit.cloud/api/telebit.cloud/account'
|
url: 'https://api.' + location.hostname + '/api/telebit.cloud/account'
|
||||||
, session: session
|
, session: session
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
|
|
||||||
console.info("Telebit Account:");
|
console.info("Telebit Account:");
|
||||||
console.log(resp.data);
|
console.log(resp.data);
|
||||||
|
|
||||||
|
if (1 === resp.data.accounts.length) {
|
||||||
|
listStuff(resp);
|
||||||
|
} else if (0 === resp.data.accounts.length) {
|
||||||
|
return oauth3.request({
|
||||||
|
url: 'https://api.' + location.hostname + 'api/telebit.cloud/account'
|
||||||
|
, method: 'POST'
|
||||||
|
, session: session
|
||||||
|
, body: {
|
||||||
|
email: email
|
||||||
|
}
|
||||||
|
}).then(function (resp) {
|
||||||
|
listStuff(resp);
|
||||||
|
});
|
||||||
|
} if (resp.data.accounts.length > 2) {
|
||||||
|
window.alert("Multiple accounts.");
|
||||||
|
} else {
|
||||||
|
window.alert("Bad response.");
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
console.error('Authentication Failed:');
|
console.error('Authentication Failed:');
|
||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$('body form.js-auth-form').addEventListener('submit', onClickLogin);
|
$('body form.js-auth-form').addEventListener('submit', onClickLogin);
|
||||||
onChangeProvider('oauth3.org');
|
onChangeProvider('oauth3.org');
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA;
|
||||||
|
try {
|
||||||
|
PromiseA = require('bluebird');
|
||||||
|
} catch(e) {
|
||||||
|
PromiseA = global.Promise;
|
||||||
|
}
|
||||||
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var sfs = require('safe-replace').create({ tmp: 'tmp', bak: 'bak' });
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
|
@ -9,13 +17,206 @@ var jwt = require('jsonwebtoken');
|
||||||
var requestAsync = util.promisify(require('@coolaj86/urequest'));
|
var requestAsync = util.promisify(require('@coolaj86/urequest'));
|
||||||
var readFileAsync = util.promisify(fs.readFile);
|
var readFileAsync = util.promisify(fs.readFile);
|
||||||
var mkdirpAsync = util.promisify(require('mkdirp'));
|
var mkdirpAsync = util.promisify(require('mkdirp'));
|
||||||
|
var TRUSTED_ISSUERS = [ 'oauth3.org' ];
|
||||||
|
var DB = {};
|
||||||
|
DB._load = function () {
|
||||||
|
try {
|
||||||
|
DB._perms = require('./permissions.json');
|
||||||
|
} catch(e) {
|
||||||
|
try {
|
||||||
|
DB._perms = require('./permissions.json.bak');
|
||||||
|
} catch(e) {
|
||||||
|
DB._perms = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DB._byDomain = {};
|
||||||
|
DB._byPort = {};
|
||||||
|
DB._byEmail = {};
|
||||||
|
DB._byPpid = {};
|
||||||
|
DB._byId = {};
|
||||||
|
DB._grants = {};
|
||||||
|
DB._perms.forEach(function (acc) {
|
||||||
|
if (acc.id) {
|
||||||
|
DB._byId[acc.id] = acc;
|
||||||
|
if (!DB._grants[acc.id]) {
|
||||||
|
DB._grants[acc.id] = [];
|
||||||
|
}
|
||||||
|
acc.domains.forEach(function (d) {
|
||||||
|
DB._grants[d.name + '|id|' + acc.id] = true
|
||||||
|
DB._grants[acc.id].push(d);
|
||||||
|
});
|
||||||
|
acc.ports.forEach(function (p) {
|
||||||
|
DB._grants[p.number + '|id|' + acc.id] = true
|
||||||
|
DB._grants[acc.id].push(p);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
acc.nodes.forEach(function (node) {
|
||||||
|
if ('mailto' === node.scheme || 'email' === node.type) {
|
||||||
|
if (!DB._grants[node.email]) {
|
||||||
|
DB._grants[node.email] = [];
|
||||||
|
}
|
||||||
|
acc.domains.forEach(function (d) {
|
||||||
|
DB._grants[d.name + '|' + (node.scheme||node.type) + '|' + node.name] = true
|
||||||
|
DB._grants[node.email].push(d);
|
||||||
|
});
|
||||||
|
acc.ports.forEach(function (d) {
|
||||||
|
DB._grants[d.name + '|' + (node.scheme||node.type) + '|' + node.name] = true
|
||||||
|
DB._grants[node.email].push(p);
|
||||||
|
});
|
||||||
|
DB._byEmail[node.name] = {
|
||||||
|
account: acc
|
||||||
|
, node: node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
acc.ppids.forEach(function (node) {
|
||||||
|
DB._byPpid[node.name] = {
|
||||||
|
account: acc
|
||||||
|
, node: node
|
||||||
|
}
|
||||||
|
});
|
||||||
|
acc.domains.forEach(function (domain) {
|
||||||
|
if (DB._byDomain[domain.name]) {
|
||||||
|
console.warn("duplicate domain '" + domain.name + "'");
|
||||||
|
console.warn("::existing account '" + acc.nodes.map(function (node) { return node.name; }) + "'");
|
||||||
|
console.warn("::new account '" + DB._byDomain[domain.name].account.nodes.map(function (node) { return node.name; }) + "'");
|
||||||
|
}
|
||||||
|
DB._byDomain[domain.name] = {
|
||||||
|
account: acc
|
||||||
|
, domain: domain
|
||||||
|
};
|
||||||
|
});
|
||||||
|
acc.ports.forEach(function (port) {
|
||||||
|
if (DB._byPort[port.number]) {
|
||||||
|
console.warn("duplicate port '" + domain.number + "'");
|
||||||
|
console.warn("::existing account '" + acc.nodes.map(function (node) { return node.name; }) + "'");
|
||||||
|
console.warn("::new account '" + DB._byPort[port.number].account.nodes.map(function (node) { return node.name; }) + "'");
|
||||||
|
}
|
||||||
|
DB._byPort[domain.name] = {
|
||||||
|
account: acc
|
||||||
|
, port: port
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB._load();
|
||||||
|
DB.accounts = {};
|
||||||
|
DB.accounts.get = function (obj) {
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
return DB._byId[obj.name] || (DB._byEmail[obj.name] || {}).acc || null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.accounts.add = function (obj) {
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
if (obj.id) {
|
||||||
|
// TODO more checks
|
||||||
|
DB._perms.push(obj);
|
||||||
|
} else if (obj.email) {
|
||||||
|
obj.email = undefined;
|
||||||
|
DB._perms.push(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.domains = {};
|
||||||
|
DB.domains.available = function (name) {
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
return !DB._byDomain[name];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.domains._add = function (acc, name) {
|
||||||
|
// TODO verifications to change ownership of a domain
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
var err;
|
||||||
|
//var acc = DB._byId[aid];
|
||||||
|
var domain = {
|
||||||
|
name: name
|
||||||
|
, createdAt: new Date().toISOString()
|
||||||
|
, wildcard: true
|
||||||
|
};
|
||||||
|
var pdomain;
|
||||||
|
var parts = name.split('.').map(function (el, i) {
|
||||||
|
return arr.slice(i).join('.');
|
||||||
|
}).reverse();
|
||||||
|
parts.shift();
|
||||||
|
parts.pop();
|
||||||
|
if (parts.some(function (part) {
|
||||||
|
if (DB._byDomain[part]) {
|
||||||
|
pdomain = part;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
err = new Error("'" + name + "' exists as '" + pdomain + "' and therefore requires an admin to review and approve");
|
||||||
|
err.code = "E_REQ_ADMIN";
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if (DB._byDomain[name]) {
|
||||||
|
if (acc !== DB._byDomain[name].account) {
|
||||||
|
throw new Error("domain '" + name + "' exists");
|
||||||
|
}
|
||||||
|
// happily ignore non-change
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DB._byDomain[name] = {
|
||||||
|
account: acc
|
||||||
|
, domain: domain
|
||||||
|
};
|
||||||
|
acc.domains.push(domain);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.ports = {};
|
||||||
|
DB.ports.available = function (number) {
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
return !DB._byPort[number];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB.ports._add = function (acc, number) {
|
||||||
|
return PromiseA.resolve().then(function () {
|
||||||
|
//var acc = DB._byId[aid];
|
||||||
|
var port = {
|
||||||
|
number: number
|
||||||
|
, createdAt: new Date().toISOString()
|
||||||
|
};
|
||||||
|
if (DB._byPort[number]) {
|
||||||
|
// TODO verifications
|
||||||
|
throw new Error("port '" + number + "' exists");
|
||||||
|
}
|
||||||
|
DB._byPort[number] = {
|
||||||
|
account: acc
|
||||||
|
, domain: domain
|
||||||
|
};
|
||||||
|
acc.domains.push(domain);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
DB._save = function () {
|
||||||
|
return sfs.writeAsync('./accounts.json', JSON.stringify(DB._perms));
|
||||||
|
};
|
||||||
|
DB._saveToken = null;
|
||||||
|
DB._savePromises = [];
|
||||||
|
DB._savePromise = PromiseA.resolve();
|
||||||
|
DB.save = function () {
|
||||||
|
cancelTimeout(DB._saveToken);
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
function doSave() {
|
||||||
|
DB._savePromise = DB._savePromise.then(function () {
|
||||||
|
return DB._save().then(function (yep) {
|
||||||
|
DB._savePromises.forEach(function (p) {
|
||||||
|
p.resolve(yep);
|
||||||
|
});
|
||||||
|
DB._savePromises.length = 1;
|
||||||
|
}, function (err) {
|
||||||
|
DB._savePromises.forEach(function (p) {
|
||||||
|
p.reject(err);
|
||||||
|
});
|
||||||
|
DB._savePromises.length = 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return DB._savePromise;
|
||||||
|
}
|
||||||
|
|
||||||
var PromiseA;
|
DB._saveToken = setTimeout(doSave, 2500);
|
||||||
try {
|
DB.savePromises.push({ resolve: resolve, reject: reject });
|
||||||
PromiseA = require('bluebird');
|
});
|
||||||
} catch(e) {
|
};
|
||||||
PromiseA = global.Promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
var _auths = module.exports._auths = {};
|
var _auths = module.exports._auths = {};
|
||||||
var Auths = {};
|
var Auths = {};
|
||||||
|
@ -140,15 +341,22 @@ Accounts.create = function (req) {
|
||||||
Accounts.link = function (req) {
|
Accounts.link = function (req) {
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
Accounts.getBySub = function (req) {
|
|
||||||
|
Accounts.getOrCreate = function (req) {
|
||||||
var id = Accounts._getTokenId(req.auth);
|
var id = Accounts._getTokenId(req.auth);
|
||||||
var subpath = Accounts._subPath(req, id);
|
var idNode = { type: 'ppid', name: id };
|
||||||
return readFileAsync(path.join(subpath, 'index.json'), 'utf8').then(function (text) {
|
|
||||||
return JSON.parse(text);
|
return DB.accounts.get(idNode).then(function (acc) {
|
||||||
}, function (/*err*/) {
|
if (acc) { return _acc; }
|
||||||
return null;
|
acc = { id: id, sub: req.auth.sub, iss: req.auth.iss, domains: [], ports: [], nodes: [ idNode ] };
|
||||||
}).then(function (links) {
|
return DB.accounts.add(acc).then(function () {
|
||||||
return links || { id: id, sub: req.auth.sub, iss: req.auth.iss, accounts: [] };
|
// intentionally not returned to the promise chain
|
||||||
|
DB.save().catch(function (err) {
|
||||||
|
console.error('DB.save() failed:');
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -343,6 +551,7 @@ function oauth3Auth(req, res, next) {
|
||||||
, json: true
|
, json: true
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
var jwk = resp.body;
|
var jwk = resp.body;
|
||||||
|
console.log('Retrieved token\'s JWK: ', resp.body);
|
||||||
if (200 !== resp.statusCode || 'object' !== typeof resp.body) {
|
if (200 !== resp.statusCode || 'object' !== typeof resp.body) {
|
||||||
//headers.authorization
|
//headers.authorization
|
||||||
res.send({
|
res.send({
|
||||||
|
@ -362,6 +571,7 @@ function oauth3Auth(req, res, next) {
|
||||||
try {
|
try {
|
||||||
pubpem = require('jwk-to-pem')(jwk, { private: false });
|
pubpem = require('jwk-to-pem')(jwk, { private: false });
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
console.error("jwk-to-pem", e);
|
||||||
pubpem = null;
|
pubpem = null;
|
||||||
}
|
}
|
||||||
return verifyJwt(token, pubpem, {
|
return verifyJwt(token, pubpem, {
|
||||||
|
@ -382,7 +592,7 @@ function oauth3Auth(req, res, next) {
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, function (err) {
|
}).catch(function (err) {
|
||||||
res.send({
|
res.send({
|
||||||
error: {
|
error: {
|
||||||
code: err.code || "E_GENERIC"
|
code: err.code || "E_GENERIC"
|
||||||
|
@ -391,6 +601,13 @@ function oauth3Auth(req, res, next) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
var OAUTH3 = require('oauth3.js').create({ pathname: process.cwd() });
|
||||||
|
/*
|
||||||
|
// TODO all of the above should be replace with the official lib
|
||||||
|
return OAUTH3.jwk.verifyToken(req.auth.jwt).then(function (token) {
|
||||||
|
}).catch(function (err) {
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
module.exports.pairRequest = function (opts) {
|
module.exports.pairRequest = function (opts) {
|
||||||
console.log("It's auth'n time!");
|
console.log("It's auth'n time!");
|
||||||
|
@ -433,6 +650,50 @@ module.exports.pairRequest = function (opts) {
|
||||||
return authnData;
|
return authnData;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
DB.getDomainAndPort = function (state) {
|
||||||
|
var domainCount = 0;
|
||||||
|
var portCount = 0;
|
||||||
|
|
||||||
|
function chooseDomain() {
|
||||||
|
var err;
|
||||||
|
if (domainCount >= 3) {
|
||||||
|
err = new Error("there too few unallocated domains left");
|
||||||
|
err.code = "E_DOMAINS_EXHAUSTED";
|
||||||
|
return PromiseA.reject(err);
|
||||||
|
}
|
||||||
|
domainCount += 1;
|
||||||
|
var hri = require('human-readable-ids').hri;
|
||||||
|
var i = Math.floor(Math.random() * state.config.sharedDomains.length);
|
||||||
|
var hrname = hri.random() + '.' + state.config.sharedDomains[i];
|
||||||
|
return DB.domains.available(hrname).then(function (available) {
|
||||||
|
if (!available) { return chooseDomain(); }
|
||||||
|
return hrname;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function choosePort() {
|
||||||
|
var err;
|
||||||
|
if (portCount >= 3) {
|
||||||
|
err = new Error("there too few unallocated ports left");
|
||||||
|
err.code = "E_PORTS_EXHAUSTED";
|
||||||
|
return PromiseA.reject(err);
|
||||||
|
}
|
||||||
|
portCount += 1;
|
||||||
|
var portnumber = (1024 + 1) + Math.round(Math.random() * 65535);
|
||||||
|
return DB.ports.available(portnumber).then(function (available) {
|
||||||
|
if (!available) { return portDomain(); }
|
||||||
|
return portnumber;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.all([
|
||||||
|
chooseDomain()
|
||||||
|
, choosePort()
|
||||||
|
]).then(function (two) {
|
||||||
|
return {
|
||||||
|
domain: two[0]
|
||||||
|
, port: two[1]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
module.exports.pairPin = function (opts) {
|
module.exports.pairPin = function (opts) {
|
||||||
var state = opts.state;
|
var state = opts.state;
|
||||||
return state.Promise.resolve().then(function () {
|
return state.Promise.resolve().then(function () {
|
||||||
|
@ -455,36 +716,63 @@ module.exports.pairPin = function (opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[pairPin] generating offer');
|
console.log('[pairPin] generating offer');
|
||||||
var hri = require('human-readable-ids').hri;
|
return DB.getDomainAndPort(state);
|
||||||
var i = Math.floor(Math.random() * state.config.sharedDomains.length);
|
}).then(function (grantable) {
|
||||||
var hrname = hri.random() + '.' + state.config.sharedDomains[i];
|
var emailNode = { scheme: 'mailto', type: 'email', name: auth.subject };
|
||||||
// TODO check used / unused names and ports
|
|
||||||
var authzData = {
|
return DB.accounts.get(emailNode).then(function (_acc) {
|
||||||
id: auth.id
|
var acc = _acc;
|
||||||
, domains: [ hrname ]
|
if (!acc) {
|
||||||
, ports: [ (1024 + 1) + Math.round(Math.random() * 65535) ]
|
acc = { email: true, domains: [], ports: [], nodes: [ emailNode ] };
|
||||||
, aud: state.config.webminDomain
|
}
|
||||||
, iat: Math.round(Date.now() / 1000)
|
return PromiseA.all([
|
||||||
, hostname: auth.hostname
|
DB.domains._add(acc, opts.domain)
|
||||||
};
|
, DB.ports._add(acc, opts.port)
|
||||||
|
]).then(function () {
|
||||||
|
var authzData = {
|
||||||
|
id: auth.id
|
||||||
|
, domains: [ grantable.domain ]
|
||||||
|
, ports: [ grantable.port ]
|
||||||
|
, aud: state.config.webminDomain
|
||||||
|
, iat: Math.round(Date.now() / 1000)
|
||||||
|
// of the client's computer
|
||||||
|
, hostname: auth.hostname
|
||||||
|
};
|
||||||
|
auth.authz = jwt.sign(authzData, state.secret);
|
||||||
|
auth.authzData = authzData;
|
||||||
|
authzData.jwt = auth.authz;
|
||||||
|
auth._offered = authzData;
|
||||||
|
if (auth.resolve) {
|
||||||
|
console.log('[pairPin] resolving');
|
||||||
|
auth.resolve(auth);
|
||||||
|
} else {
|
||||||
|
console.log('[pairPin] not resolvable');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_acc) {
|
||||||
|
return DB.accounts.add(acc).then(function () {
|
||||||
|
// intentionally not returned to the promise chain
|
||||||
|
DB.save().catch(function (err) {
|
||||||
|
console.error('DB.save() failed:');
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
return authzData;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return authzData;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
var pathname = path.join(__dirname, 'emails', auth.subject + '.' + hrname + '.data');
|
var pathname = path.join(__dirname, 'emails', auth.subject + '.' + hrname + '.data');
|
||||||
auth.authz = jwt.sign(authzData, state.secret);
|
|
||||||
auth.authzData = authzData;
|
|
||||||
authzData.jwt = auth.authz;
|
|
||||||
auth._offered = authzData;
|
|
||||||
if (auth.resolve) {
|
|
||||||
console.log('[pairPin] resolving');
|
|
||||||
auth.resolve(auth);
|
|
||||||
} else {
|
|
||||||
console.log('[pairPin] not resolvable');
|
|
||||||
}
|
|
||||||
fs.writeFile(pathname, JSON.stringify(authzData), function (err) {
|
fs.writeFile(pathname, JSON.stringify(authzData), function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('[ERROR] in writing token details');
|
console.error('[ERROR] in writing token details');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return authzData;
|
*/
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -620,11 +908,81 @@ app.use('/api', CORS({
|
||||||
app.use('/api', bodyParser.json());
|
app.use('/api', bodyParser.json());
|
||||||
|
|
||||||
app.use('/api/telebit.cloud/account', oauth3Auth);
|
app.use('/api/telebit.cloud/account', oauth3Auth);
|
||||||
|
Accounts._associateEmails = function (req) {
|
||||||
|
if (-1 === (req._state.config.trustedIssuers||TRUSTED_ISSUERS).indexOf(req.auth.data.iss)) {
|
||||||
|
// again, make sure that untrusted issuers do not get
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// oauth3.org, issuer@oauth3.org, profile
|
||||||
|
return OAUTH3.request({
|
||||||
|
url: "https://api." + req.auth.data.iss + "/api/issuer@oauth3.org/acl/profile"
|
||||||
|
, session: { accessToken: req.auth.jwt }
|
||||||
|
}).then(function (resp) {
|
||||||
|
var email;
|
||||||
|
var err;
|
||||||
|
(resp.data.nodes||[]).some(function (node) {
|
||||||
|
// TODO use verified email addresses
|
||||||
|
return true
|
||||||
|
});
|
||||||
|
// back-compat for current way email is stored
|
||||||
|
if (!email && /@/.test(resp.data.username)) {
|
||||||
|
email = resp.data.username;
|
||||||
|
}
|
||||||
|
if (!email) {
|
||||||
|
err = new Error ("could not find a verified email address in profile settings");
|
||||||
|
err.code = "E_NO_EMAIL"
|
||||||
|
return PromiseA.reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ { scheme: 'mailto', type: 'email', name: email } ];
|
||||||
|
});
|
||||||
|
};
|
||||||
app.get('/api/telebit.cloud/account', function (req, res) {
|
app.get('/api/telebit.cloud/account', function (req, res) {
|
||||||
Accounts.getBySub(req).then(function (subData) {
|
return Accounts.getOrCreate(req).then(function (acc) {
|
||||||
res.send(subData);
|
var hasEmail = subData.nodes.some(function (node) {
|
||||||
}, function (err) {
|
return 'email' === node.type;
|
||||||
res.send({
|
});
|
||||||
|
function getAllGrants() {
|
||||||
|
return PromiseA.all(acc.nodes.map(function (node) {
|
||||||
|
return DB.accounts.get(node);
|
||||||
|
})).then(function (grants) {
|
||||||
|
var domainsMap = {};
|
||||||
|
var portsMap = {};
|
||||||
|
var result = JSON.parse(JSON.stringify(acc));
|
||||||
|
result.domains.length = 0;
|
||||||
|
result.ports.length = 0;
|
||||||
|
grants.forEach(function (account) {
|
||||||
|
account.domains.forEach(function (d) {
|
||||||
|
domainsMap[d.name] = d;
|
||||||
|
});
|
||||||
|
account.ports.forEach(function (p) {
|
||||||
|
portsMap[p.number] = p;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
result.domains = Object.keys(domainsMap).map(function (k) {
|
||||||
|
return domainsMap[k];
|
||||||
|
});
|
||||||
|
result.ports = Object.keys(portsMap).map(function (k) {
|
||||||
|
return portsMap[k];
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!hasEmail) {
|
||||||
|
return Accounts._associateEmails(req).then(function (nodes) {
|
||||||
|
nodes.forEach(function (node) {
|
||||||
|
acc.nodes.push(node);
|
||||||
|
});
|
||||||
|
return getAllGrants();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return getAllGrants();
|
||||||
|
}
|
||||||
|
}).then(function (result) {
|
||||||
|
res.send(result);
|
||||||
|
}).catch(function (err) {
|
||||||
|
return res.send({
|
||||||
error: {
|
error: {
|
||||||
code: err.code || "E_GENERIC"
|
code: err.code || "E_GENERIC"
|
||||||
, message: err.toString()
|
, message: err.toString()
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "telebit.commercial",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Commercial node.js APIs for Telebit",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
|
"dependencies": {
|
||||||
|
"jwk-to-pem": "^2.0.0",
|
||||||
|
"oauth3.js": "^1.2.5"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue