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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -30,9 +31,12 @@
 | 
				
			|||||||
          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:');
 | 
				
			||||||
@ -44,6 +48,10 @@
 | 
				
			|||||||
            //
 | 
					            //
 | 
				
			||||||
            console.info('Secure PPID (aka subject):', session.token.sub);
 | 
					            console.info('Secure PPID (aka subject):', session.token.sub);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            function listStuff() {
 | 
				
			||||||
 | 
					              window.alert("TODO: show authorized devices, domains, and connectivity information");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return oauth3.request({
 | 
					            return oauth3.request({
 | 
				
			||||||
              url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid.json'
 | 
					              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)
 | 
				
			||||||
@ -62,13 +70,32 @@
 | 
				
			|||||||
                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.");
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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 PromiseA;
 | 
					var DB = {};
 | 
				
			||||||
 | 
					DB._load = function () {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
  PromiseA = require('bluebird');
 | 
					    DB._perms = require('./permissions.json');
 | 
				
			||||||
  } catch(e) {
 | 
					  } catch(e) {
 | 
				
			||||||
  PromiseA = global.Promise;
 | 
					    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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DB._saveToken = setTimeout(doSave, 2500);
 | 
				
			||||||
 | 
					    DB.savePromises.push({ resolve: resolve, reject: reject });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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,19 +716,28 @@ 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
 | 
					
 | 
				
			||||||
 | 
					    return DB.accounts.get(emailNode).then(function (_acc) {
 | 
				
			||||||
 | 
					      var acc = _acc;
 | 
				
			||||||
 | 
					      if (!acc) {
 | 
				
			||||||
 | 
					        acc = { email: true, domains: [], ports: [], nodes: [ emailNode ] };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return PromiseA.all([
 | 
				
			||||||
 | 
					        DB.domains._add(acc, opts.domain)
 | 
				
			||||||
 | 
					      , DB.ports._add(acc, opts.port)
 | 
				
			||||||
 | 
					      ]).then(function () {
 | 
				
			||||||
        var authzData = {
 | 
					        var authzData = {
 | 
				
			||||||
          id: auth.id
 | 
					          id: auth.id
 | 
				
			||||||
    , domains: [ hrname ]
 | 
					        , domains: [ grantable.domain ]
 | 
				
			||||||
    , ports: [ (1024 + 1) + Math.round(Math.random() * 65535) ]
 | 
					        , ports: [ grantable.port ]
 | 
				
			||||||
        , aud: state.config.webminDomain
 | 
					        , aud: state.config.webminDomain
 | 
				
			||||||
        , iat: Math.round(Date.now() / 1000)
 | 
					        , iat: Math.round(Date.now() / 1000)
 | 
				
			||||||
 | 
					          // of the client's computer
 | 
				
			||||||
        , hostname: auth.hostname
 | 
					        , hostname: auth.hostname
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    var pathname = path.join(__dirname, 'emails', auth.subject + '.' + hrname + '.data');
 | 
					 | 
				
			||||||
        auth.authz = jwt.sign(authzData, state.secret);
 | 
					        auth.authz = jwt.sign(authzData, state.secret);
 | 
				
			||||||
        auth.authzData = authzData;
 | 
					        auth.authzData = authzData;
 | 
				
			||||||
        authzData.jwt = auth.authz;
 | 
					        authzData.jwt = auth.authz;
 | 
				
			||||||
@ -478,13 +748,31 @@ module.exports.pairPin = function (opts) {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          console.log('[pairPin] not resolvable');
 | 
					          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');
 | 
				
			||||||
    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()
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								lib/extensions/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								lib/extensions/package.json
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user