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() | ||||||
|  | |||||||
							
								
								
									
										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