'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 = cli.privkey || require('../samples/privkey.js'); jwt = require('jsonwebtoken'); privpem = require('jwk-to-pem')(jwk, { private: true }); pubpem = require('jwk-to-pem')(jwk, { private: false }); console.info("===================="); console.info(" EC256 Private PEM: "); console.info("===================="); console.info(privpem); console.info(); console.info("================================"); console.info(" JWT Write Authorization Token: "); console.info("================================"); console.info(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.info("==============================="); console.info(" JWT Read Authorization Token: "); console.info("==============================="); console.info(jwt.sign( { sub: subparts[0] , iss: subparts[1] , aud: 'localhost' , scp: '+r@adns.org' } , privpem , { notBefore: 0 // from now , algorithm: 'ES256' } )); console.info("=========================="); } function runHttp() { var path = require('path'); var express = require('express'); var jsonParser = require('body-parser').json({ strict: true, limit: '100kb' }); var app = express(); var httpServer = require('http').createServer(app); function hasClaim(claim) { return function (req, res, next) { if ((req.token.scp||'').split(/[\s,]/g).some(function (c) { return claim === c; })) { next(); } else { res.send({ error: { message: "no claim to '" + claim + "' in token" } }); return; } }; } 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 }); }); }); app.get('/api/zones', function (req, res) { engine.zones.all(function (err, zones) { res.send({ zones: zones }); }); }); app.post('/api/zones', jsonParser, hasClaim('+rw@adns.org'), function (req, res) { var upzone = req.body || {}; console.log('create zone', upzone); engine.zones.save(upzone, function (err, zone) { if (err) { res.send({ error: { message: err.message } }); return; } console.log('create zone result', zone); res.send(zone); }); }); app.delete('/api/zones/:id', hasClaim('+rw@adns.org'), function (req, res) { var zoneId = req.params.id; engine.zones.destroy(zoneId, function (err, zone) { if (err) { res.send({ error: { message: err.message } }); return; } // zone + records res.send(zone); }); }); function mapRecord(r) { return { id: r.id , zone: r.zone , name: r.name , tld: r.tld , type: r.type , class: r.class , ttl: r.ttl , data: r.data , address: r.address , exchange: r.exchange , priority: r.priority , value: r.value , aname: r.aname , flag: r.flag , tag: r.tag , weight: r.weight , port: r.port , target: r.target }; } app.get('/api/zones/:zone/records', function (req, res) { var zonename = req.params.zone; // [{ name: zonename }] engine.zones.get({ names: [ zonename ] }, function (err, zones) { console.log('zone:'); console.log(zones[0]); var zone = engine.zones._toSoa(zones[0]); zone.class = zone.className; zone.type = zone.typeName; engine.records.all(function (err, records) { records = records.filter(function (r) { return r.zone === zonename; }).map(mapRecord); records.unshift(zone); res.send({ records: records }); }); }); }); app.get('/api/records', function (req, res) { engine.records.all(function (err, records) { res.send({ records: records.map(mapRecord) }); }); }); app.get('/api/records/:name', function (req, res) { engine.records.all(function (err, records) { res.send({ records: records.filter(function (r) { if (r.name === req.params.name) { return true; } var parts = req.params.name.split('.'); parts.shift(); if ('*.' + parts.join('.') === r.name) { return true; } }).map(mapRecord) }); }); }); app.post('/api/records/:id?', jsonParser, hasClaim('+rw@adns.org'), function (req, res) { var record = req.body || {}; record.id = req.params.id || record.id; if ('SOA' === record.type) { // TODO be strict about what can be edited engine.zones.save(record, function (err, zone) { if (err) { res.send({ error: { message: err.message } }); return; } // { success: true } res.send(zone); }); } else { engine.records.save(record, function (err, record) { res.send({ success: true, id: record.id }); }); } }); app.delete('/api/records/:id', jsonParser, hasClaim('+rw@adns.org'), function (req, res) { var id = req.params.id; engine.records.one(id, function (err, record) { if (err) { res.send({ error: { message: err.message } }); return; } if (!record) { res.send({ error: { message: "no record with id '" + id + "' found" } }); return; } if ('SOA' === record.type) { // TODO be strict about what can be edited engine.zones.destroy(id, function (err, record) { if (!err) { res.send(record); return; } res.send({ error: { message: err.message } }); }); } else { engine.records.destroy(id, function (err, record) { if (!err) { res.send(record || { error: { message: "no record with id '" + id + "' found" } }); return; } res.send({ error: { message: err.message } }); }); } }); }); app.use('/', express.static(path.join(__dirname, 'public'))); httpServer.listen(cli.http, function () { console.info(httpServer.address().address + '#' + httpServer.address().port + ' (http)'); }); } runHttp(); };