diff --git a/lib/httpd.js b/lib/httpd.js index 3b53ee4..d48212c 100644 --- a/lib/httpd.js +++ b/lib/httpd.js @@ -1,6 +1,86 @@ 'use strict'; module.exports.create = function (cli, engine/*, dnsd*/) { + var subparts = (cli.subject || '').split('@'); + /* + { + "kty": "EC", + "use": "sig", + "crv": "P-256", + "x": "ogbK2nP6SiEIIp4w8oXBn3dcs6kljFfTbgZYG591tUU", + "y": "sB0AekMYwpvbQfAoW-2LlEWdapNhxynfj1zBtWpE9lo", + "alg": "ES256" + } + */ + var jwt; + var jwk; + var privpem; + var pubpem; + if (!subparts[1]) { + subparts = [ 'root', 'localhost' ]; + // TODO generate new random key and store it + jwk = { + //"kid": "thumbnail(pubkey)", + "kty": "EC", + "d": "GRIT-yJVlhAsgIChbNanxv41iCxbZszbHHgK8kbZovs", + "use": "sig", + "crv": "P-256", + "x": "ogbK2nP6SiEIIp4w8oXBn3dcs6kljFfTbgZYG591tUU", + "y": "sB0AekMYwpvbQfAoW-2LlEWdapNhxynfj1zBtWpE9lo", + "alg": "ES256" + }; + jwt = require('jsonwebtoken'); + privpem = require('jwk-to-pem')(jwk, { private: true }); + pubpem = require('jwk-to-pem')(jwk, { private: false }); + console.log(privpem); + console.log("================================"); + console.log(" JWT Write Authorization Token: "); + console.log("================================"); + console.log(jwt.sign( + { sub: subparts[0] + , iss: subparts[1] + , aud: 'localhost' + , scp: '+rw@adns.org' + } + , privpem + , { notBefore: 0 // from now + , expiresIn: '2h' + , algorithm: 'ES256' + } + )); + // expressed as "from now" + /* + { NotBeforeError: jwt not active + at Object.module.exports [as verify] (digd.js/node_modules/jsonwebtoken/verify.js:117:19) + at digd.js/lib/httpd.js:112:15 + at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5) + at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13) + at digd.js/node_modules/express/lib/router/index.js:284:7 + at Function.process_params (digd.js/node_modules/express/lib/router/index.js:335:12) + at next (digd.js/node_modules/express/lib/router/index.js:275:10) + at expressInit (digd.js/node_modules/express/lib/middleware/init.js:40:5) + at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5) + at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13) + name: 'NotBeforeError', + message: 'jwt not active', + date: +050046-12-28T01:12:58.000Z } + */ + console.log("==============================="); + console.log(" JWT Read Authorization Token: "); + console.log("==============================="); + console.log(jwt.sign( + { sub: subparts[0] + , iss: subparts[1] + , aud: 'localhost' + , scp: '+r@adns.org' + } + , privpem + , { notBefore: 0 // from now + , algorithm: 'ES256' + } + )); + console.log("=========================="); + } function runHttp() { var path = require('path'); @@ -8,7 +88,79 @@ module.exports.create = function (cli, engine/*, dnsd*/) { var app = express(); var httpServer = require('http').createServer(app); - app.use('/', express.static(path.join(__dirname, 'public'))); + app.use('/api', function (req, res, next) { + var auth = (req.headers.authorization || req.query.token || '').split(/\s+/)[1]; + var token; + + if (!auth) { + res.statusCode = 403; + res.send({ error: { message: "need authorization" } }); + return; + } + + jwt = jwt || require('jsonwebtoken'); + try { + token = jwt.decode(auth); + } catch (e) { + token = null; + } + + if (!token || !token.iss) { + res.statusCode = 403; + res.send({ error: { message: "need jwt-format authorization" } }); + return; + } + + if (subparts[0] === token.sub && subparts[1] === token.iss) { + try { + /* + // { algorithm: 'ES256' } + { JsonWebTokenError: invalid algorithm + at Object.module.exports [as verify] (digd.js/node_modules/jsonwebtoken/verify.js:90:17) + at digd.js/lib/httpd.js:82:15 + at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5) + at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13) + at digd.js/node_modules/express/lib/router/index.js:284:7 + at Function.process_params (digd.js/node_modules/express/lib/router/index.js:335:12) + at next (digd.js/node_modules/express/lib/router/index.js:275:10) + at expressInit (digd.js/node_modules/express/lib/middleware/init.js:40:5) + at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5) + at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13) name: 'JsonWebTokenError', message: 'invalid algorithm' } + */ + // could be that it's private but expecting public, or public but expecting private + /* + Error: error:0906D06C:PEM routines:PEM_read_bio:no start line + at Verify.verify (crypto.js:381:23) + at verify (digd.js/node_modules/jwa/index.js:68:21) + at Object.verify (digd.js/node_modules/jwa/index.js:85:18) + at Object.jwsVerify [as verify] (digd.js/node_modules/jws/lib/verify-stream.js:54:15) + at Object.module.exports [as verify] (digd.js/node_modules/jsonwebtoken/verify.js:96:17) + at digd.js/lib/httpd.js:82:15 + at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5) + at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13) + at digd.js/node_modules/express/lib/router/index.js:284:7 + at Function.process_params (digd.js/node_modules/express/lib/router/index.js:335:12) + */ + jwt.verify(auth, pubpem, { algorithms: [ 'ES256' ] }); + } catch(e) { + res.statusCode = 403; + console.error(e); + console.log(auth); + console.log(jwt.decode(auth, { complete: true })); + res.send({ error: { message: "jwt was not verified authorization" } }); + return; + } + } + + req.auth = auth; + req.token = token; + + next(); + }); + + app.get('/api/verify-auth', function (req, res) { + res.send({ success: true }); + }); app.get('/api/peers', function (req, res) { engine.peers.all(function (err, peers) { res.send({ peers: peers }); @@ -69,6 +221,8 @@ module.exports.create = function (cli, engine/*, dnsd*/) { }); }); + app.use('/', express.static(path.join(__dirname, 'public'))); + httpServer.listen(cli.http, function () { console.log(httpServer.address().address + '#' + httpServer.address().port + ' (http)'); }); diff --git a/lib/public/index.html b/lib/public/index.html index 12b7a3e..4dd8db9 100644 --- a/lib/public/index.html +++ b/lib/public/index.html @@ -4,6 +4,12 @@ ADNS +

ADNS Zones and Records

+

+ + +

+

/api/peers

/api/zones

/api/zones/

+ + diff --git a/lib/public/js/app.js b/lib/public/js/app.js index 16e76d1..1b95d03 100644 --- a/lib/public/js/app.js +++ b/lib/public/js/app.js @@ -1,28 +1,87 @@ (function () { 'use strict'; + if (!Element.prototype.matches) { + Element.prototype.matches = Element.prototype.msMatchesSelector; + } function $qs(qs) { return document.querySelector(qs); } + function $on(selector, eventname, cb) { + if (!$on._events[eventname]) { + $on._events[eventname] = $on._dispatcher(eventname); + document.addEventListener(eventname, $on._events[eventname]); + } + if (!$on._handlers[eventname]) { + $on._handlers[eventname] = {}; + } + if (!$on._handlers[eventname][selector]) { + $on._handlers[eventname][selector] = []; + } + $on._handlers[eventname][selector].push(cb); + } + $on._events = {}; + $on._handlers = {}; + $on._dispatcher = function (eventname) { + return function (ev) { + //console.log('event: ' + ev.type); + if (!$on._handlers[eventname]) { + console.warn('no handlers for event'); + return; + } + var matches = Object.keys($on._handlers[eventname]).some(function (selector) { + if (ev.target.matches(selector)) { + $on._handlers[eventname][selector].forEach(function (cb) { cb(ev); }); + return true; + } + }); + if (!matches) { + console.warn("no handlers for selector"); + } + }; + }; + $on('body', 'click', function () { + console.log('woo-hoo, that tickles my body!'); + }); - document.body.addEventListener('keyup', function (ev) { - console.log('ev.target.tagName:'); - console.log(ev.target.tagName); - console.log('/\\bjs-zone\\b/.test(ev.target.className):'); - console.log(/\bjs-zone\b/.test(ev.target.className)); - if ('INPUT' === ev.target.tagName && /\bjs-zone\b/.test(ev.target.className)) { - $qs('code.js-zone').innerHTML = ev.target.value || ':zone'; - // $qs('a.js-zone').setAttribute('data-href', ...) - $qs('a.js-zone').href = - $qs('a.js-zone').dataset.href.replace(/:zone/, ev.target.value || ':zone'); - return; - } - if ('INPUT' === ev.target.tagName && /\bjs-name\b/.test(ev.target.className)) { - $qs('code.js-name').innerHTML = ev.target.value || ':name'; - $qs('a.js-name').href = - $qs('a.js-name').dataset.href.replace(/:name/, ev.target.value || ':name'); - return; - } + var auth = localStorage.getItem('auth'); + + $qs('input.js-jwt').value = auth || ''; + + + $on('button.js-jwt', 'click', function (/*ev*/) { + auth = $qs('input.js-jwt').value; + return window.fetch( + '/api/verify-auth' + , { method: 'GET' + , headers: new window.Headers({ 'Authorization': 'Bearer ' + auth }) + } + ).then(function (resp) { + return resp.json().then(function (data) { + if (data.error) { + console.error(data.error); + window.alert('Bad HTTP Response: ' + data.error.message); + throw new Error('Bad HTTP Response: ' + data.error.message); + } + + console.log(data); + + localStorage.setItem('auth', auth); + }); + }); + }); + + $on('input.js-zone', 'keyup', function (ev) { + $qs('code.js-zone').innerHTML = ev.target.value || ':zone'; + // $qs('a.js-zone').setAttribute('data-href', ...) + $qs('a.js-zone').href = + $qs('a.js-zone').dataset.href.replace(/:zone/, ev.target.value || ':zone'); + }); + + $on('input.js-name', 'keyup', function (ev) { + $qs('code.js-name').innerHTML = ev.target.value || ':name'; + $qs('a.js-name').href = + $qs('a.js-name').dataset.href.replace(/:name/, ev.target.value || ':name'); }); }()); diff --git a/package.json b/package.json index c453b87..c5856bd 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,8 @@ "dependencies": { "dig.js": "git+https://git.coolaj86.com/coolaj86/dig.js#v1.3", "express": "^4.16.2", - "hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4" + "hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4", + "jsonwebtoken": "^8.1.0", + "jwk-to-pem": "^1.2.6" } }