WIP implementing semi-proper accounts
This commit is contained in:
		
							parent
							
								
									1099a75509
								
							
						
					
					
						commit
						64281e4c93
					
				| @ -3,8 +3,9 @@ | ||||
|     <title>Telebit Account</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <h1>Login</h1> | ||||
|     <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> | ||||
|     </form> | ||||
| 
 | ||||
| @ -19,37 +20,44 @@ | ||||
|         }); | ||||
|         var $ = function () { return document.querySelector.apply(document, arguments); } | ||||
| 
 | ||||
| 				function onChangeProvider(providerUri) { | ||||
| 					// example https://oauth3.org | ||||
| 					return oauth3.setIdentityProvider(providerUri); | ||||
| 				} | ||||
|         function onChangeProvider(providerUri) { | ||||
|           // example https://oauth3.org | ||||
|           return oauth3.setIdentityProvider(providerUri); | ||||
|         } | ||||
| 
 | ||||
| 				// This opens up the login window for the specified provider | ||||
| 				// | ||||
| 				function onClickLogin(ev) { | ||||
|         // This opens up the login window for the specified provider | ||||
|         // | ||||
|         function onClickLogin(ev) { | ||||
|           ev.preventDefault(); | ||||
|           ev.stopPropagation(); | ||||
| 
 | ||||
|           var email = $('.js-auth-subject').value; | ||||
| 
 | ||||
|           // TODO check subject for provider viability | ||||
|           return oauth3.authenticate({ | ||||
|             subject: $('.js-auth-subject').value | ||||
|             subject: email | ||||
|           , scope: 'email@oauth3.org' | ||||
|           }).then(function (session) { | ||||
| 
 | ||||
| 						console.info('Authentication was Successful:'); | ||||
| 						console.log(session); | ||||
|             console.info('Authentication was Successful:'); | ||||
|             console.log(session); | ||||
| 
 | ||||
| 						// 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) | ||||
| 						// 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); | ||||
|             // 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) | ||||
|             // 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); | ||||
| 
 | ||||
| 						return oauth3.request({ | ||||
| 							url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid.json' | ||||
|             function listStuff() { | ||||
|               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(/:kid/g, session.token.iss) | ||||
| 						, session: session | ||||
| 						}).then(function (resp) { | ||||
|             , session: session | ||||
|             }).then(function (resp) { | ||||
|               console.info("Public Key:"); | ||||
|               console.log(resp.data); | ||||
| 
 | ||||
| @ -62,25 +70,44 @@ | ||||
|                 console.log(resp.data); | ||||
| 
 | ||||
|                 return oauth3.request({ | ||||
|                   url: 'https://api.telebit.cloud/api/telebit.cloud/account' | ||||
|                   url: 'https://api.' + location.hostname + '/api/telebit.cloud/account' | ||||
|                 , session: session | ||||
|                 }).then(function (resp) { | ||||
| 
 | ||||
|                   console.info("Telebit Account:"); | ||||
|                   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) { | ||||
| 						console.error('Authentication Failed:'); | ||||
| 						console.log(err); | ||||
| 					}); | ||||
| 				} | ||||
|           }, function (err) { | ||||
|             console.error('Authentication Failed:'); | ||||
|             console.log(err); | ||||
|           }); | ||||
|         } | ||||
| 
 | ||||
|         $('body form.js-auth-form').addEventListener('submit', onClickLogin); | ||||
|         onChangeProvider('oauth3.org'); | ||||
|  | ||||
| @ -1,6 +1,14 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var PromiseA; | ||||
| try { | ||||
|   PromiseA = require('bluebird'); | ||||
| } catch(e) { | ||||
|   PromiseA = global.Promise; | ||||
| } | ||||
| 
 | ||||
| var fs = require('fs'); | ||||
| var sfs = require('safe-replace').create({ tmp: 'tmp', bak: 'bak' }); | ||||
| var path = require('path'); | ||||
| var util = require('util'); | ||||
| var crypto = require('crypto'); | ||||
| @ -9,13 +17,206 @@ var jwt = require('jsonwebtoken'); | ||||
| var requestAsync = util.promisify(require('@coolaj86/urequest')); | ||||
| var readFileAsync = util.promisify(fs.readFile); | ||||
| 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; | ||||
| try { | ||||
|   PromiseA = require('bluebird'); | ||||
| } catch(e) { | ||||
|   PromiseA = global.Promise; | ||||
| } | ||||
|     DB._saveToken = setTimeout(doSave, 2500); | ||||
|     DB.savePromises.push({ resolve: resolve, reject: reject }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| var _auths = module.exports._auths = {}; | ||||
| var Auths = {}; | ||||
| @ -140,15 +341,22 @@ Accounts.create = function (req) { | ||||
| Accounts.link = function (req) { | ||||
| }; | ||||
| */ | ||||
| Accounts.getBySub = function (req) { | ||||
| 
 | ||||
| Accounts.getOrCreate = function (req) { | ||||
|   var id = Accounts._getTokenId(req.auth); | ||||
|   var subpath = Accounts._subPath(req, id); | ||||
|   return readFileAsync(path.join(subpath, 'index.json'), 'utf8').then(function (text) { | ||||
|     return JSON.parse(text); | ||||
|   }, function (/*err*/) { | ||||
|     return null; | ||||
|   }).then(function (links) { | ||||
|     return links || { id: id, sub: req.auth.sub, iss: req.auth.iss, accounts: [] }; | ||||
|   var idNode = { type: 'ppid', name: id }; | ||||
| 
 | ||||
|   return DB.accounts.get(idNode).then(function (acc) { | ||||
|     if (acc) { return _acc; } | ||||
|     acc = { id: id, sub: req.auth.sub, iss: req.auth.iss, domains: [], ports: [], nodes: [ idNode ] }; | ||||
|     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 acc; | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| @ -343,6 +551,7 @@ function oauth3Auth(req, res, next) { | ||||
|     , json: true | ||||
|     }).then(function (resp) { | ||||
|       var jwk = resp.body; | ||||
|       console.log('Retrieved token\'s JWK: ', resp.body); | ||||
|       if (200 !== resp.statusCode || 'object' !== typeof resp.body) { | ||||
|         //headers.authorization
 | ||||
|         res.send({ | ||||
| @ -362,6 +571,7 @@ function oauth3Auth(req, res, next) { | ||||
|       try { | ||||
|         pubpem = require('jwk-to-pem')(jwk, { private: false }); | ||||
|       } catch(e) { | ||||
|         console.error("jwk-to-pem", e); | ||||
|         pubpem = null; | ||||
|       } | ||||
| 			return verifyJwt(token, pubpem, { | ||||
| @ -382,7 +592,7 @@ function oauth3Auth(req, res, next) { | ||||
|         next(); | ||||
| 			}); | ||||
|     }); | ||||
|   }, function (err) { | ||||
|   }).catch(function (err) { | ||||
|     res.send({ | ||||
|       error: { | ||||
|         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) { | ||||
|   console.log("It's auth'n time!"); | ||||
| @ -433,6 +650,50 @@ module.exports.pairRequest = function (opts) { | ||||
|     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) { | ||||
|   var state = opts.state; | ||||
|   return state.Promise.resolve().then(function () { | ||||
| @ -455,36 +716,63 @@ module.exports.pairPin = function (opts) { | ||||
|     } | ||||
| 
 | ||||
|     console.log('[pairPin] generating offer'); | ||||
|     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]; | ||||
|     // TODO check used / unused names and ports
 | ||||
|     var authzData = { | ||||
|       id: auth.id | ||||
|     , domains: [ hrname ] | ||||
|     , ports: [ (1024 + 1) + Math.round(Math.random() * 65535) ] | ||||
|     , aud: state.config.webminDomain | ||||
|     , iat: Math.round(Date.now() / 1000) | ||||
|     , hostname: auth.hostname | ||||
|     }; | ||||
|     return DB.getDomainAndPort(state); | ||||
|   }).then(function (grantable) { | ||||
|     var emailNode = { scheme: 'mailto', type: 'email', name: auth.subject }; | ||||
| 
 | ||||
|     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 = { | ||||
|           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'); | ||||
|     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) { | ||||
|       if (err) { | ||||
|         console.error('[ERROR] in writing token details'); | ||||
|         console.error(err); | ||||
|       } | ||||
|     }); | ||||
|     return authzData; | ||||
|     */ | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| @ -620,11 +908,81 @@ app.use('/api', CORS({ | ||||
| app.use('/api', bodyParser.json()); | ||||
| 
 | ||||
| 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) { | ||||
|   Accounts.getBySub(req).then(function (subData) { | ||||
|     res.send(subData); | ||||
|   }, function (err) { | ||||
|     res.send({ | ||||
|   return Accounts.getOrCreate(req).then(function (acc) { | ||||
|     var hasEmail = subData.nodes.some(function (node) { | ||||
|       return 'email' === node.type; | ||||
|     }); | ||||
|     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: { | ||||
|         code: err.code || "E_GENERIC" | ||||
|       , 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