digd.js/lib/httpd.js

326 lines
11 KiB
JavaScript

'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 || '').split(/\s+/)[1] || req.query.access_token);
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
, soa: r.soa
, 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;
zone.soa = true;
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();
};