dns check complete
This commit is contained in:
		
							parent
							
								
									2819117f10
								
							
						
					
					
						commit
						2564b750e6
					
				| @ -16,16 +16,25 @@ | ||||
| 
 | ||||
|       <div v-if="hasAccount"> | ||||
|         <h1>Account</h1> | ||||
|         <form v-on:submit="challengeDns()"> | ||||
|         <form v-on:submit.prevent="challengeDns()"> | ||||
|           Add a custom domain: | ||||
|           <input v-model="newDomain" placeholder="example.com" type="text" required/> | ||||
|           <button type="submit">Next</button> | ||||
|         </form> | ||||
|         <form v-on:submit="challengeEmail()"> | ||||
|         <form v-on:submit.prevent="challengeEmail()"> | ||||
|           Authorize another email: | ||||
|           <input v-model="newEmail" placeholder="jon@example.com" type="email" required/> | ||||
|           <button type="submit">Next</button> | ||||
|         </form> | ||||
|         <h3>Claims</h3> | ||||
|         <ol> | ||||
|           <li v-for="claim in claims"> | ||||
|             <span>{{ claim.value }}</span> | ||||
|             <span v-if="'dns' === claim.type">TXT _claim-challenge.{{ claim.value }}: {{ claim.challenge }}</span> | ||||
|             <button v-on:click.prevent="checkDns(claim)">Check</button> | ||||
|           </li> | ||||
|         </ol> | ||||
|         <h3>Domains</h3> | ||||
|         <ol> | ||||
|           <li v-for="domain in domains"> | ||||
|             {{ domain }} | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| /*global Vue*/ | ||||
| (function () { | ||||
|   'use strict'; | ||||
|   var OAUTH3 = window.OAUTH3; | ||||
| @ -5,33 +6,7 @@ | ||||
|     host: window.location.host | ||||
|   , pathname: window.location.pathname.replace(/\/[^\/]*$/, '/') | ||||
|   }); | ||||
|   var $ = function () { return document.querySelector.apply(document, arguments); } | ||||
|   var vueData = { | ||||
|     domains: [] | ||||
|   , newDomain: null | ||||
|   , newEmail: null | ||||
|   , hasAccount: false | ||||
|   , token: null | ||||
|   }; | ||||
|   var app = new Vue({ | ||||
|     el: '.v-app' | ||||
|   , data: vueData | ||||
|   , methods: { | ||||
|       challengeDns: function () { | ||||
|         console.log("A new (DNS) challenger!", vueData); | ||||
|       } | ||||
|     , challengeEmail: function () { | ||||
|         console.log("A new (Email) challenger!", vueData); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   function listStuff(data) { | ||||
|     //window.alert("TODO: show authorized devices, domains, and connectivity information");
 | ||||
|     vueData.hasAccount = true; | ||||
|     vueData.domains = data.domains; | ||||
|   } | ||||
| 
 | ||||
|   var $ = function () { return document.querySelector.apply(document, arguments); }; | ||||
|   var sessionStr = localStorage.getItem('session'); | ||||
|   var session; | ||||
|   if (sessionStr) { | ||||
| @ -42,6 +17,49 @@ | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   var vueData = { | ||||
|     claims: [] | ||||
|   , domains: [] | ||||
|   , newDomain: null | ||||
|   , newDomainWildcard: false | ||||
|   , newEmail: null | ||||
|   , hasAccount: false | ||||
|   , token: null | ||||
|   }; | ||||
|   var app = new Vue({ | ||||
|     el: '.v-app' | ||||
|   , data: vueData | ||||
|   , methods: { | ||||
|       challengeDns: function () { | ||||
|         return oauth3.request({ | ||||
|           url: 'https://api.' + location.hostname + '/api/telebit.cloud/account/authorizations/new' | ||||
|         , method: 'POST' | ||||
|         , session: session | ||||
|         , data: { type: 'dns', value: vueData.newDomain, wildcard: vueData.newDomainWildcard } | ||||
|         }); | ||||
|       } | ||||
|     , checkDns: function (claim) { | ||||
|         return oauth3.request({ | ||||
|           url: 'https://api.' + location.hostname + '/api/telebit.cloud/account/authorizations/new/:value/:challenge' | ||||
|             .replace(/:value/g, claim.value) | ||||
|             .replace(/:challenge/g, claim.challenge) | ||||
|         , method: 'POST' | ||||
|         , session: session | ||||
|         }); | ||||
|       } | ||||
|     , challengeEmail: function () { | ||||
|         console.log("A new (Email) challenger!", vueData); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|   app = null; | ||||
| 
 | ||||
|   function listStuff(data) { | ||||
|     //window.alert("TODO: show authorized devices, domains, and connectivity information");
 | ||||
|     vueData.hasAccount = true; | ||||
|     vueData.domains = data.domains; | ||||
|     vueData.claims = data.claims; | ||||
|   } | ||||
|   function loadAccount(session) { | ||||
|     return oauth3.request({ | ||||
|       url: 'https://api.' + location.hostname + '/api/telebit.cloud/account' | ||||
| @ -64,7 +82,7 @@ | ||||
|         , method: 'POST' | ||||
|         , session: session | ||||
|         , body: { | ||||
|             email: email | ||||
|             email: vueData.newEmail | ||||
|           } | ||||
|         }).then(function (resp) { | ||||
|           listStuff(resp); | ||||
| @ -124,7 +142,7 @@ | ||||
|           console.log(resp.data); | ||||
| 
 | ||||
|           localStorage.setItem('session', JSON.stringify(session)); | ||||
|           loadAccount(session) | ||||
|           loadAccount(session); | ||||
|         }); | ||||
| 
 | ||||
|       }); | ||||
| @ -138,7 +156,7 @@ | ||||
|   $('body form.js-auth-form').addEventListener('submit', onClickLogin); | ||||
|   onChangeProvider('oauth3.org'); | ||||
|   if (session) { | ||||
|     vueData.token = session.access_token | ||||
|     vueData.token = session.access_token; | ||||
|     loadAccount(session); | ||||
|   } | ||||
| }()); | ||||
|  | ||||
| @ -29,7 +29,12 @@ DB._load = function () { | ||||
|   DB._byId = {}; | ||||
|   DB._grants = {}; | ||||
|   DB._grantsMap = {}; | ||||
|   DB._authz = {}; | ||||
|   DB._perms.forEach(function (acc) { | ||||
|     if ('authz' === acc.type) { | ||||
|       DB._authz[acc.id] = acc; | ||||
|       return; | ||||
|     } | ||||
|     if (acc.id) { | ||||
|       // if account has an id
 | ||||
|       DB._byId[acc.id] = acc; | ||||
| @ -129,6 +134,80 @@ DB.accounts.add = function (obj) { | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| DB.authorizations = {}; | ||||
| DB.authorizations.create = function (acc, claim) { | ||||
|   if (!acc.id || !claim.type || !claim.value) { throw new Error("requires account id"); } | ||||
|   var crypto = require('crypto'); | ||||
|   var authz = DB._authz[acc.id]; | ||||
|   if (!authz) { | ||||
|     authz = { | ||||
|       id: acc.id | ||||
|     , type: 'authz' | ||||
|     , claims: [] | ||||
|     }; | ||||
|     DB._authz[acc.id] = authz; | ||||
|     DB._perms.push(authz); | ||||
|   } | ||||
|   // TODO check for unique type:value pairing in claims
 | ||||
|   claim.challenge = crypto.randomBytes(16).toString('hex'); | ||||
|   claim.createdAt = Date.now(); | ||||
|   claim.verifiedAt = 0; | ||||
|   authz.claims.push(claim); | ||||
|   DB.save(); | ||||
|   return JSON.parse(JSON.stringify(claim)); | ||||
| }; | ||||
| DB.authorizations.check = function (acc, claim) { | ||||
|   var authz = DB._authz[acc.id]; | ||||
|   var vclaim = null; | ||||
|   if (!authz) { | ||||
|     return vclaim; | ||||
|   } | ||||
| 
 | ||||
|   authz.claims.some(function (c) { | ||||
|     console.log('authz.check', c); | ||||
|     if (claim.challenge) { | ||||
|       if (c.challenge === claim.challenge) { | ||||
|         vclaim = JSON.parse(JSON.stringify(c)); | ||||
|         return true; | ||||
|       } | ||||
|     } else if (claim.value === c.value) { | ||||
|       vclaim = JSON.parse(JSON.stringify(c)); | ||||
|       return true; | ||||
|     } | ||||
|   }); | ||||
|   return vclaim; | ||||
| }; | ||||
| DB.authorizations.checkAll = function (acc) { | ||||
|   var authz = DB._authz[acc.id]; | ||||
|   if (!authz) { | ||||
|     return []; | ||||
|   } | ||||
| 
 | ||||
|   return authz.claims.map(function (claim) { | ||||
|     return JSON.parse(JSON.stringify(claim)); | ||||
|   }); | ||||
| }; | ||||
| DB.authorizations.verify = function (acc, claim) { | ||||
|   var scmp = require('scmp'); | ||||
|   var authz = DB._authz[acc.id]; | ||||
|   var vclaim; | ||||
|   if (!authz) { return false; } | ||||
| 
 | ||||
|   authz.claims.some(function (c) { | ||||
|     if (scmp(c.challenge, claim.challenge)) { | ||||
|       vclaim = c; | ||||
|       c.verifiedAt = Date.now(); | ||||
|       return true; | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   if (vclaim) { | ||||
|     DB.save(); | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   return false; | ||||
| }; | ||||
| DB.domains = {}; | ||||
| DB.domains.available = function (name) { | ||||
|   return PromiseA.resolve().then(function () { | ||||
| @ -154,7 +233,7 @@ DB.domains._add = function (acc, opts) { | ||||
|     parts.shift(); | ||||
|     parts.pop(); | ||||
|     if (parts.some(function (part) { | ||||
|       if (DB._byDomain[part]) { | ||||
|       if (DB._byDomain[part] && DB._byDomain[part].wildcard) { | ||||
|         pdomain = part; | ||||
|         return true; | ||||
|       } | ||||
| @ -175,6 +254,7 @@ DB.domains._add = function (acc, opts) { | ||||
|     , domain: domain | ||||
|     }; | ||||
|     acc.domains.push(domain); | ||||
|     DB.save(); | ||||
|   }); | ||||
| }; | ||||
| DB.ports = {}; | ||||
|  | ||||
| @ -19,6 +19,15 @@ var requestAsync = util.promisify(require('@coolaj86/urequest')); | ||||
| var mkdirpAsync = util.promisify(require('mkdirp')); | ||||
| var TRUSTED_ISSUERS = [ 'oauth3.org' ]; | ||||
| var DB = require('./db.js'); | ||||
| var Claims = {}; | ||||
| Claims.publicize = function publicizeClaim(claim) { | ||||
|   if (!claim) { | ||||
|     return null; | ||||
|   } | ||||
|   var result = { type: claim.type, value: claim.value, verifiedAt: claim.verifiedAt, createdAt: claim.createdAt }; | ||||
|   if ('dns' === claim.type) { result.challenge = claim.challenge; } | ||||
|   return result; | ||||
| }; | ||||
| 
 | ||||
| var _auths = module.exports._auths = {}; | ||||
| var Auths = {}; | ||||
| @ -763,6 +772,7 @@ app.get('/api/telebit.cloud/account', function (req, res) { | ||||
|         //console.log(grants);
 | ||||
|         var domainsMap = {}; | ||||
|         var portsMap = {}; | ||||
|         var claimsMap = {}; | ||||
|         var result = JSON.parse(JSON.stringify(acc)); | ||||
|         result.domains.length = 0; | ||||
|         result.ports.length = 0; | ||||
| @ -775,6 +785,11 @@ app.get('/api/telebit.cloud/account', function (req, res) { | ||||
|           account.ports.forEach(function (p) { | ||||
|             portsMap[p.number] = p; | ||||
|           }); | ||||
|           DB.authorizations.checkAll({ id: account.id }).filter(function (claim) { | ||||
|             return !claim.verifiedAt; | ||||
|           }).forEach(function (claim) { | ||||
|             claimsMap[claim.challenge] = claim; | ||||
|           }); | ||||
|         }); | ||||
|         result.domains = Object.keys(domainsMap).map(function (k) { | ||||
|           return domainsMap[k]; | ||||
| @ -782,6 +797,9 @@ app.get('/api/telebit.cloud/account', function (req, res) { | ||||
|         result.ports = Object.keys(portsMap).map(function (k) { | ||||
|           return portsMap[k]; | ||||
|         }); | ||||
|         result.claims = Object.keys(claimsMap).map(function (k) { | ||||
|           return Claims.publicize(claimsMap[k]); | ||||
|         }); | ||||
|         return result; | ||||
|       }); | ||||
|     } | ||||
| @ -821,24 +839,121 @@ app.post('/api/telebit.cloud/account', function (req, res) { | ||||
| // Challenge Nodes / Email, Domains / DNS
 | ||||
| app.post('/api/telebit.cloud/account/authorizations/new', function (req, res) { | ||||
|   // Send email via SMTP, confirm client's chosen pin
 | ||||
|   res.statusCode = 500; | ||||
|   res.send({ error: { code: "E_NO_IMPL", message: "not implemented" } }); | ||||
|   var accId = Accounts._getTokenId(req.auth); | ||||
|   var typ = req.body.type; | ||||
|   var val = req.body.value; | ||||
|   var wild = req.body.wildcard; | ||||
|   var claim; | ||||
| 
 | ||||
|   if ('dns' === typ && /^[a-z0-9\-\.]+.[a-z]+$/i.test(val)) { | ||||
|     claim = DB.authorizations.create({ id: accId }, { type: typ, value: val, wildcard: wild }); | ||||
|     // MUST RETURN PUBLIC VALUES ONLY!
 | ||||
|     // (challenge is public with dns because the verification is internal)
 | ||||
|     res.send({ success: true, claim: claim }); | ||||
|   } else if ('email' === typ) { | ||||
|     //claim = DB.authorizations.create({ type: dns, claim: claim });
 | ||||
|     // MUST RETURN PUBLIC VALUES ONLY!
 | ||||
|     // (challenge is private with email because the verification is external)
 | ||||
|     //claim.challenge = undefined;;
 | ||||
|     // TODO send email
 | ||||
|     res.statusCode = 501; | ||||
|     res.send({ error: { code: "E_NO_IMPL", message: "authz '" + typ + "' understood but not implemented" } }); | ||||
|   } else { | ||||
|     res.statusCode = 501; | ||||
|     res.send({ error: { code: "E_NO_IMPL", message: "unknown authz type '" + typ + "'" } }); | ||||
|   } | ||||
| }); | ||||
| app.get('/api/telebit.cloud/account/authorizations/status/:id', function (req, res) { | ||||
| app.get('/api/telebit.cloud/account/authorizations/status/:value?', function (req, res) { | ||||
|   // For client to check on status
 | ||||
|   res.statusCode = 500; | ||||
|   res.send({ error: { code: "E_NO_IMPL", message: "not implemented" } }); | ||||
|   var accId = Accounts._getTokenId(req.auth); | ||||
|   var val = req.params.value; | ||||
|   var result; | ||||
| 
 | ||||
|   if (val) { | ||||
|     result = Claims.publicize(DB.authorizations.check({ id: accId }, { value: val })); | ||||
|     // MUST RETURN PUBLIC VALUES ONLY!
 | ||||
|     res.send({ success: true, claim: result }); | ||||
|   } else { | ||||
|     result = DB.authorizations.checkAll({ id: accId }).map(Claims.publicize); | ||||
|     // MUST RETURN PUBLIC VALUES ONLY!
 | ||||
|     res.send({ success: true, claims: result }); | ||||
|   } | ||||
| }); | ||||
| app.get('/api/telebit.cloud/account/authorizations/meta/:secret', function (req, res) { | ||||
|   // For agent to retrieve metadata
 | ||||
|   res.statusCode = 500; | ||||
|   res.send({ error: { code: "E_NO_IMPL", message: "not implemented" } }); | ||||
| }); | ||||
| app.post('/api/telebit.cloud/account/authorizations/new/:magic/:pin', function (req, res) { | ||||
| app.post('/api/telebit.cloud/account/authorizations/verify/:magic/:pin', function (req, res) { | ||||
|   // For agent to confirm user's intent
 | ||||
|   res.statusCode = 500; | ||||
|   res.send({ error: { code: "E_NO_IMPL", message: "not implemented" } }); | ||||
| }); | ||||
| app.post('/api/telebit.cloud/account/authorizations/new/:value/:challenge?', function (req, res) { | ||||
|   // For agent to confirm user's intent
 | ||||
|   var dns = require('dns'); | ||||
|   var accId = Accounts._getTokenId(req.auth); | ||||
|   var val = req.params.value; | ||||
|   var ch = req.params.challenge; | ||||
|   var claim = DB.authorizations.check({ id: accId }, { challenge: ch, value: val }); | ||||
| 
 | ||||
|   function notFound() { | ||||
|     res.send({ error: { | ||||
|       code: "E_PENDING" | ||||
|     , message: "Did not find '" + claim.challenge + "' among records at '_claim-challenge." + claim.value + "'" | ||||
|     } }); | ||||
|   } | ||||
| 
 | ||||
|   function grantDnsClaim() { | ||||
|     return Accounts.getOrCreate(req).then(function (acc) { | ||||
|       return DB.domains._add(acc, { domain: claim.value, wildcard: claim.wildcard }).then(function (result) { | ||||
|         if (!DB.authorizations.verify({ id: accId }, claim)) { | ||||
|           var err = new Error("'_claim-challenge." + claim.value + "' matched, but final verification failed"); | ||||
|           err.code = "E_UNKNOWN"; | ||||
|           return PromiseA.reject(err); | ||||
|         } | ||||
|         return result; | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function checkDns() { | ||||
|     dns.resolveTxt('_claim-challenge.' + claim.value, function (err, records) { | ||||
|       if (err) { | ||||
|         notFound(); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       if (!records.some(function (txts) { | ||||
|         return txts.some(function (txt) { | ||||
|           console.log('TXT', txt); | ||||
|           return claim.challenge === txt; | ||||
|         }); | ||||
|       })) { | ||||
|         notFound(); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       grantDnsClaim().then(function () { | ||||
|         res.send({ success: true }); | ||||
|       }).catch(function (err) { | ||||
|         res.send({ error: { code: err.code, message: err.toString(), _stack: err.stack } });  | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   console.log('claim', claim); | ||||
| 
 | ||||
|   if ('dns' === claim.type) { | ||||
|     checkDns(); | ||||
|   } else if ('email' === claim.type) { | ||||
|     res.statusCode = 500; | ||||
|     res.send({ error: { code: "E_NO_IMPL", message: "'" + claim.type + "' not implemented yet" } }); | ||||
|   } else { | ||||
|     res.statusCode = 500; | ||||
|     res.send({ error: { code: "E_NO_IMPL", message: "'" + claim.type + "' not understood" } }); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
| // From Device (which knows id, but not secret)
 | ||||
|  | ||||
| @ -11,6 +11,7 @@ | ||||
|   "license": "SEE LICENSE IN LICENSE", | ||||
|   "dependencies": { | ||||
|     "jwk-to-pem": "^2.0.0", | ||||
|     "oauth3.js": "^1.2.5" | ||||
|     "oauth3.js": "^1.2.5", | ||||
|     "scmp": "^1.0.2" | ||||
|   } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user