dns check complete
This commit is contained in:
parent
2819117f10
commit
2564b750e6
|
@ -16,16 +16,25 @@
|
||||||
|
|
||||||
<div v-if="hasAccount">
|
<div v-if="hasAccount">
|
||||||
<h1>Account</h1>
|
<h1>Account</h1>
|
||||||
<form v-on:submit="challengeDns()">
|
<form v-on:submit.prevent="challengeDns()">
|
||||||
Add a custom domain:
|
Add a custom domain:
|
||||||
<input v-model="newDomain" placeholder="example.com" type="text" required/>
|
<input v-model="newDomain" placeholder="example.com" type="text" required/>
|
||||||
<button type="submit">Next</button>
|
<button type="submit">Next</button>
|
||||||
</form>
|
</form>
|
||||||
<form v-on:submit="challengeEmail()">
|
<form v-on:submit.prevent="challengeEmail()">
|
||||||
Authorize another email:
|
Authorize another email:
|
||||||
<input v-model="newEmail" placeholder="jon@example.com" type="email" required/>
|
<input v-model="newEmail" placeholder="jon@example.com" type="email" required/>
|
||||||
<button type="submit">Next</button>
|
<button type="submit">Next</button>
|
||||||
</form>
|
</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>
|
<ol>
|
||||||
<li v-for="domain in domains">
|
<li v-for="domain in domains">
|
||||||
{{ domain }}
|
{{ domain }}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/*global Vue*/
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
var OAUTH3 = window.OAUTH3;
|
var OAUTH3 = window.OAUTH3;
|
||||||
|
@ -5,33 +6,7 @@
|
||||||
host: window.location.host
|
host: window.location.host
|
||||||
, pathname: window.location.pathname.replace(/\/[^\/]*$/, '/')
|
, pathname: window.location.pathname.replace(/\/[^\/]*$/, '/')
|
||||||
});
|
});
|
||||||
var $ = function () { return document.querySelector.apply(document, arguments); }
|
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 sessionStr = localStorage.getItem('session');
|
var sessionStr = localStorage.getItem('session');
|
||||||
var session;
|
var session;
|
||||||
if (sessionStr) {
|
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) {
|
function loadAccount(session) {
|
||||||
return oauth3.request({
|
return oauth3.request({
|
||||||
url: 'https://api.' + location.hostname + '/api/telebit.cloud/account'
|
url: 'https://api.' + location.hostname + '/api/telebit.cloud/account'
|
||||||
|
@ -64,7 +82,7 @@
|
||||||
, method: 'POST'
|
, method: 'POST'
|
||||||
, session: session
|
, session: session
|
||||||
, body: {
|
, body: {
|
||||||
email: email
|
email: vueData.newEmail
|
||||||
}
|
}
|
||||||
}).then(function (resp) {
|
}).then(function (resp) {
|
||||||
listStuff(resp);
|
listStuff(resp);
|
||||||
|
@ -124,7 +142,7 @@
|
||||||
console.log(resp.data);
|
console.log(resp.data);
|
||||||
|
|
||||||
localStorage.setItem('session', JSON.stringify(session));
|
localStorage.setItem('session', JSON.stringify(session));
|
||||||
loadAccount(session)
|
loadAccount(session);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -138,7 +156,7 @@
|
||||||
$('body form.js-auth-form').addEventListener('submit', onClickLogin);
|
$('body form.js-auth-form').addEventListener('submit', onClickLogin);
|
||||||
onChangeProvider('oauth3.org');
|
onChangeProvider('oauth3.org');
|
||||||
if (session) {
|
if (session) {
|
||||||
vueData.token = session.access_token
|
vueData.token = session.access_token;
|
||||||
loadAccount(session);
|
loadAccount(session);
|
||||||
}
|
}
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -29,7 +29,12 @@ DB._load = function () {
|
||||||
DB._byId = {};
|
DB._byId = {};
|
||||||
DB._grants = {};
|
DB._grants = {};
|
||||||
DB._grantsMap = {};
|
DB._grantsMap = {};
|
||||||
|
DB._authz = {};
|
||||||
DB._perms.forEach(function (acc) {
|
DB._perms.forEach(function (acc) {
|
||||||
|
if ('authz' === acc.type) {
|
||||||
|
DB._authz[acc.id] = acc;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (acc.id) {
|
if (acc.id) {
|
||||||
// if account has an id
|
// if account has an id
|
||||||
DB._byId[acc.id] = acc;
|
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 = {};
|
||||||
DB.domains.available = function (name) {
|
DB.domains.available = function (name) {
|
||||||
return PromiseA.resolve().then(function () {
|
return PromiseA.resolve().then(function () {
|
||||||
|
@ -154,7 +233,7 @@ DB.domains._add = function (acc, opts) {
|
||||||
parts.shift();
|
parts.shift();
|
||||||
parts.pop();
|
parts.pop();
|
||||||
if (parts.some(function (part) {
|
if (parts.some(function (part) {
|
||||||
if (DB._byDomain[part]) {
|
if (DB._byDomain[part] && DB._byDomain[part].wildcard) {
|
||||||
pdomain = part;
|
pdomain = part;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -175,6 +254,7 @@ DB.domains._add = function (acc, opts) {
|
||||||
, domain: domain
|
, domain: domain
|
||||||
};
|
};
|
||||||
acc.domains.push(domain);
|
acc.domains.push(domain);
|
||||||
|
DB.save();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
DB.ports = {};
|
DB.ports = {};
|
||||||
|
|
|
@ -19,6 +19,15 @@ var requestAsync = util.promisify(require('@coolaj86/urequest'));
|
||||||
var mkdirpAsync = util.promisify(require('mkdirp'));
|
var mkdirpAsync = util.promisify(require('mkdirp'));
|
||||||
var TRUSTED_ISSUERS = [ 'oauth3.org' ];
|
var TRUSTED_ISSUERS = [ 'oauth3.org' ];
|
||||||
var DB = require('./db.js');
|
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 = module.exports._auths = {};
|
||||||
var Auths = {};
|
var Auths = {};
|
||||||
|
@ -763,6 +772,7 @@ app.get('/api/telebit.cloud/account', function (req, res) {
|
||||||
//console.log(grants);
|
//console.log(grants);
|
||||||
var domainsMap = {};
|
var domainsMap = {};
|
||||||
var portsMap = {};
|
var portsMap = {};
|
||||||
|
var claimsMap = {};
|
||||||
var result = JSON.parse(JSON.stringify(acc));
|
var result = JSON.parse(JSON.stringify(acc));
|
||||||
result.domains.length = 0;
|
result.domains.length = 0;
|
||||||
result.ports.length = 0;
|
result.ports.length = 0;
|
||||||
|
@ -775,6 +785,11 @@ app.get('/api/telebit.cloud/account', function (req, res) {
|
||||||
account.ports.forEach(function (p) {
|
account.ports.forEach(function (p) {
|
||||||
portsMap[p.number] = 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) {
|
result.domains = Object.keys(domainsMap).map(function (k) {
|
||||||
return domainsMap[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) {
|
result.ports = Object.keys(portsMap).map(function (k) {
|
||||||
return portsMap[k];
|
return portsMap[k];
|
||||||
});
|
});
|
||||||
|
result.claims = Object.keys(claimsMap).map(function (k) {
|
||||||
|
return Claims.publicize(claimsMap[k]);
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -821,24 +839,121 @@ app.post('/api/telebit.cloud/account', function (req, res) {
|
||||||
// Challenge Nodes / Email, Domains / DNS
|
// Challenge Nodes / Email, Domains / DNS
|
||||||
app.post('/api/telebit.cloud/account/authorizations/new', function (req, res) {
|
app.post('/api/telebit.cloud/account/authorizations/new', function (req, res) {
|
||||||
// Send email via SMTP, confirm client's chosen pin
|
// Send email via SMTP, confirm client's chosen pin
|
||||||
res.statusCode = 500;
|
var accId = Accounts._getTokenId(req.auth);
|
||||||
res.send({ error: { code: "E_NO_IMPL", message: "not implemented" } });
|
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
|
// For client to check on status
|
||||||
res.statusCode = 500;
|
var accId = Accounts._getTokenId(req.auth);
|
||||||
res.send({ error: { code: "E_NO_IMPL", message: "not implemented" } });
|
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) {
|
app.get('/api/telebit.cloud/account/authorizations/meta/:secret', function (req, res) {
|
||||||
// For agent to retrieve metadata
|
// For agent to retrieve metadata
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
res.send({ error: { code: "E_NO_IMPL", message: "not implemented" } });
|
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
|
// For agent to confirm user's intent
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
res.send({ error: { code: "E_NO_IMPL", message: "not implemented" } });
|
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)
|
// From Device (which knows id, but not secret)
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jwk-to-pem": "^2.0.0",
|
"jwk-to-pem": "^2.0.0",
|
||||||
"oauth3.js": "^1.2.5"
|
"oauth3.js": "^1.2.5",
|
||||||
|
"scmp": "^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue