diff --git a/lib/httpd.js b/lib/httpd.js index 480d8b4..e04ad67 100644 --- a/lib/httpd.js +++ b/lib/httpd.js @@ -180,6 +180,15 @@ module.exports.create = function (cli, engine/*, dnsd*/) { 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); + }); + }); function mapRecord(r) { return { id: r.id @@ -247,12 +256,13 @@ module.exports.create = function (cli, engine/*, dnsd*/) { if ('SOA' === record.type) { // TODO be strict about what can be edited - engine.zones.save(record, function (err/*, record*/) { - if (!err) { - res.send({ success: true }); + engine.zones.save(record, function (err, zone) { + if (err) { + res.send({ error: { message: err.message } }); return; } - res.send({ error: { message: err.message } }); + // { success: true } + res.send(zone); }); } else { engine.records.save(record, function (err, record) { diff --git a/lib/public/js/app.js b/lib/public/js/app.js index 60dbd28..a1652b4 100644 --- a/lib/public/js/app.js +++ b/lib/public/js/app.js @@ -323,7 +323,9 @@ console.log('nss:'); console.log(nss); - if (!$qs('input.js-zone-form-vanityns').checked) { + zone.vanity = false; + if ($qs('input.js-zone-form-vanityns').checked) { + zone.vanity = true; zone.vanityNs = nss; } $qs('.js-zone-form-tpl').innerHTML = ''; @@ -347,7 +349,8 @@ console.log('result:', data); if (!zone.id) { zone.id = data.id; - renderRecords(); + cache.zones.push(data); + renderZones(); } }); }); diff --git a/lib/store.json.js b/lib/store.json.js index 37b5ca7..225b3c4 100644 --- a/lib/store.json.js +++ b/lib/store.json.js @@ -94,8 +94,8 @@ module.exports.create = function (opts) { , email_addr: domain.admin || ('admin.' + domain.name) // serial -- the version, for cache-busting of secondary nameservers. suggested format: YYYYMMDDnn - , serial: domain.serial || Math.round((domain.updatedAt || domain.createdAt || 0) / 1000) - , sn: domain.serial || Math.round((domain.updatedAt || domain.createdAt || 0) / 1000) + , serial: domain.serial || engine.zones._dateToSerial(domain.updatedAt || domain.createdAt || Date.now()) + , sn: domain.serial || engine.zones._dateToSerial(domain.updatedAt || domain.createdAt || Date.now()) // refresh -- only used when nameservers following the DNS NOTIFY spec talk , refresh: domain.refresh || 1800 @@ -106,8 +106,8 @@ module.exports.create = function (opts) { , ret: domain.retry || 600 // expiration -- how long other nameservers should continue when the primary goes down - , expiration: domain.expiration || 2419200 - , ex: domain.expiration || 2419200 + , expiration: domain.expiration || 2419200 // 4 weeks + , ex: domain.expiration || 2419200 // 4 weeks // minimum -- how long to cache a non-existent domain (also the default ttl for BIND) , minimum: domain.minimum || 5 @@ -138,7 +138,15 @@ module.exports.create = function (opts) { } }; engine.zones = { - all: function (cb) { + _immutableKeys: [ 'id', 'name', 'primary', 'serial', 'revokedAt', 'changedAt', 'insertedAt', 'updatedAt', 'deletedAt' ] + , _mutableKeys: [ 'admin', 'expiration', 'minimum', 'refresh', 'retry', 'ttl', 'vanity' ] + , _dateToSerial: function (date) { + // conventionally the format is YYYYMMDDxx, + // but since it's an integer and I don't want to keep track of incrementing xx, + // epoch in seconds will do + return parseInt(Math.round(date/1000).toString().slice(-10), 10); + } + , all: function (cb) { process.nextTick(function () { cb(null, db.zones.slice(0).filter(notDeleted)); }); @@ -203,8 +211,7 @@ module.exports.create = function (opts) { console.log(existing); console.log(zone); Object.keys(zone).forEach(function (key) { - var keys = [ 'name', 'id', 'revokedAt', 'changedAt', 'insertedAt', 'updatedAt', 'deletedAt' ]; - if (-1 !== keys.indexOf(key)) { return; } + if (-1 !== engine.zones._immutableKeys.indexOf(key)) { return; } if (existing[key] !== zone[key]) { dirty = true; console.log('existing key', key, existing[key], zone[key]); @@ -222,6 +229,89 @@ module.exports.create = function (opts) { cb(err, !err && existing || null); }); } + , create: function (zone, cb) { + var newZone = { id: crypto.randomBytes(16).toString('hex') }; + var existing; + var nss = []; + + zone.name = (zone.name||'').toLowerCase(); + db.zones.some(function (z) { + if (z.name === zone.name) { + existing = z; + return true; + } + }); + + if (existing) { + cb(new Error("tried to create new zone, but '" + existing.name + "' already exists")); + return; + } + newZone.name = zone.name; + newZone.createdAt = Date.now(); + newZone.updatedAt = newZone.createdAt; + + Object.keys(zone).forEach(function (key) { + //if (-1 !== engine.zones._immutableKeys.indexOf(key)) { return; } + if (-1 === engine.zones._mutableKeys.indexOf(key)) { return; } + newZone[key] = zone[key]; + }); + + // TODO create NS and A records for normal and vanity nameservers + if (zone.vanity) { + newZone.vanity = true; + } else { + newZone.vanity = false; + } + db.primaryNameservers.forEach(function (ns, i) { + var nsx = 'ns' + (i + 1); + var nsZone; + var ttl = 43200; // 12h // TODO pick a well-reasoned number + var now = Date.now(); + + if (zone.vanity) { + nsZone = nsx + '.' + newZone.name; + } else { + nsZone = ns.name; + } + + // NS example.com ns1.example.com 43200 + nss.push({ + id: crypto.randomBytes(16).toString('hex') + , createdAt: Date.now() + , updatedAt: Date.now() + , changedAt: Date.now() + , zone: newZone.name + , soa: true + , type: 'NS' + , data: nsZone + , name: newZone.name + , ttl: ttl + }); + // A ns1.example.com 127.0.0.1 43200 + nss.push({ + id: crypto.randomBytes(16).toString('hex') + , createdAt: now + , updatedAt: now + , changedAt: now + , zone: newZone.name + , soa: true + , type: ns.type + , name: nsZone + , address: ns.address + , ttl: 43200 // 12h // TODO pick a good number + }); + }); + + db.zones.push(newZone); + nss.forEach(function (ns) { + db.records.push(ns); + }); + + console.log('saving...'); + db.save(function (err) { + cb(err, !err && newZone || null); + }); + } }; engine.records = { all: function (cb) {