From 85a1e6f80c9794b876e8501a744bd01baaab4082 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 6 Mar 2024 16:34:50 -0800 Subject: [PATCH 01/13] feat(lib/nameserver): added, with tests, fixes #22 --- .github/workflows/ci.yml | 2 + .release | 2 +- lib/test/zone.json | 16 ++++++++ lib/zone.js | 89 ++++++++++++++++++++++++++++++++++++++++ lib/zone.test.js | 48 ++++++++++++++++++++++ routes/user.js | 1 - 6 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 lib/test/zone.json create mode 100644 lib/zone.js create mode 100644 lib/zone.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1f034e..181e49c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,8 @@ name: CI on: push: + paths-ignore: + - '*.md' pull_request: env: diff --git a/.release b/.release index 8959a99..74f584c 160000 --- a/.release +++ b/.release @@ -1 +1 @@ -Subproject commit 8959a99cbbd4ac0e6871386a81d15b06307d6528 +Subproject commit 74f584cf23f63960f4fccefe08436e1261e34241 diff --git a/lib/test/zone.json b/lib/test/zone.json new file mode 100644 index 0000000..c6bdc17 --- /dev/null +++ b/lib/test/zone.json @@ -0,0 +1,16 @@ +{ + "id": 4096, + "gid": 4096, + "zone": "example.com", + "mailaddr": "hostmaster.example.com.", + "description": "unit test", + "serial": 20240306, + "refresh": 1, + "retry": 2, + "expire": 3, + "minimum": 4, + "ttl": 3600, + "location": "", + "last_publish": null, + "deleted": false +} diff --git a/lib/zone.js b/lib/zone.js new file mode 100644 index 0000000..ca98b78 --- /dev/null +++ b/lib/zone.js @@ -0,0 +1,89 @@ +import Mysql from './mysql.js' +import { mapToDbColumn } from './util.js' + +const zoneDbMap = { id: 'nt_zone_id', gid: 'nt_group_id' } +const boolFields = ['deleted'] + +class Zone { + constructor() { + this.mysql = Mysql + } + + async create(args) { + if (args.id) { + const g = await this.get({ id: args.id }) + if (g.length === 1) return g[0].id + } + + return await Mysql.execute( + ...Mysql.insert( + `nt_zone`, + mapToDbColumn(args, zoneDbMap), + ), + ) + } + + async get(args) { + const rows = await Mysql.execute( + ...Mysql.select( + `SELECT nt_zone_id AS id + , nt_group_id AS gid + , zone + , mailaddr + , description + , serial + , refresh + , retry + , expire + , minimum + , ttl + , location + , last_modified + , last_publish + , deleted + FROM nt_zone`, + mapToDbColumn(args, zoneDbMap), + ), + ) + for (const r of rows) { + for (const b of boolFields) { + r[b] = r[b] === 1 + } + for (const f of ['description', 'location']) { + if ([null].includes(r[f])) r[f] = '' + } + } + + return rows + } + + async put(args) { + if (!args.id) return false + const id = args.id + delete args.id + const r = await Mysql.execute( + ...Mysql.update( + `nt_zone`, + `nt_zone_id=${id}`, + mapToDbColumn(args, zoneDbMap), + ), + ) + return r.changedRows === 1 + } + + async delete(args) { + await Mysql.execute( + `UPDATE nt_zone SET deleted=? WHERE nt_zone_id=?`, + [args.deleted ?? 1, args.id], + ) + return true + } + + async destroy(args) { + return await Mysql.execute( + ...Mysql.delete(`nt_zone`, { nt_zone_id: args.id }), + ) + } +} + +export default new Zone() diff --git a/lib/zone.test.js b/lib/zone.test.js new file mode 100644 index 0000000..05bab3e --- /dev/null +++ b/lib/zone.test.js @@ -0,0 +1,48 @@ +import assert from 'node:assert/strict' +import { describe, it, after, before } from 'node:test' + +import Zone from './zone.js' + +import testCase from './test/zone.json' with { type: 'json' } + +before(async () => { + await Zone.destroy({ id: testCase.id }) + await Zone.create(testCase) +}) + +after(async () => { + // await Zone.destroy({ id: testCase.id }) + Zone.mysql.disconnect() +}) + +describe('zone', function () { + it('gets zone by id', async () => { + const g = await Zone.get({ id: testCase.id }) + delete g[0].last_modified + assert.deepEqual(g[0], testCase) + }) + + it('gets zone by name', async () => { + const g = await Zone.get({ zone: testCase.zone }) + delete g[0].last_modified + assert.deepEqual(g[0], testCase) + }) + + it('changes a zone', async () => { + assert.ok( + await Zone.put({ id: testCase.id, mailaddr: 'toastmaster.example.com.' }), + ) + const ns = await Zone.get({ id: testCase.id }) + assert.deepEqual(ns[0].mailaddr, 'toastmaster.example.com.') + assert.ok(await Zone.put({ id: testCase.id, mailaddr: testCase.mailaddr })) + }) + + it('deletes a zone', async () => { + assert.ok(await Zone.delete({ id: testCase.id })) + let g = await Zone.get({ id: testCase.id, deleted: 1 }) + assert.equal(g[0]?.deleted, true) + await Zone.delete({ id: testCase.id, deleted: 0 }) // restore + g = await Zone.get({ id: testCase.id }) + assert.equal(g[0].deleted, false) + }) +}) diff --git a/routes/user.js b/routes/user.js index 288cd66..dc0c541 100644 --- a/routes/user.js +++ b/routes/user.js @@ -47,7 +47,6 @@ function UserRoutes(server) { tags: ['api'], }, handler: async (request, h) => { - const users = await User.get({ deleted: request.query.deleted ?? 0, id: parseInt(request.params.id, 10), From 0087060a7b0d37eba28ab63537d9a572bf354bf0 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 6 Mar 2024 16:36:07 -0800 Subject: [PATCH 02/13] update changes --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87125d3..dcb75b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ ### Unreleased +### [3.0.0-alpha.5] - 2024-03-06 + +- feat(lib/nameserver): added, with tests, fixes #22 + + ### [3.0.0-alpha.4] - 2024-03-05 - feat(lib/nameserver): added, with tests @@ -21,3 +26,4 @@ [3.0.0-alpha.3]: https://github.com/NicTool/api/releases/tag/3.0.0-alpha.3 [3.0.0-alpha.4]: https://github.com/NicTool/api/releases/tag/3.0.0-alpha.4 +[3.0.0-alpha.5]: https://github.com/NicTool/api/releases/tag/3.0.0-alpha.5 diff --git a/package.json b/package.json index 9e20c74..dbdbb4e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nictool/api", - "version": "3.0.0-alpha.4", + "version": "3.0.0-alpha.5", "description": "NicTool API", "main": "index.js", "type": "module", From acf6876d893afde3b914863dd3d2d9ba8d363772 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 6 Mar 2024 16:36:11 -0800 Subject: [PATCH 03/13] lint & format --- lib/zone.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/zone.js b/lib/zone.js index ca98b78..4f89668 100644 --- a/lib/zone.js +++ b/lib/zone.js @@ -16,10 +16,7 @@ class Zone { } return await Mysql.execute( - ...Mysql.insert( - `nt_zone`, - mapToDbColumn(args, zoneDbMap), - ), + ...Mysql.insert(`nt_zone`, mapToDbColumn(args, zoneDbMap)), ) } @@ -72,10 +69,10 @@ class Zone { } async delete(args) { - await Mysql.execute( - `UPDATE nt_zone SET deleted=? WHERE nt_zone_id=?`, - [args.deleted ?? 1, args.id], - ) + await Mysql.execute(`UPDATE nt_zone SET deleted=? WHERE nt_zone_id=?`, [ + args.deleted ?? 1, + args.id, + ]) return true } From e3b9c822011e2be00517d2f8663fdd778661c55d Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 6 Mar 2024 17:10:40 -0800 Subject: [PATCH 04/13] - feat(lib/zone_record): added, with tests, fixes #23 --- CHANGELOG.md | 3 +- lib/test/zone_record.json | 8 ++++ lib/zone_record.js | 86 +++++++++++++++++++++++++++++++++++++++ lib/zone_record.test.js | 48 ++++++++++++++++++++++ package.json | 1 + 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 lib/test/zone_record.json create mode 100644 lib/zone_record.js create mode 100644 lib/zone_record.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb75b7..155b3f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ ### [3.0.0-alpha.5] - 2024-03-06 -- feat(lib/nameserver): added, with tests, fixes #22 +- feat(lib/zone): added, with tests, fixes #22 +- feat(lib/zone_record): added, with tests, fixes #23 ### [3.0.0-alpha.4] - 2024-03-05 diff --git a/lib/test/zone_record.json b/lib/test/zone_record.json new file mode 100644 index 0000000..d102022 --- /dev/null +++ b/lib/test/zone_record.json @@ -0,0 +1,8 @@ +{ + "id": 4096, + "zid": 4096, + "name": "a", + "ttl": 86400, + "type": "A", + "address": "1.1.1.1" +} \ No newline at end of file diff --git a/lib/zone_record.js b/lib/zone_record.js new file mode 100644 index 0000000..f975b68 --- /dev/null +++ b/lib/zone_record.js @@ -0,0 +1,86 @@ +import Mysql from './mysql.js' +import { mapToDbColumn } from './util.js' + +const zrDbMap = { id: 'nt_zone_record_id', zid: 'nt_zone_id' } +const boolFields = ['deleted'] + +class ZoneRecord { + constructor() { + this.mysql = Mysql + } + + async create(args) { + if (args.id) { + const g = await this.get({ id: args.id }) + if (g.length === 1) return g[0].id + } + + // const type = await Mysql.execute(...Mysql.select(`SELECT `)) + + return await Mysql.execute( + ...Mysql.insert(`nt_zone_record`, mapToDbColumn(args, zrDbMap)), + ) + } + + async get(args) { + const rows = await Mysql.execute( + ...Mysql.select( + `SELECT nt_zone_record_id AS id + , nt_zone_id AS zid + , name + , ttl + , description + , type_id + , address + , weight + , priority + , other + , location + , timestamp + , deleted + FROM nt_zone_record`, + mapToDbColumn(args, zrDbMap), + ), + ) + for (const r of rows) { + for (const b of boolFields) { + r[b] = r[b] === 1 + } + for (const f of ['description', 'other', 'location']) { + if ([null].includes(r[f])) r[f] = '' + } + } + + return rows + } + + async put(args) { + if (!args.id) return false + const id = args.id + delete args.id + const r = await Mysql.execute( + ...Mysql.update( + `nt_zone_record`, + `nt_zone_record_id=${id}`, + mapToDbColumn(args, zrDbMap), + ), + ) + return r.changedRows === 1 + } + + async delete(args) { + await Mysql.execute(`UPDATE nt_zone_record SET deleted=? WHERE nt_zone_record_id=?`, [ + args.deleted ?? 1, + args.id, + ]) + return true + } + + async destroy(args) { + return await Mysql.execute( + ...Mysql.delete(`nt_zone_record`, { nt_zone_record_id: args.id }), + ) + } +} + +export default new ZoneRecord() diff --git a/lib/zone_record.test.js b/lib/zone_record.test.js new file mode 100644 index 0000000..3c61620 --- /dev/null +++ b/lib/zone_record.test.js @@ -0,0 +1,48 @@ +import assert from 'node:assert/strict' +import { describe, it, after, before } from 'node:test' + +import ZoneRecord from './zone_record.js' + +import testCase from './test/zone_record.json' with { type: 'json' } + +before(async () => { + await ZoneRecord.destroy({ id: testCase.id }) + await ZoneRecord.create(testCase) +}) + +after(async () => { + // await ZoneRecord.destroy({ id: testCase.id }) + ZoneRecord.mysql.disconnect() +}) + +describe('zone_record', function () { + it('gets zone_record by id', async () => { + const g = await ZoneRecord.get({ id: testCase.id }) + delete g[0].last_modified + assert.deepEqual(g[0], testCase) + }) + + it('gets zone_record by name', async () => { + const g = await ZoneRecord.get({ zone_record: testCase.zone_record }) + delete g[0].last_modified + assert.deepEqual(g[0], testCase) + }) + + it('changes a zone_record', async () => { + assert.ok( + await ZoneRecord.put({ id: testCase.id, address: '2.2.2.2' }), + ) + const ns = await ZoneRecord.get({ id: testCase.id }) + assert.deepEqual(ns[0].address, '2.2.2.2') + assert.ok(await ZoneRecord.put({ id: testCase.id, address: testCase.address })) + }) + + it('deletes a zone_record', async () => { + assert.ok(await ZoneRecord.delete({ id: testCase.id })) + let g = await ZoneRecord.get({ id: testCase.id, deleted: 1 }) + assert.equal(g[0]?.deleted, true) + await ZoneRecord.delete({ id: testCase.id, deleted: 0 }) // restore + g = await ZoneRecord.get({ id: testCase.id }) + assert.equal(g[0].deleted, false) + }) +}) diff --git a/package.json b/package.json index dbdbb4e..8371411 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@hapi/hoek": "^11.0.4", "@hapi/inert": "^7.1.0", "@hapi/vision": "^7.0.3", + "@nictool/dns-resource-record": "^1.1.7", "@nictool/validate": "^0.8.0", "hapi-swagger": "^17.2.1", "mysql2": "^3.9.2", From fdb0520c179af86268ed35fe6206bdb2957d6ef4 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 7 Mar 2024 14:39:20 -0800 Subject: [PATCH 05/13] default GET sets deleted=false --- .github/FUNDING.yml | 3 ++ .release | 2 +- CHANGELOG.md | 2 + lib/group.js | 8 +++- lib/group.test.js | 5 +-- lib/mysql.js | 4 ++ lib/nameserver.js | 8 +++- lib/nameserver.test.js | 2 +- lib/permission.js | 83 +++++++++++++++++++++------------------ lib/permission.test.js | 2 +- lib/test/group.json | 3 +- lib/test/nameserver.json | 3 +- lib/test/permission.json | 1 - lib/test/user.json | 3 +- lib/test/zone.json | 3 +- lib/test/zone_record.json | 2 +- lib/user.js | 5 +++ lib/user.test.js | 2 +- lib/zone.js | 10 +++-- lib/zone.test.js | 24 +++++++---- lib/zone_record.js | 38 +++++++++++++----- lib/zone_record.test.js | 50 +++++++++++++---------- package.json | 2 +- 23 files changed, 164 insertions(+), 101 deletions(-) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..53e71ae --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: msimerson diff --git a/.release b/.release index 74f584c..704e50c 160000 --- a/.release +++ b/.release @@ -1 +1 @@ -Subproject commit 74f584cf23f63960f4fccefe08436e1261e34241 +Subproject commit 704e50ce14387d29293bd208d0a6ff290b49d66e diff --git a/CHANGELOG.md b/CHANGELOG.md index 155b3f1..9122a57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - feat(lib/zone): added, with tests, fixes #22 - feat(lib/zone_record): added, with tests, fixes #23 +- feat: default get sets deleted=false + - group, nameserver, permission, user, zone ### [3.0.0-alpha.4] - 2024-03-05 diff --git a/lib/group.js b/lib/group.js index 8e3f376..957656a 100644 --- a/lib/group.js +++ b/lib/group.js @@ -21,6 +21,9 @@ class Group { } async get(args) { + args = JSON.parse(JSON.stringify(args)) + if (args.deleted === undefined) args.deleted = false + const rows = await Mysql.execute( ...Mysql.select( `SELECT nt_group_id AS id @@ -31,10 +34,11 @@ class Group { mapToDbColumn(args, groupDbMap), ), ) - for (const r of rows) { + for (const row of rows) { for (const b of boolFields) { - r[b] = r[b] === 1 + row[b] = row[b] === 1 } + if (args.deleted === false) delete row.deleted } return rows } diff --git a/lib/group.test.js b/lib/group.test.js index 7ec9da1..0c2bed3 100644 --- a/lib/group.test.js +++ b/lib/group.test.js @@ -20,7 +20,6 @@ describe('group', function () { id: testCase.id, name: testCase.name, parent_gid: 0, - deleted: false, }) }) @@ -30,7 +29,6 @@ describe('group', function () { id: testCase.id, name: testCase.name, parent_gid: 0, - deleted: false, }) }) @@ -41,7 +39,6 @@ describe('group', function () { id: testCase.id, name: 'example.net', parent_gid: 0, - deleted: false, }, ]) assert.ok(await Group.put({ id: testCase.id, name: testCase.name })) @@ -53,6 +50,6 @@ describe('group', function () { assert.equal(g[0]?.deleted, true) await Group.delete({ id: testCase.id, deleted: 0 }) // restore g = await Group.get({ id: testCase.id }) - assert.equal(g[0].deleted, false) + assert.equal(g[0].deleted, undefined) }) }) diff --git a/lib/mysql.js b/lib/mysql.js index b55f35c..8754bd7 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -38,6 +38,10 @@ class Mysql { } if (/^(REPLACE|INSERT) INTO/.test(query)) return rows.insertId + if (/^UPDATE /.test(query)) { + console.log(rows) + console.log(fields) + } return rows } diff --git a/lib/nameserver.js b/lib/nameserver.js index 2aa28c8..d515ffd 100644 --- a/lib/nameserver.js +++ b/lib/nameserver.js @@ -35,6 +35,9 @@ class Nameserver { } async get(args) { + args = JSON.parse(JSON.stringify(args)) + if (args.deleted === undefined) args.deleted = false + if (args.name !== undefined) { args['ns.name'] = args.name delete args.name @@ -61,10 +64,11 @@ class Nameserver { mapToDbColumn(args, nsDbMap), ), ) - for (const r of rows) { + for (const row of rows) { for (const b of boolFields) { - r[b] = r[b] === 1 + row[b] = row[b] === 1 } + if (args.deleted === false) delete row.deleted } return dbToObject(rows) } diff --git a/lib/nameserver.test.js b/lib/nameserver.test.js index 7684348..619b5eb 100644 --- a/lib/nameserver.test.js +++ b/lib/nameserver.test.js @@ -41,6 +41,6 @@ describe('nameserver', function () { assert.equal(g[0]?.deleted, true) await Nameserver.delete({ id: testCase.id, deleted: 0 }) // restore g = await Nameserver.get({ id: testCase.id }) - assert.equal(g[0].deleted, false) + assert.equal(g[0].deleted, undefined) }) }) diff --git a/lib/permission.js b/lib/permission.js index e868640..b4feda5 100644 --- a/lib/permission.js +++ b/lib/permission.js @@ -26,6 +26,9 @@ class Permission { } async get(args) { + args = JSON.parse(JSON.stringify(args)) + if (args.deleted === undefined) args.deleted = false + const query = `SELECT p.nt_perm_id AS id , p.nt_user_id AS uid , p.nt_group_id AS gid @@ -35,7 +38,6 @@ class Permission { , p.deleted FROM nt_perm p` // Mysql.debug(1) - if (args.deleted === undefined) args.deleted = false const rows = await Mysql.execute( ...Mysql.select(query, mapToDbColumn(args, permDbMap)), @@ -46,7 +48,9 @@ class Permission { `permissions.get found ${rows.length} rows for uid ${args.uid}`, ) } - return dbToObject(rows[0]) + const row = dbToObject(rows[0]) + if (args.deleted === false) delete row.deleted + return row } async getGroup(args) { @@ -59,11 +63,13 @@ class Permission { , p.deleted FROM nt_perm p INNER JOIN nt_user u ON p.nt_group_id = u.nt_group_id - WHERE p.deleted=0 + WHERE p.deleted=${args.deleted === true ? 1 : 0} AND u.deleted=0 AND u.nt_user_id=?` const rows = await Mysql.execute(...Mysql.select(query, [args.uid])) - return dbToObject(rows[0]) + const row = dbToObject(rows[0]) + if ([false, undefined].includes(args.deleted)) delete row.deleted + return row } async put(args) { @@ -185,60 +191,61 @@ JSON object format: const boolFields = ['self_write', 'inherit', 'deleted'] function dbToObject(row) { - const newRow = JSON.parse(JSON.stringify(row)) + row = JSON.parse(JSON.stringify(row)) for (const f of ['group', 'nameserver', 'zone', 'zonerecord', 'user']) { for (const p of ['create', 'write', 'delete', 'delegate']) { - if (newRow[`${f}_${p}`] !== undefined) { - if (newRow[f] === undefined) newRow[f] = {} - newRow[f][p] = newRow[`${f}_${p}`] === 1 - delete newRow[`${f}_${p}`] + if (row[`${f}_${p}`] !== undefined) { + if (row[f] === undefined) row[f] = {} + row[f][p] = row[`${f}_${p}`] === 1 + delete row[`${f}_${p}`] } } } for (const b of boolFields) { - newRow[b] = newRow[b] === 1 + row[b] = row[b] === 1 } - if (newRow.uid !== undefined) { - newRow.user.id = newRow.uid - delete newRow.uid + + if (row.uid !== undefined) { + row.user.id = row.uid + delete row.uid } - if (newRow.gid !== undefined) { - newRow.group.id = newRow.gid - delete newRow.gid + if (row.gid !== undefined) { + row.group.id = row.gid + delete row.gid } - newRow.nameserver.usable = [] - if (![undefined, ''].includes(newRow.usable_ns)) { - newRow.nameserver.usable = newRow.usable_ns.split(',') + row.nameserver.usable = [] + if (![undefined, ''].includes(row.usable_ns)) { + row.nameserver.usable = row.usable_ns.split(',') } - delete newRow.usable_ns - return newRow + delete row.usable_ns + return row } function objectToDb(row) { - const newRow = JSON.parse(JSON.stringify(row)) - if (newRow?.user?.id !== undefined) { - newRow.uid = newRow.user.id - delete newRow.user.id + row = JSON.parse(JSON.stringify(row)) + if (row?.user?.id !== undefined) { + row.uid = row.user.id + delete row.user.id } - if (newRow?.group?.id !== undefined) { - newRow.gid = newRow.group.id - delete newRow.group.id + if (row?.group?.id !== undefined) { + row.gid = row.group.id + delete row.group.id } - if (newRow?.nameserver?.usable !== undefined) { - newRow.usable_ns = newRow.nameserver.usable.join(',') - delete newRow.nameserver.usable + if (row?.nameserver?.usable !== undefined) { + row.usable_ns = row.nameserver.usable.join(',') + delete row.nameserver.usable } for (const f of ['group', 'nameserver', 'zone', 'zonerecord', 'user']) { for (const p of ['create', 'write', 'delete', 'delegate']) { - if (newRow[f] === undefined) continue - if (newRow[f][p] === undefined) continue - newRow[`${f}_${p}`] = newRow[f][p] === true ? 1 : 0 - delete newRow[f][p] + if (row[f] === undefined) continue + if (row[f][p] === undefined) continue + row[`${f}_${p}`] = row[f][p] === true ? 1 : 0 + delete row[f][p] } - delete newRow[f] + delete row[f] } for (const b of boolFields) { - newRow[b] = newRow[b] === true ? 1 : 0 + row[b] = row[b] === true ? 1 : 0 } - return newRow + return row } diff --git a/lib/permission.test.js b/lib/permission.test.js index aaf2984..2d7725e 100644 --- a/lib/permission.test.js +++ b/lib/permission.test.js @@ -66,7 +66,7 @@ describe('permission', function () { assert.equal(p?.deleted, true) await Permission.delete({ id: permTestCase.id, deleted: 0 }) // restore p = await Permission.get({ id: permTestCase.id }) - assert.equal(p.deleted, false) + assert.equal(p.deleted, undefined) }) it('destroys a permission', async () => { diff --git a/lib/test/group.json b/lib/test/group.json index 1c31eaa..b0eb8bc 100644 --- a/lib/test/group.json +++ b/lib/test/group.json @@ -1,6 +1,5 @@ { "id": 4096, "parent_gid": 0, - "name": "example.com", - "deleted": false + "name": "example.com" } diff --git a/lib/test/nameserver.json b/lib/test/nameserver.json index 6888ab9..39e76ab 100644 --- a/lib/test/nameserver.json +++ b/lib/test/nameserver.json @@ -14,6 +14,5 @@ "status": "last run:03-05 15:25
last cp :09-20 12:59", "type": "NSD" }, - "ttl": 3600, - "deleted": false + "ttl": 3600 } diff --git a/lib/test/permission.json b/lib/test/permission.json index 21b10b3..dc09a85 100644 --- a/lib/test/permission.json +++ b/lib/test/permission.json @@ -3,7 +3,6 @@ "inherit": true, "name": "Test Permission", "self_write": false, - "deleted": false, "group": { "id": 4096, "create": false, "write": false, "delete": false }, "nameserver": { "usable": [], diff --git a/lib/test/user.json b/lib/test/user.json index bbc9fac..60532b9 100644 --- a/lib/test/user.json +++ b/lib/test/user.json @@ -6,6 +6,5 @@ "password": "Wh@tA-Decent#P6ssw0rd", "first_name": "Unit", "last_name": "Test", - "is_admin": false, - "deleted": false + "is_admin": false } diff --git a/lib/test/zone.json b/lib/test/zone.json index c6bdc17..3f18ca0 100644 --- a/lib/test/zone.json +++ b/lib/test/zone.json @@ -11,6 +11,5 @@ "minimum": 4, "ttl": 3600, "location": "", - "last_publish": null, - "deleted": false + "last_publish": null } diff --git a/lib/test/zone_record.json b/lib/test/zone_record.json index d102022..a5be936 100644 --- a/lib/test/zone_record.json +++ b/lib/test/zone_record.json @@ -5,4 +5,4 @@ "ttl": 86400, "type": "A", "address": "1.1.1.1" -} \ No newline at end of file +} diff --git a/lib/user.js b/lib/user.js index fe10ee4..d6fa590 100644 --- a/lib/user.js +++ b/lib/user.js @@ -66,6 +66,8 @@ class User { const u = await this.get({ id: args.id, gid: args.gid }) if (u.length === 1) return u[0].id + args = JSON.parse(JSON.stringify(args)) + if (args.password) { if (!args.pass_salt) args.pass_salt = this.generateSalt() args.password = await this.hashAuthPbkdf2(args.password, args.pass_salt) @@ -78,7 +80,9 @@ class User { } async get(args) { + args = JSON.parse(JSON.stringify(args)) if (args.deleted === undefined) args.deleted = false + const rows = await Mysql.execute( ...Mysql.select( `SELECT email @@ -97,6 +101,7 @@ class User { for (const b of boolFields) { r[b] = r[b] === 1 } + if (args.deleted === false) delete r.deleted } return rows } diff --git a/lib/user.test.js b/lib/user.test.js index 152706a..63337d7 100644 --- a/lib/user.test.js +++ b/lib/user.test.js @@ -61,7 +61,7 @@ describe('user', function () { assert.equal(u[0].deleted, true) await User.delete({ id: testCase.id, deleted: 0 }) // restore u = await User.get({ id: testCase.id }) - assert.equal(u[0].deleted, false) + assert.equal(u[0].deleted, undefined) }) }) diff --git a/lib/zone.js b/lib/zone.js index 4f89668..2f385fd 100644 --- a/lib/zone.js +++ b/lib/zone.js @@ -21,6 +21,9 @@ class Zone { } async get(args) { + args = JSON.parse(JSON.stringify(args)) + if (args.deleted === undefined) args.deleted = false + const rows = await Mysql.execute( ...Mysql.select( `SELECT nt_zone_id AS id @@ -42,13 +45,14 @@ class Zone { mapToDbColumn(args, zoneDbMap), ), ) - for (const r of rows) { + for (const row of rows) { for (const b of boolFields) { - r[b] = r[b] === 1 + row[b] = row[b] === 1 } for (const f of ['description', 'location']) { - if ([null].includes(r[f])) r[f] = '' + if ([null].includes(row[f])) row[f] = '' } + if (args.deleted === false) delete row.deleted } return rows diff --git a/lib/zone.test.js b/lib/zone.test.js index 05bab3e..01185de 100644 --- a/lib/zone.test.js +++ b/lib/zone.test.js @@ -37,12 +37,22 @@ describe('zone', function () { assert.ok(await Zone.put({ id: testCase.id, mailaddr: testCase.mailaddr })) }) - it('deletes a zone', async () => { - assert.ok(await Zone.delete({ id: testCase.id })) - let g = await Zone.get({ id: testCase.id, deleted: 1 }) - assert.equal(g[0]?.deleted, true) - await Zone.delete({ id: testCase.id, deleted: 0 }) // restore - g = await Zone.get({ id: testCase.id }) - assert.equal(g[0].deleted, false) + describe('deletes a zone', async () => { + it('can delete a zone', async () => { + assert.ok(await Zone.delete({ id: testCase.id })) + }) + it('default get does not find deleted zone', async () => { + let z = await Zone.get({ id: testCase.id }) + assert.equal(z.length, 0) + }) + it('can get the deleted zone', async () => { + let z = await Zone.get({ id: testCase.id, deleted: 1 }) + assert.equal(z[0]?.deleted, true) + }) + it('can restore the zone', async () => { + await Zone.delete({ id: testCase.id, deleted: 0 }) + let z = await Zone.get({ id: testCase.id }) + assert.equal(z.length, 1) + }) }) }) diff --git a/lib/zone_record.js b/lib/zone_record.js index f975b68..a84b28e 100644 --- a/lib/zone_record.js +++ b/lib/zone_record.js @@ -1,3 +1,5 @@ +import { typeMap } from '@nictool/dns-resource-record' + import Mysql from './mysql.js' import { mapToDbColumn } from './util.js' @@ -15,7 +17,9 @@ class ZoneRecord { if (g.length === 1) return g[0].id } - // const type = await Mysql.execute(...Mysql.select(`SELECT `)) + args = JSON.parse(JSON.stringify(args)) + args.type_id = typeMap[args.type] + delete args.type return await Mysql.execute( ...Mysql.insert(`nt_zone_record`, mapToDbColumn(args, zrDbMap)), @@ -23,6 +27,9 @@ class ZoneRecord { } async get(args) { + args = JSON.parse(JSON.stringify(args)) + if (args.deleted === undefined) args.deleted = false + const rows = await Mysql.execute( ...Mysql.select( `SELECT nt_zone_record_id AS id @@ -42,13 +49,24 @@ class ZoneRecord { mapToDbColumn(args, zrDbMap), ), ) - for (const r of rows) { + + for (const row of rows) { for (const b of boolFields) { - r[b] = r[b] === 1 + row[b] = row[b] === 1 } - for (const f of ['description', 'other', 'location']) { - if ([null].includes(r[f])) r[f] = '' + for (const f of [ + 'description', + 'other', + 'location', + 'weight', + 'priority', + 'timestamp', + ]) { + if (null === row[f]) delete row[f] } + row.type = typeMap[row.type_id] + delete row.type_id + if (args.deleted === false) delete row.deleted } return rows @@ -69,11 +87,11 @@ class ZoneRecord { } async delete(args) { - await Mysql.execute(`UPDATE nt_zone_record SET deleted=? WHERE nt_zone_record_id=?`, [ - args.deleted ?? 1, - args.id, - ]) - return true + return await Mysql.execute( + ...Mysql.update(`nt_zone_record`, `nt_zone_record_id=${args.id}`, { + deleted: args.deleted ?? 1, + }), + ) } async destroy(args) { diff --git a/lib/zone_record.test.js b/lib/zone_record.test.js index 3c61620..b73cbba 100644 --- a/lib/zone_record.test.js +++ b/lib/zone_record.test.js @@ -16,33 +16,43 @@ after(async () => { }) describe('zone_record', function () { - it('gets zone_record by id', async () => { - const g = await ZoneRecord.get({ id: testCase.id }) - delete g[0].last_modified - assert.deepEqual(g[0], testCase) + it('GET by id', async () => { + const zrs = await ZoneRecord.get({ id: testCase.id }) + delete zrs[0].last_modified + assert.deepEqual(zrs[0], testCase) }) - it('gets zone_record by name', async () => { - const g = await ZoneRecord.get({ zone_record: testCase.zone_record }) - delete g[0].last_modified - assert.deepEqual(g[0], testCase) + it('GET by name', async () => { + const zrs = await ZoneRecord.get({ zone_record: testCase.zone_record }) + delete zrs[0].last_modified + assert.deepEqual(zrs[0], testCase) }) - it('changes a zone_record', async () => { + it('PUT makes changes', async () => { + assert.ok(await ZoneRecord.put({ id: testCase.id, address: '2.2.2.2' })) + const zrs = await ZoneRecord.get({ id: testCase.id }) + assert.deepEqual(zrs[0].address, '2.2.2.2') assert.ok( - await ZoneRecord.put({ id: testCase.id, address: '2.2.2.2' }), + await ZoneRecord.put({ id: testCase.id, address: testCase.address }), ) - const ns = await ZoneRecord.get({ id: testCase.id }) - assert.deepEqual(ns[0].address, '2.2.2.2') - assert.ok(await ZoneRecord.put({ id: testCase.id, address: testCase.address })) }) - it('deletes a zone_record', async () => { - assert.ok(await ZoneRecord.delete({ id: testCase.id })) - let g = await ZoneRecord.get({ id: testCase.id, deleted: 1 }) - assert.equal(g[0]?.deleted, true) - await ZoneRecord.delete({ id: testCase.id, deleted: 0 }) // restore - g = await ZoneRecord.get({ id: testCase.id }) - assert.equal(g[0].deleted, false) + describe('DELETE', async () => { + it('deletes a zr', async () => { + assert.ok(await ZoneRecord.delete({ id: testCase.id })) + }) + it('deleted zr is not found', async () => { + let zrs = await ZoneRecord.get({ id: testCase.id }) + assert.equal(zrs.length, 0) + }) + it('deleted zr can be retrieved', async () => { + let zrs = await ZoneRecord.get({ id: testCase.id, deleted: true }) + assert.equal(zrs[0]?.deleted, true) + }) + it('deleted record can be restored', async () => { + await ZoneRecord.delete({ id: testCase.id, deleted: false }) // restore + let zrs = await ZoneRecord.get({ id: testCase.id }) + assert.equal(zrs[0].deleted, undefined) + }) }) }) diff --git a/package.json b/package.json index 8371411..59d48dd 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@hapi/hoek": "^11.0.4", "@hapi/inert": "^7.1.0", "@hapi/vision": "^7.0.3", - "@nictool/dns-resource-record": "^1.1.7", + "@nictool/dns-resource-record": "^1.2.0", "@nictool/validate": "^0.8.0", "hapi-swagger": "^17.2.1", "mysql2": "^3.9.2", From 9687bb6120c42b06d50043199374761e990864a5 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Thu, 7 Mar 2024 15:18:27 -0800 Subject: [PATCH 06/13] sql: return indicative boolean for delete --- lib/group.js | 18 ++++++++++-------- lib/mysql.js | 4 ---- lib/nameserver.js | 15 +++++++++------ lib/permission.js | 19 +++++++++++-------- lib/permission.test.js | 3 +-- lib/user.js | 10 +++++++--- lib/user.test.js | 4 ++-- lib/zone.js | 16 ++++++++++------ lib/zone_record.js | 6 ++++-- routes/user.js | 3 +-- routes/user.test.js | 6 +++--- 11 files changed, 58 insertions(+), 46 deletions(-) diff --git a/lib/group.js b/lib/group.js index 957656a..111590a 100644 --- a/lib/group.js +++ b/lib/group.js @@ -47,7 +47,6 @@ class Group { if (!args.id) return false const id = args.id delete args.id - // Mysql.debug(1) const r = await Mysql.execute( ...Mysql.update( `nt_group`, @@ -55,22 +54,25 @@ class Group { mapToDbColumn(args, groupDbMap), ), ) - // console.log(r) return r.changedRows === 1 } async delete(args) { - await Mysql.execute(`UPDATE nt_group SET deleted=? WHERE nt_group_id=?`, [ - args.deleted ?? 1, - args.id, - ]) - return true + const r = await Mysql.execute( + ...Mysql.update( + `nt_group`, + `nt_group_id=${args.id}`, + { deleted: args.deleted ?? 1 }, + ), + ) + return r.changedRows === 1 } async destroy(args) { - return await Mysql.execute( + const r = await Mysql.execute( ...Mysql.delete(`nt_group`, { nt_group_id: args.id }), ) + return r.affectedRows === 1 } } diff --git a/lib/mysql.js b/lib/mysql.js index 8754bd7..b55f35c 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -38,10 +38,6 @@ class Mysql { } if (/^(REPLACE|INSERT) INTO/.test(query)) return rows.insertId - if (/^UPDATE /.test(query)) { - console.log(rows) - console.log(fields) - } return rows } diff --git a/lib/nameserver.js b/lib/nameserver.js index d515ffd..256ed54 100644 --- a/lib/nameserver.js +++ b/lib/nameserver.js @@ -85,22 +85,25 @@ class Nameserver { mapToDbColumn(args, nsDbMap), ), ) - // console.log(r) return r.changedRows === 1 } async delete(args) { - await Mysql.execute( - `UPDATE nt_nameserver SET deleted=? WHERE nt_nameserver_id=?`, - [args.deleted ?? 1, args.id], + const r = await Mysql.execute( + ...Mysql.update( + `nt_nameserver`, + `nt_nameserver_id=${args.id}`, + { deleted: args.deleted ?? 1 }, + ), ) - return true + return r.changedRows === 1 } async destroy(args) { - return await Mysql.execute( + const r = await Mysql.execute( ...Mysql.delete(`nt_nameserver`, { nt_nameserver_id: args.id }), ) + return r.affectedRows === 1 } } diff --git a/lib/permission.js b/lib/permission.js index b4feda5..75bfd67 100644 --- a/lib/permission.js +++ b/lib/permission.js @@ -37,7 +37,6 @@ class Permission { ${getPermFields()} , p.deleted FROM nt_perm p` - // Mysql.debug(1) const rows = await Mysql.execute( ...Mysql.select(query, mapToDbColumn(args, permDbMap)), @@ -76,7 +75,6 @@ class Permission { if (!args.id) return false const id = args.id delete args.id - // Mysql.debug(1) const r = await Mysql.execute( ...Mysql.update( `nt_perm`, @@ -88,17 +86,22 @@ class Permission { } async delete(args) { - await Mysql.execute(`UPDATE nt_perm SET deleted=? WHERE nt_perm_id=?`, [ - args.deleted ?? 1, - args.id, - ]) - return true + if (!args.id) return false + const r = await Mysql.execute( + ...Mysql.update( + `nt_perm`, + `nt_perm_id=${args.id}`, + { deleted: args.deleted ?? 1 }, + ), + ) + return r.changedRows === 1 } async destroy(args) { - return await Mysql.execute( + const r = await Mysql.execute( ...Mysql.delete(`nt_perm`, mapToDbColumn(args, permDbMap)), ) + return r.affectedRows === 1 } } diff --git a/lib/permission.test.js b/lib/permission.test.js index 2d7725e..20eb8a4 100644 --- a/lib/permission.test.js +++ b/lib/permission.test.js @@ -70,8 +70,7 @@ describe('permission', function () { }) it('destroys a permission', async () => { - const r = await Permission.destroy({ id: permTestCase.id }) - assert.equal(r.affectedRows, 1) + assert.ok(await Permission.destroy({ id: permTestCase.id })) const p = await Permission.get({ id: permTestCase.id }) assert.equal(p, undefined) }) diff --git a/lib/user.js b/lib/user.js index d6fa590..1260c95 100644 --- a/lib/user.js +++ b/lib/user.js @@ -122,16 +122,20 @@ class User { async delete(args) { const r = await Mysql.execute( - `UPDATE nt_user SET deleted=? WHERE nt_user_id=?`, - [args.deleted ?? 1, args.id], + ...Mysql.update( + `nt_user`, + `nt_user_id=${args.id}`, + { deleted: args.deleted ?? 1 }, + ), ) return r.changedRows === 1 } async destroy(args) { - await Mysql.execute( + const r = await Mysql.execute( ...Mysql.delete(`nt_user`, mapToDbColumn({ id: args.id }, userDbMap)), ) + return r.affectedRows === 1 } generateSalt(length = 16) { diff --git a/lib/user.test.js b/lib/user.test.js index 63337d7..460ef63 100644 --- a/lib/user.test.js +++ b/lib/user.test.js @@ -57,9 +57,9 @@ describe('user', function () { describe('DELETE', function () { it('deletes a user', async () => { assert.ok(await User.delete({ id: testCase.id })) - let u = await User.get({ id: testCase.id, deleted: 1 }) + let u = await User.get({ id: testCase.id, deleted: true }) assert.equal(u[0].deleted, true) - await User.delete({ id: testCase.id, deleted: 0 }) // restore + await User.delete({ id: testCase.id, deleted: false }) // restore u = await User.get({ id: testCase.id }) assert.equal(u[0].deleted, undefined) }) diff --git a/lib/zone.js b/lib/zone.js index 2f385fd..2c3818a 100644 --- a/lib/zone.js +++ b/lib/zone.js @@ -73,17 +73,21 @@ class Zone { } async delete(args) { - await Mysql.execute(`UPDATE nt_zone SET deleted=? WHERE nt_zone_id=?`, [ - args.deleted ?? 1, - args.id, - ]) - return true + const r = await Mysql.execute( + ...Mysql.update( + `nt_zone`, + `nt_zone_id=${args.id}`, + { deleted: args.deleted ?? 1 }, + ), + ) + return r.changedRows === 1 } async destroy(args) { - return await Mysql.execute( + const r = await Mysql.execute( ...Mysql.delete(`nt_zone`, { nt_zone_id: args.id }), ) + return r.affectedRows === 1 } } diff --git a/lib/zone_record.js b/lib/zone_record.js index a84b28e..48b86cb 100644 --- a/lib/zone_record.js +++ b/lib/zone_record.js @@ -87,17 +87,19 @@ class ZoneRecord { } async delete(args) { - return await Mysql.execute( + const r = await Mysql.execute( ...Mysql.update(`nt_zone_record`, `nt_zone_record_id=${args.id}`, { deleted: args.deleted ?? 1, }), ) + return r.changedRows === 1 } async destroy(args) { - return await Mysql.execute( + const r = await Mysql.execute( ...Mysql.delete(`nt_zone_record`, { nt_zone_record_id: args.id }), ) + return r.affectedRows === 1 } } diff --git a/routes/user.js b/routes/user.js index dc0c541..f199d00 100644 --- a/routes/user.js +++ b/routes/user.js @@ -138,8 +138,7 @@ function UserRoutes(server) { .code(204) } - const action = request.query.destroy === 'true' ? 'destroy' : 'delete' - await User[action]({ id: users[0].id }) + await User.delete({ id: users[0].id }) delete users[0].gid diff --git a/routes/user.test.js b/routes/user.test.js index 27f541e..1602921 100644 --- a/routes/user.test.js +++ b/routes/user.test.js @@ -116,10 +116,10 @@ describe('user routes', () => { }, }) // console.log(res.result) - assert.equal(res.statusCode, 204) + assert.ok([200, 204].includes(res.statusCode)) }) - it(`GET /user/${userId2}?deleted=1`, async () => { + it(`GET /user/${userId2}?deleted=true`, async () => { const res = await server.inject({ method: 'GET', url: `/user/${userId2}?deleted=true`, @@ -128,7 +128,7 @@ describe('user routes', () => { }, }) // console.log(res.result) - assert.equal(res.statusCode, 200) + assert.ok([200, 204].includes(res.statusCode)) }) it('DELETE /session', async () => { From 85599c7a1918163898fe1969fe70e197d8cba757 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 8 Mar 2024 16:24:35 -0800 Subject: [PATCH 07/13] zr: added dbToObject and objectToDb --- CHANGELOG.md | 4 +- lib/test/zone_record.json | 2 +- lib/zone_record.js | 128 ++++++++++++++++++++++++++++++++------ test.js | 6 ++ 4 files changed, 119 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9122a57..a137077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,9 @@ - feat(lib/zone): added, with tests, fixes #22 - feat(lib/zone_record): added, with tests, fixes #23 -- feat: default get sets deleted=false +- feat: default GET sets deleted=false - group, nameserver, permission, user, zone - +- sql: return indicative boolean for delete ### [3.0.0-alpha.4] - 2024-03-05 diff --git a/lib/test/zone_record.json b/lib/test/zone_record.json index a5be936..0f1aea7 100644 --- a/lib/test/zone_record.json +++ b/lib/test/zone_record.json @@ -1,7 +1,7 @@ { "id": 4096, "zid": 4096, - "name": "a", + "owner": "a.example.com.", "ttl": 86400, "type": "A", "address": "1.1.1.1" diff --git a/lib/zone_record.js b/lib/zone_record.js index 48b86cb..b85bc75 100644 --- a/lib/zone_record.js +++ b/lib/zone_record.js @@ -1,9 +1,9 @@ -import { typeMap } from '@nictool/dns-resource-record' +import * as RR from '@nictool/dns-resource-record' import Mysql from './mysql.js' import { mapToDbColumn } from './util.js' -const zrDbMap = { id: 'nt_zone_record_id', zid: 'nt_zone_id' } +const zrDbMap = { id: 'nt_zone_record_id', zid: 'nt_zone_id', owner: 'name' } const boolFields = ['deleted'] class ZoneRecord { @@ -17,9 +17,15 @@ class ZoneRecord { if (g.length === 1) return g[0].id } - args = JSON.parse(JSON.stringify(args)) - args.type_id = typeMap[args.type] - delete args.type + try { + const zr = new RR[args.type](args) + console.log(zr) + } + catch (e) { + console.error(e.message) + } + + args = objectToDb(args) return await Mysql.execute( ...Mysql.insert(`nt_zone_record`, mapToDbColumn(args, zrDbMap)), @@ -54,22 +60,11 @@ class ZoneRecord { for (const b of boolFields) { row[b] = row[b] === 1 } - for (const f of [ - 'description', - 'other', - 'location', - 'weight', - 'priority', - 'timestamp', - ]) { - if (null === row[f]) delete row[f] - } - row.type = typeMap[row.type_id] - delete row.type_id + if (args.deleted === false) delete row.deleted } - return rows + return dbToObject(rows) } async put(args) { @@ -104,3 +99,100 @@ class ZoneRecord { } export default new ZoneRecord() + +function dbToObject(rows) { + rows = JSON.parse(JSON.stringify(rows)) + + for (const row of rows) { + row.owner = row.name + delete row.name + + row.type = RR.typeMap[row.type_id] + delete row.type_id + + for (const f of [ + 'description', + 'other', + 'location', + 'weight', + 'priority', + 'timestamp', + ]) { + if (null === row[f]) delete row[f] + } + } + + return rows +} + +function objectToDb(obj) { + obj = JSON.parse(JSON.stringify(obj)) + + switch (obj.type) { + case 'CAA': + applyMap(obj, { + weight: 'flags', + priority: 'tag', + address: 'value', + }) + break + case 'CNAME': + applyMap(obj, { address: 'cname' }) + case 'DNAME': + applyMap(obj, { address: 'target' }) + case 'DNSKEY': + applyMap(obj, { + address: 'public key', + weight: 'flags', + priority: 'protocol', + other: 'algorithm', + }) + break + case 'HINFO': + obj.name = `${obj.cpu} ${obj.os}`; delete obj.cpu; delete obj.os + obj.address = obj.description; delete obj.description + break + case 'IPSECKEY': + applyMap(obj, { + address: 'gateway', + description: 'publickey', + weight: 'precedence', + priority: 'gateway type', + other: 'algorithm', + }) + break + case 'NAPTR': + applyMap(obj, { + weight: 'order', + priority: 'preference', + description: 'replacement', + }) + obj.address = `${obj.flags} ${obj.service} ${obj.regexp}` + delete obj.flags; delete obj.service; delete obj.regexp + break + case 'SSHFP': + applyMap(obj, { + address: 'fingerprint', + weight: 'algorithm', + priority: 'fptype', + }) + break + case 'SRV': + applyMap(obj, { other: 'port' }) + break + case 'URI': + applyMap(obj, { address: 'target' }) + break + } + + obj.type_id = RR.typeMap[obj.type] + delete obj.type + + return obj +} + +function applyMap (obj, map) { + for (const [key, value] of Object.entries(map)) { + obj[key] = obj[value]; delete obj[value] + } +} diff --git a/test.js b/test.js index cba2bd4..9de6e22 100644 --- a/test.js +++ b/test.js @@ -7,9 +7,13 @@ import User from './lib/user.js' import Session from './lib/session.js' import Permission from './lib/permission.js' import Nameserver from './lib/nameserver.js' +import Zone from './lib/zone.js' +import ZoneRecord from './lib/zone_record.js' import groupCase from './lib/test/group.json' with { type: 'json' } import userCase from './lib/test/user.json' with { type: 'json' } +import zoneCase from './lib/test/zone.json' with { type: 'json' } +import zrCase from './lib/test/zone_record.json' with { type: 'json' } import groupCaseR from './routes/test/group.json' with { type: 'json' } import userCaseR from './routes/test/user.json' with { type: 'json' } import nsCaseR from './routes/test/nameserver.json' with { type: 'json' } @@ -47,6 +51,8 @@ async function setup() { // } async function teardown() { + await ZoneRecord.destroy({ id: zrCase.id }) + await Zone.destroy({ id: zoneCase.id }) await Nameserver.destroy({ id: nsCaseR.id }) await Nameserver.destroy({ id: nsCaseR.id - 1 }) await Permission.destroy({ id: userCase.id }) From f00011f07f012bdf892c2810f75f2d92d40d4f77 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 8 Mar 2024 16:26:50 -0800 Subject: [PATCH 08/13] lint --- lib/group.js | 8 +++----- lib/nameserver.js | 8 +++----- lib/permission.js | 8 +++----- lib/user.js | 8 +++----- lib/zone.js | 8 +++----- lib/zone_record.js | 25 ++++++++++++++++--------- 6 files changed, 31 insertions(+), 34 deletions(-) diff --git a/lib/group.js b/lib/group.js index 111590a..332a5de 100644 --- a/lib/group.js +++ b/lib/group.js @@ -59,11 +59,9 @@ class Group { async delete(args) { const r = await Mysql.execute( - ...Mysql.update( - `nt_group`, - `nt_group_id=${args.id}`, - { deleted: args.deleted ?? 1 }, - ), + ...Mysql.update(`nt_group`, `nt_group_id=${args.id}`, { + deleted: args.deleted ?? 1, + }), ) return r.changedRows === 1 } diff --git a/lib/nameserver.js b/lib/nameserver.js index 256ed54..f349b98 100644 --- a/lib/nameserver.js +++ b/lib/nameserver.js @@ -90,11 +90,9 @@ class Nameserver { async delete(args) { const r = await Mysql.execute( - ...Mysql.update( - `nt_nameserver`, - `nt_nameserver_id=${args.id}`, - { deleted: args.deleted ?? 1 }, - ), + ...Mysql.update(`nt_nameserver`, `nt_nameserver_id=${args.id}`, { + deleted: args.deleted ?? 1, + }), ) return r.changedRows === 1 } diff --git a/lib/permission.js b/lib/permission.js index 75bfd67..84cc41d 100644 --- a/lib/permission.js +++ b/lib/permission.js @@ -88,11 +88,9 @@ class Permission { async delete(args) { if (!args.id) return false const r = await Mysql.execute( - ...Mysql.update( - `nt_perm`, - `nt_perm_id=${args.id}`, - { deleted: args.deleted ?? 1 }, - ), + ...Mysql.update(`nt_perm`, `nt_perm_id=${args.id}`, { + deleted: args.deleted ?? 1, + }), ) return r.changedRows === 1 } diff --git a/lib/user.js b/lib/user.js index 1260c95..be2cf46 100644 --- a/lib/user.js +++ b/lib/user.js @@ -122,11 +122,9 @@ class User { async delete(args) { const r = await Mysql.execute( - ...Mysql.update( - `nt_user`, - `nt_user_id=${args.id}`, - { deleted: args.deleted ?? 1 }, - ), + ...Mysql.update(`nt_user`, `nt_user_id=${args.id}`, { + deleted: args.deleted ?? 1, + }), ) return r.changedRows === 1 } diff --git a/lib/zone.js b/lib/zone.js index 2c3818a..e19a6e1 100644 --- a/lib/zone.js +++ b/lib/zone.js @@ -74,11 +74,9 @@ class Zone { async delete(args) { const r = await Mysql.execute( - ...Mysql.update( - `nt_zone`, - `nt_zone_id=${args.id}`, - { deleted: args.deleted ?? 1 }, - ), + ...Mysql.update(`nt_zone`, `nt_zone_id=${args.id}`, { + deleted: args.deleted ?? 1, + }), ) return r.changedRows === 1 } diff --git a/lib/zone_record.js b/lib/zone_record.js index b85bc75..212a804 100644 --- a/lib/zone_record.js +++ b/lib/zone_record.js @@ -20,8 +20,7 @@ class ZoneRecord { try { const zr = new RR[args.type](args) console.log(zr) - } - catch (e) { + } catch (e) { console.error(e.message) } @@ -109,7 +108,7 @@ function dbToObject(rows) { row.type = RR.typeMap[row.type_id] delete row.type_id - + for (const f of [ 'description', 'other', @@ -138,8 +137,10 @@ function objectToDb(obj) { break case 'CNAME': applyMap(obj, { address: 'cname' }) + break case 'DNAME': applyMap(obj, { address: 'target' }) + break case 'DNSKEY': applyMap(obj, { address: 'public key', @@ -149,8 +150,11 @@ function objectToDb(obj) { }) break case 'HINFO': - obj.name = `${obj.cpu} ${obj.os}`; delete obj.cpu; delete obj.os - obj.address = obj.description; delete obj.description + obj.name = `${obj.cpu} ${obj.os}` + delete obj.cpu + delete obj.os + obj.address = obj.description + delete obj.description break case 'IPSECKEY': applyMap(obj, { @@ -159,7 +163,7 @@ function objectToDb(obj) { weight: 'precedence', priority: 'gateway type', other: 'algorithm', - }) + }) break case 'NAPTR': applyMap(obj, { @@ -168,7 +172,9 @@ function objectToDb(obj) { description: 'replacement', }) obj.address = `${obj.flags} ${obj.service} ${obj.regexp}` - delete obj.flags; delete obj.service; delete obj.regexp + delete obj.flags + delete obj.service + delete obj.regexp break case 'SSHFP': applyMap(obj, { @@ -191,8 +197,9 @@ function objectToDb(obj) { return obj } -function applyMap (obj, map) { +function applyMap(obj, map) { for (const [key, value] of Object.entries(map)) { - obj[key] = obj[value]; delete obj[value] + obj[key] = obj[value] + delete obj[value] } } From 546043d7653e49e9f4118aa7f2a3c11660b81a43 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 8 Mar 2024 16:30:14 -0800 Subject: [PATCH 09/13] user.test: use creds from json test case --- lib/user.test.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/user.test.js b/lib/user.test.js index 460ef63..bab699c 100644 --- a/lib/user.test.js +++ b/lib/user.test.js @@ -4,7 +4,7 @@ import { describe, it, after, before } from 'node:test' import User from './user.js' import Group from './group.js' -import testCase from './test/user.json' with { type: 'json' } +import userCase from './test/user.json' with { type: 'json' } import groupCase from './test/group.json' with { type: 'json' } before(async () => { @@ -26,41 +26,41 @@ function sanitize(u) { describe('user', function () { describe('POST', function () { it('creates a user', async () => { - assert.ok(await User.create(testCase)) - let users = await User.get({ id: testCase.id }) - assert.deepEqual(users[0], sanitize(testCase)) + assert.ok(await User.create(userCase)) + let users = await User.get({ id: userCase.id }) + assert.deepEqual(users[0], sanitize(userCase)) }) }) describe('GET', function () { it('finds existing user by id', async () => { - const u = await User.get({ id: testCase.id }) + const u = await User.get({ id: userCase.id }) // console.log(u) - assert.deepEqual(u[0], sanitize(testCase)) + assert.deepEqual(u[0], sanitize(userCase)) }) it('finds existing user by username', async () => { const u = await User.get({ username: 'unit-test' }) - assert.deepEqual(u[0], sanitize(testCase)) + assert.deepEqual(u[0], sanitize(userCase)) }) }) describe('PUT', function () { it('modifies existing user', async () => { - assert.ok(await User.put({ id: testCase.id, first_name: 'Untie' })) - let users = await User.get({ id: testCase.id }) + assert.ok(await User.put({ id: userCase.id, first_name: 'Untie' })) + let users = await User.get({ id: userCase.id }) assert.equal(users[0].first_name, 'Untie') - await User.put({ id: testCase.id, first_name: 'Unit' }) + await User.put({ id: userCase.id, first_name: 'Unit' }) }) }) describe('DELETE', function () { it('deletes a user', async () => { - assert.ok(await User.delete({ id: testCase.id })) - let u = await User.get({ id: testCase.id, deleted: true }) + assert.ok(await User.delete({ id: userCase.id })) + let u = await User.get({ id: userCase.id, deleted: true }) assert.equal(u[0].deleted, true) - await User.delete({ id: testCase.id, deleted: false }) // restore - u = await User.get({ id: testCase.id }) + await User.delete({ id: userCase.id, deleted: false }) // restore + u = await User.get({ id: userCase.id }) assert.equal(u[0].deleted, undefined) }) }) @@ -129,8 +129,8 @@ describe('user', function () { it('accepts a valid username & password', async () => { const u = await User.authenticate({ - username: 'unit-test@example.com', - password: 'Wh@tA-Decent#P6ssw0rd', + username: `${userCase.username}@${groupCase.name}`, + password: userCase.password, }) assert.ok(u) }) From bbc8d3332f9a4ced79c1863e9acc597d83b4bad8 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 8 Mar 2024 16:35:24 -0800 Subject: [PATCH 10/13] avoid logging auth data --- lib/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/user.js b/lib/user.js index be2cf46..208dd7b 100644 --- a/lib/user.js +++ b/lib/user.js @@ -15,7 +15,7 @@ class User { } async authenticate(authTry) { - if (this.debug) console.log(authTry) + // if (this.debug) console.log(authTry) let [username, groupName] = authTry.username.split('@') if (!groupName) groupName = this.cfg.group ?? 'NicTool' From 46190840faec7d68e80e14936ba3e6f08c1b671a Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sun, 10 Mar 2024 19:06:59 -0700 Subject: [PATCH 11/13] zr: add rr tests for many types --- lib/test/{zone_record.json => rrs/a.json} | 0 lib/test/rrs/aaaa.json | 8 ++ lib/test/rrs/caa.json | 10 ++ lib/test/rrs/cert.json | 11 ++ lib/test/rrs/cname.json | 8 ++ lib/test/rrs/dname.json | 8 ++ lib/test/rrs/dnskey.json | 11 ++ lib/test/rrs/ds.json | 11 ++ lib/test/rrs/hinfo.json | 9 ++ lib/test/rrs/mx.json | 9 ++ lib/test/rrs/ns.json | 8 ++ lib/test/rrs/spf.json | 8 ++ lib/test/rrs/srv.json | 11 ++ lib/test/rrs/sshfp.json | 10 ++ lib/test/rrs/svcb.json | 10 ++ lib/test/rrs/txt.json | 8 ++ lib/test/rrs/uri.json | 10 ++ lib/zone_record.js | 132 +++++++++++++--------- lib/zone_record.test.js | 100 ++++++++-------- 19 files changed, 284 insertions(+), 98 deletions(-) rename lib/test/{zone_record.json => rrs/a.json} (100%) create mode 100644 lib/test/rrs/aaaa.json create mode 100644 lib/test/rrs/caa.json create mode 100644 lib/test/rrs/cert.json create mode 100644 lib/test/rrs/cname.json create mode 100644 lib/test/rrs/dname.json create mode 100644 lib/test/rrs/dnskey.json create mode 100644 lib/test/rrs/ds.json create mode 100644 lib/test/rrs/hinfo.json create mode 100644 lib/test/rrs/mx.json create mode 100644 lib/test/rrs/ns.json create mode 100644 lib/test/rrs/spf.json create mode 100644 lib/test/rrs/srv.json create mode 100644 lib/test/rrs/sshfp.json create mode 100644 lib/test/rrs/svcb.json create mode 100644 lib/test/rrs/txt.json create mode 100644 lib/test/rrs/uri.json diff --git a/lib/test/zone_record.json b/lib/test/rrs/a.json similarity index 100% rename from lib/test/zone_record.json rename to lib/test/rrs/a.json diff --git a/lib/test/rrs/aaaa.json b/lib/test/rrs/aaaa.json new file mode 100644 index 0000000..6c4a2a7 --- /dev/null +++ b/lib/test/rrs/aaaa.json @@ -0,0 +1,8 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "test.example.com.", + "ttl": 86400, + "type": "AAAA", + "address": "2001:0db8:0020:000a:0000:0000:0000:0004" +} diff --git a/lib/test/rrs/caa.json b/lib/test/rrs/caa.json new file mode 100644 index 0000000..e3b32bb --- /dev/null +++ b/lib/test/rrs/caa.json @@ -0,0 +1,10 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "ns1.example.com.", + "ttl": 86400, + "type": "CAA", + "flags": 0, + "tag": "issue", + "value": "http://letsencrypt.org" +} diff --git a/lib/test/rrs/cert.json b/lib/test/rrs/cert.json new file mode 100644 index 0000000..67689ef --- /dev/null +++ b/lib/test/rrs/cert.json @@ -0,0 +1,11 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "mail.example.com.", + "ttl": 86400, + "type": "CERT", + "cert type": "PGP", + "key tag": 0, + "algorithm": 0, + "certificate": "hexidecimalkeystring1" +} diff --git a/lib/test/rrs/cname.json b/lib/test/rrs/cname.json new file mode 100644 index 0000000..38739e9 --- /dev/null +++ b/lib/test/rrs/cname.json @@ -0,0 +1,8 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "ns1.example.com.", + "ttl": 86400, + "type": "CNAME", + "cname": "ns2.example.com." +} diff --git a/lib/test/rrs/dname.json b/lib/test/rrs/dname.json new file mode 100644 index 0000000..51fd42b --- /dev/null +++ b/lib/test/rrs/dname.json @@ -0,0 +1,8 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "_tcp.example.com.", + "ttl": 86400, + "type": "DNAME", + "target": "_tcp.example.net." +} diff --git a/lib/test/rrs/dnskey.json b/lib/test/rrs/dnskey.json new file mode 100644 index 0000000..67689ef --- /dev/null +++ b/lib/test/rrs/dnskey.json @@ -0,0 +1,11 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "mail.example.com.", + "ttl": 86400, + "type": "CERT", + "cert type": "PGP", + "key tag": 0, + "algorithm": 0, + "certificate": "hexidecimalkeystring1" +} diff --git a/lib/test/rrs/ds.json b/lib/test/rrs/ds.json new file mode 100644 index 0000000..b7b730c --- /dev/null +++ b/lib/test/rrs/ds.json @@ -0,0 +1,11 @@ +{ + "id": 4096, + "zid": 4096, + "ttl": 86400, + "type": "DS", + "owner": "dskey.example.com.", + "key tag": 60485, + "algorithm": 5, + "digest type": 1, + "digest": "( 2BB183AF5F22588179A53B0A 98631FAD1A292118 )" +} diff --git a/lib/test/rrs/hinfo.json b/lib/test/rrs/hinfo.json new file mode 100644 index 0000000..b466ea5 --- /dev/null +++ b/lib/test/rrs/hinfo.json @@ -0,0 +1,9 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "server-under-my-desk.example.com.", + "ttl": 86400, + "type": "HINFO", + "cpu": "PDP-11/73", + "os": "UNIX" +} diff --git a/lib/test/rrs/mx.json b/lib/test/rrs/mx.json new file mode 100644 index 0000000..2f034e1 --- /dev/null +++ b/lib/test/rrs/mx.json @@ -0,0 +1,9 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "test.example.com.", + "ttl": 86400, + "type": "MX", + "preference": 0, + "exchange": "mail.example.com." +} diff --git a/lib/test/rrs/ns.json b/lib/test/rrs/ns.json new file mode 100644 index 0000000..a6742d4 --- /dev/null +++ b/lib/test/rrs/ns.json @@ -0,0 +1,8 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "example.com.", + "ttl": 86400, + "type": "NS", + "dname": "ns1.example.com." +} diff --git a/lib/test/rrs/spf.json b/lib/test/rrs/spf.json new file mode 100644 index 0000000..648f889 --- /dev/null +++ b/lib/test/rrs/spf.json @@ -0,0 +1,8 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "mar2024._domainkey.example.com.", + "ttl": 86400, + "type": "SPF", + "data": "v=DKIM1;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoyUzGOTSOmakY8BcxXgi0mN/nFegLBPs7aaGQUtjHfa8yUrt9T2j6GSXgdjLuG3R43WjePQv3RHzc+bwwOkdw0XDOXiztn5mhrlaflbVr5PMSTrv64/cpFQKLtgQx8Vgqp7Dh3jw13rLomRTqJFgMrMHdhIibZEa69gtuAfDqoeXo6QDSGk5JuBAeRHEH27FriHulg5ob4F4lmh7fMFVsDGkQEF6jaIVYqvRjDyyQed3R3aTJX3fpb3QrtRqvfn/LAf+3kzW58AjsERpsNCSTD2RquxbnyoR/1wdGKb8cUlD/EXvqtvpVnOzHeSeMEqex3kQI8HOGsEehWZlKd+GqwIDAQAB" +} diff --git a/lib/test/rrs/srv.json b/lib/test/rrs/srv.json new file mode 100644 index 0000000..2ed86af --- /dev/null +++ b/lib/test/rrs/srv.json @@ -0,0 +1,11 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "_imaps._tcp.example.com.", + "ttl": 86400, + "type": "SRV", + "target": "mail.example.com.", + "priority": 1, + "weight": 0, + "port": 993 +} diff --git a/lib/test/rrs/sshfp.json b/lib/test/rrs/sshfp.json new file mode 100644 index 0000000..c84adb3 --- /dev/null +++ b/lib/test/rrs/sshfp.json @@ -0,0 +1,10 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "mail.example.com.", + "ttl": 86400, + "type": "SSHFP", + "fingerprint": "ed8c6e16fdae4f633eee6a7b8f64fdd356bbb32841d535565d777014c9ea4c26", + "fptype": 0, + "algorithm": 0 +} diff --git a/lib/test/rrs/svcb.json b/lib/test/rrs/svcb.json new file mode 100644 index 0000000..7c1d1e2 --- /dev/null +++ b/lib/test/rrs/svcb.json @@ -0,0 +1,10 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "_8443._foo.api.example.com.", + "priority": 0, + "target name": "svc4.example.net.", + "params": "alpn=\"bar\" port=\"8004\" ech=\"...\"", + "ttl": 86400, + "type": "SVCB" +} diff --git a/lib/test/rrs/txt.json b/lib/test/rrs/txt.json new file mode 100644 index 0000000..21fc280 --- /dev/null +++ b/lib/test/rrs/txt.json @@ -0,0 +1,8 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "mar2024._domainkey.example.com.", + "ttl": 86400, + "type": "TXT", + "data": "v=DKIM1;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoyUzGOTSOmakY8BcxXgi0mN/nFegLBPs7aaGQUtjHfa8yUrt9T2j6GSXgdjLuG3R43WjePQv3RHzc+bwwOkdw0XDOXiztn5mhrlaflbVr5PMSTrv64/cpFQKLtgQx8Vgqp7Dh3jw13rLomRTqJFgMrMHdhIibZEa69gtuAfDqoeXo6QDSGk5JuBAeRHEH27FriHulg5ob4F4lmh7fMFVsDGkQEF6jaIVYqvRjDyyQed3R3aTJX3fpb3QrtRqvfn/LAf+3kzW58AjsERpsNCSTD2RquxbnyoR/1wdGKb8cUlD/EXvqtvpVnOzHeSeMEqex3kQI8HOGsEehWZlKd+GqwIDAQAB" +} diff --git a/lib/test/rrs/uri.json b/lib/test/rrs/uri.json new file mode 100644 index 0000000..6227fae --- /dev/null +++ b/lib/test/rrs/uri.json @@ -0,0 +1,10 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "www.example.com.", + "ttl": 86400, + "type": "URI", + "target": "www2.example.com.", + "priority": 0, + "weight": 0 +} diff --git a/lib/zone_record.js b/lib/zone_record.js index 212a804..ef17953 100644 --- a/lib/zone_record.js +++ b/lib/zone_record.js @@ -17,12 +17,7 @@ class ZoneRecord { if (g.length === 1) return g[0].id } - try { - const zr = new RR[args.type](args) - console.log(zr) - } catch (e) { - console.error(e.message) - } + new RR[args.type](args) args = objectToDb(args) @@ -109,6 +104,9 @@ function dbToObject(rows) { row.type = RR.typeMap[row.type_id] delete row.type_id + const map = getMap(row.type) + if (map) unApplyMap(row, map) + for (const f of [ 'description', 'other', @@ -127,79 +125,107 @@ function dbToObject(rows) { function objectToDb(obj) { obj = JSON.parse(JSON.stringify(obj)) - switch (obj.type) { + const map = getMap(obj.type) + if (map) applyMap(obj, map) + + obj.type_id = RR.typeMap[obj.type] + delete obj.type + + return obj +} + +function applyMap(obj, map) { + for (const [key, value] of Object.entries(map)) { + obj[key] = obj[value] + delete obj[value] + } +} + +function unApplyMap(obj, map) { + for (const [key, value] of Object.entries(map)) { + switch (value) { + case 'key tag': // DS record + case 'port': // SRV + obj[value] = parseInt(obj[key]) + break + default: + obj[value] = obj[key] + } + delete obj[key] + } +} + +function getMap(rrType) { + switch (rrType) { case 'CAA': - applyMap(obj, { + return { weight: 'flags', - priority: 'tag', + other: 'tag', address: 'value', - }) - break + } + case 'CERT': + return { + other: 'cert type', + priority: 'key tag', + weight: 'algorithm', + address: 'certificate', + } case 'CNAME': - applyMap(obj, { address: 'cname' }) - break + return { address: 'cname' } case 'DNAME': - applyMap(obj, { address: 'target' }) - break + return { address: 'target' } case 'DNSKEY': - applyMap(obj, { + return { address: 'public key', weight: 'flags', priority: 'protocol', other: 'algorithm', - }) - break + } + case 'DS': + return { + address: 'digest', + weight: 'digest type', + priority: 'algorithm', + other: 'key tag', + } case 'HINFO': - obj.name = `${obj.cpu} ${obj.os}` - delete obj.cpu - delete obj.os - obj.address = obj.description - delete obj.description - break + return { address: 'os', other: 'cpu' } case 'IPSECKEY': - applyMap(obj, { + return { address: 'gateway', description: 'publickey', weight: 'precedence', priority: 'gateway type', other: 'algorithm', - }) - break + } + case 'MX': + return { weight: 'preference', address: 'exchange' } case 'NAPTR': - applyMap(obj, { + return { weight: 'order', priority: 'preference', description: 'replacement', - }) - obj.address = `${obj.flags} ${obj.service} ${obj.regexp}` - delete obj.flags - delete obj.service - delete obj.regexp - break + } + case 'NS': + return { address: 'dname' } + case 'SPF': + return { address: 'data' } case 'SSHFP': - applyMap(obj, { + return { address: 'fingerprint', weight: 'algorithm', priority: 'fptype', - }) - break + } case 'SRV': - applyMap(obj, { other: 'port' }) - break + return { address: 'target', other: 'port' } + case 'SVCB': + return { + address: 'target name', + other: 'params', + } + case 'TXT': + return { address: 'data' } case 'URI': - applyMap(obj, { address: 'target' }) - break - } - - obj.type_id = RR.typeMap[obj.type] - delete obj.type - - return obj -} - -function applyMap(obj, map) { - for (const [key, value] of Object.entries(map)) { - obj[key] = obj[value] - delete obj[value] + return { address: 'target' } } } diff --git a/lib/zone_record.test.js b/lib/zone_record.test.js index b73cbba..4519a65 100644 --- a/lib/zone_record.test.js +++ b/lib/zone_record.test.js @@ -1,58 +1,68 @@ +import fs from 'node:fs' +import path from 'node:path' + import assert from 'node:assert/strict' import { describe, it, after, before } from 'node:test' import ZoneRecord from './zone_record.js' -import testCase from './test/zone_record.json' with { type: 'json' } - -before(async () => { - await ZoneRecord.destroy({ id: testCase.id }) - await ZoneRecord.create(testCase) -}) - after(async () => { // await ZoneRecord.destroy({ id: testCase.id }) ZoneRecord.mysql.disconnect() }) describe('zone_record', function () { - it('GET by id', async () => { - const zrs = await ZoneRecord.get({ id: testCase.id }) - delete zrs[0].last_modified - assert.deepEqual(zrs[0], testCase) - }) - - it('GET by name', async () => { - const zrs = await ZoneRecord.get({ zone_record: testCase.zone_record }) - delete zrs[0].last_modified - assert.deepEqual(zrs[0], testCase) - }) - - it('PUT makes changes', async () => { - assert.ok(await ZoneRecord.put({ id: testCase.id, address: '2.2.2.2' })) - const zrs = await ZoneRecord.get({ id: testCase.id }) - assert.deepEqual(zrs[0].address, '2.2.2.2') - assert.ok( - await ZoneRecord.put({ id: testCase.id, address: testCase.address }), - ) - }) - - describe('DELETE', async () => { - it('deletes a zr', async () => { - assert.ok(await ZoneRecord.delete({ id: testCase.id })) - }) - it('deleted zr is not found', async () => { - let zrs = await ZoneRecord.get({ id: testCase.id }) - assert.equal(zrs.length, 0) - }) - it('deleted zr can be retrieved', async () => { - let zrs = await ZoneRecord.get({ id: testCase.id, deleted: true }) - assert.equal(zrs[0]?.deleted, true) - }) - it('deleted record can be restored', async () => { - await ZoneRecord.delete({ id: testCase.id, deleted: false }) // restore - let zrs = await ZoneRecord.get({ id: testCase.id }) - assert.equal(zrs[0].deleted, undefined) + for (const rrType of fs.readdirSync('./lib/test/rrs')) { + describe(`${path.basename(rrType, '.json').toUpperCase()}`, function () { + let testCase + + before(async () => { + testCase = JSON.parse(fs.readFileSync(`./lib/test/rrs/${rrType}`)) + await ZoneRecord.destroy({ id: testCase.id }) + await ZoneRecord.create(testCase) + }) + + after(async () => { + await ZoneRecord.destroy({ id: testCase.id }) + }) + + it('GET by id', async () => { + const zrs = await ZoneRecord.get({ id: testCase.id }) + delete zrs[0].last_modified + assert.deepEqual(zrs[0], testCase) + }) + + it('GET by name', async () => { + const zrs = await ZoneRecord.get({ zone_record: testCase.zone_record }) + delete zrs[0].last_modified + assert.deepEqual(zrs[0], testCase) + }) + + it('PUT makes changes', async () => { + assert.ok(await ZoneRecord.put({ id: testCase.id, ttl: 3600 })) + const zrs = await ZoneRecord.get({ id: testCase.id }) + assert.deepEqual(zrs[0].ttl, 3600) + assert.ok(await ZoneRecord.put({ id: testCase.id, ttl: testCase.ttl })) + }) + + describe('DELETE', async () => { + it('deletes a zr', async () => { + assert.ok(await ZoneRecord.delete({ id: testCase.id })) + }) + it('deleted zr is not found', async () => { + let zrs = await ZoneRecord.get({ id: testCase.id }) + assert.equal(zrs.length, 0) + }) + it('deleted zr can be retrieved', async () => { + let zrs = await ZoneRecord.get({ id: testCase.id, deleted: true }) + assert.equal(zrs[0]?.deleted, true) + }) + it('deleted record can be restored', async () => { + await ZoneRecord.delete({ id: testCase.id, deleted: false }) // restore + let zrs = await ZoneRecord.get({ id: testCase.id }) + assert.equal(zrs[0].deleted, undefined) + }) + }) }) - }) + } }) From eb8eeca4db8f573e6eb97c99e5bff60809e5a4c4 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sun, 10 Mar 2024 23:45:19 -0700 Subject: [PATCH 12/13] feat(zrs): test every type of zone record --- lib/test/rrs/https.json | 10 +++ lib/test/rrs/ipseckey.json | 12 ++++ lib/test/rrs/key.json | 11 +++ lib/test/rrs/loc.json | 8 +++ lib/test/rrs/naptr.json | 13 ++++ lib/test/rrs/nsec.json | 9 +++ lib/test/rrs/nsec3.json | 13 ++++ lib/test/rrs/nsec3param.json | 11 +++ lib/test/rrs/nxt.json | 9 +++ lib/test/rrs/openpgpkey.json | 8 +++ lib/test/rrs/ptr.json | 8 +++ lib/test/rrs/smimea.json | 11 +++ lib/test/rrs/soa.json | 14 ++++ lib/test/rrs/tlsa.json | 11 +++ lib/zone_record.js | 130 ++++++++++++++++++++++++++++++++++- lib/zone_record.test.js | 2 + test.js | 6 +- 17 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 lib/test/rrs/https.json create mode 100644 lib/test/rrs/ipseckey.json create mode 100644 lib/test/rrs/key.json create mode 100644 lib/test/rrs/loc.json create mode 100644 lib/test/rrs/naptr.json create mode 100644 lib/test/rrs/nsec.json create mode 100644 lib/test/rrs/nsec3.json create mode 100644 lib/test/rrs/nsec3param.json create mode 100644 lib/test/rrs/nxt.json create mode 100644 lib/test/rrs/openpgpkey.json create mode 100644 lib/test/rrs/ptr.json create mode 100644 lib/test/rrs/smimea.json create mode 100644 lib/test/rrs/soa.json create mode 100644 lib/test/rrs/tlsa.json diff --git a/lib/test/rrs/https.json b/lib/test/rrs/https.json new file mode 100644 index 0000000..0d78492 --- /dev/null +++ b/lib/test/rrs/https.json @@ -0,0 +1,10 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "_8443._foo.api.example.com.", + "priority": 0, + "target name": "svc4.example.net.", + "params": "alpn=\"bar\" port=\"8004\" ech=\"...\"", + "ttl": 86400, + "type": "HTTPS" +} diff --git a/lib/test/rrs/ipseckey.json b/lib/test/rrs/ipseckey.json new file mode 100644 index 0000000..bf6cceb --- /dev/null +++ b/lib/test/rrs/ipseckey.json @@ -0,0 +1,12 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "38.2.0.192.in-addr.arpa.", + "ttl": 86400, + "type": "IPSECKEY", + "precedence": 10, + "gateway type": 1, + "algorithm": 2, + "gateway": "192.0.2.38", + "publickey": "AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ==" +} diff --git a/lib/test/rrs/key.json b/lib/test/rrs/key.json new file mode 100644 index 0000000..7452fe7 --- /dev/null +++ b/lib/test/rrs/key.json @@ -0,0 +1,11 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "example.com.", + "ttl": 86400, + "type": "KEY", + "flags": 256, + "protocol": 3, + "algorithm": 5, + "publickey": "( AQPSKmynfzW4kyBv015MUG2DeIQ3 Cbl+BBZH4b/0PY1kxkmvHjcZc8no kfzj31GajIQKY+5CptLr3buXA10h WqTkF7H6RfoRqXQeogmMHfpftf6z Mv1LyBUgia7za6ZEzOJBOztyvhjL 742iU/TpPSEDhm2SNKLijfUppn1U aNvv4w== )" +} diff --git a/lib/test/rrs/loc.json b/lib/test/rrs/loc.json new file mode 100644 index 0000000..4f3b7ea --- /dev/null +++ b/lib/test/rrs/loc.json @@ -0,0 +1,8 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "loc.home.example.com.", + "ttl": 86400, + "type": "LOC", + "address": "47 43 47 N 122 21 35 W 132m 100m 100m 2m" +} diff --git a/lib/test/rrs/naptr.json b/lib/test/rrs/naptr.json new file mode 100644 index 0000000..95e406f --- /dev/null +++ b/lib/test/rrs/naptr.json @@ -0,0 +1,13 @@ +{ + "id": 4096, + "zid": 4096, + "ttl": 86400, + "type": "NAPTR", + "owner": "cid.urn.arpa.", + "order": 100, + "preference": 10, + "flags": "", + "service": "", + "regexp": "!^urn:cid:.+@([^\\.]+\\.)(.*)$!\\x02!i", + "replacement": "." +} diff --git a/lib/test/rrs/nsec.json b/lib/test/rrs/nsec.json new file mode 100644 index 0000000..60132ff --- /dev/null +++ b/lib/test/rrs/nsec.json @@ -0,0 +1,9 @@ +{ + "id": 4096, + "zid": 4096, + "ttl": 86400, + "type": "NSEC", + "owner": "alfa.example.com.", + "next domain": "host.example.com.", + "type bit maps": "A MX RRSIG NSEC TYPE1234" +} diff --git a/lib/test/rrs/nsec3.json b/lib/test/rrs/nsec3.json new file mode 100644 index 0000000..053d2a4 --- /dev/null +++ b/lib/test/rrs/nsec3.json @@ -0,0 +1,13 @@ +{ + "id": 4096, + "zid": 4096, + "ttl": 86400, + "type": "NSEC3", + "owner": "test.example.com.", + "hash algorithm": 1, + "flags": 1, + "iterations": 12, + "salt": "aabbccdd", + "type bit maps": "A\tRRSIG", + "next hashed owner name": "2vptu5timamqttgl4luu9kg21e0aor3s" +} diff --git a/lib/test/rrs/nsec3param.json b/lib/test/rrs/nsec3param.json new file mode 100644 index 0000000..4951995 --- /dev/null +++ b/lib/test/rrs/nsec3param.json @@ -0,0 +1,11 @@ +{ + "id": 4096, + "zid": 4096, + "ttl": 86400, + "type": "NSEC3PARAM", + "owner": "test.example.com.", + "hash algorithm": 1, + "flags": 1, + "iterations": 12, + "salt": "aabbccdd" +} diff --git a/lib/test/rrs/nxt.json b/lib/test/rrs/nxt.json new file mode 100644 index 0000000..2a1a5d3 --- /dev/null +++ b/lib/test/rrs/nxt.json @@ -0,0 +1,9 @@ +{ + "id": 4096, + "zid": 4096, + "ttl": 86400, + "type": "NXT", + "owner": "big.example.com.", + "next domain": "medium.example.com.", + "type bit map": "A SIG NXT" +} diff --git a/lib/test/rrs/openpgpkey.json b/lib/test/rrs/openpgpkey.json new file mode 100644 index 0000000..0908e1f --- /dev/null +++ b/lib/test/rrs/openpgpkey.json @@ -0,0 +1,8 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "matt.example.com.", + "ttl": 86400, + "type": "OPENPGPKEY", + "public key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nxsBNBGXucegBCAD+FLaLfH4QsU2DS2gkbI3QktOg27I2LQvdEI4mvR+nHGev1KK7\n...\n0VFBNRBmWIMXRAtmrzPyNp/nD7BymAQ=\n=kezp\n-----END PGP PUBLIC KEY BLOCK-----" +} diff --git a/lib/test/rrs/ptr.json b/lib/test/rrs/ptr.json new file mode 100644 index 0000000..0902e60 --- /dev/null +++ b/lib/test/rrs/ptr.json @@ -0,0 +1,8 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "2.2.0.192.in-addr.arpa.", + "ttl": 86400, + "type": "PTR", + "dname": "dhcp.example.com." +} diff --git a/lib/test/rrs/smimea.json b/lib/test/rrs/smimea.json new file mode 100644 index 0000000..893faf1 --- /dev/null +++ b/lib/test/rrs/smimea.json @@ -0,0 +1,11 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "_443._tcp.www.example.com.", + "ttl": 86400, + "type": "SMIMEA", + "certificate usage": 0, + "selector": 0, + "matching type": 1, + "certificate association data": "( d2abde240d7cd3ee6b4b28c54df034b9 7983a1d16e8a410e4561cb106618e971 )" +} diff --git a/lib/test/rrs/soa.json b/lib/test/rrs/soa.json new file mode 100644 index 0000000..bafdf9c --- /dev/null +++ b/lib/test/rrs/soa.json @@ -0,0 +1,14 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "example.com.", + "ttl": 86400, + "type": "SOA", + "mname": "ns1.example.com.", + "rname": "matt.example.com.", + "serial": 1, + "refresh": 7200, + "retry": 3600, + "expire": 1209600, + "minimum": 3600 +} diff --git a/lib/test/rrs/tlsa.json b/lib/test/rrs/tlsa.json new file mode 100644 index 0000000..84d5a3c --- /dev/null +++ b/lib/test/rrs/tlsa.json @@ -0,0 +1,11 @@ +{ + "id": 4096, + "zid": 4096, + "owner": "_443._tcp.www.example.com.", + "ttl": 86400, + "type": "TLSA", + "certificate usage": 0, + "selector": 0, + "matching type": 1, + "certificate association data": "d2abde240d7cd3ee6b4b28c54df034b9 7983a1d16e8a410e4561cb106618e971" +} diff --git a/lib/zone_record.js b/lib/zone_record.js index ef17953..4d67e79 100644 --- a/lib/zone_record.js +++ b/lib/zone_record.js @@ -135,17 +135,77 @@ function objectToDb(obj) { } function applyMap(obj, map) { + // map dns-r-r (RFC/IETF) field names to NicTool 2.0 DB fields + for (const [key, value] of Object.entries(map)) { - obj[key] = obj[value] + if (Array.isArray(value)) { + obj[key] = `'${value.map((a) => obj[a]).join("','")}'` + for (const f of value) { + delete obj[f] + } + } else { + obj[key] = obj[value] + } + delete obj[value] } } function unApplyMap(obj, map) { + // map NicTool 2.0 DB fields to dns-r-r (RFC/IETF) field names + if (obj.type === 'NAPTR') { + const [flags, service, regexp] = obj.address.slice(1, -1).split("','") + obj.flags = flags ?? '' + obj.service = service ?? '' + obj.regexp = regexp ?? '' + delete obj.address + delete map.address + } + if (obj.type === 'NSEC3') { + const [algo, flags, iters, salt, bitmaps, next] = obj.address + .slice(1, -1) + .split("','") + obj['hash algorithm'] = /^\d+$/.test(algo) ? parseInt(algo) : algo ?? '' + obj.flags = /^\d+$/.test(flags) ? parseInt(flags) : flags ?? '' + obj.iterations = /^\d+$/.test(iters) ? parseInt(iters) : iters ?? '' + obj.salt = salt + obj['type bit maps'] = bitmaps + obj['next hashed owner name'] = next + delete obj.address + delete map.address + } + if (obj.type === 'NSEC3PARAM') { + const [algo, flags, iters, salt] = obj.address.slice(1, -1).split("','") + obj['hash algorithm'] = /^\d+$/.test(algo) ? parseInt(algo) : algo ?? '' + obj.flags = /^\d+$/.test(flags) ? parseInt(flags) : flags ?? '' + obj.iterations = /^\d+$/.test(iters) ? parseInt(iters) : iters ?? '' + obj.salt = salt + delete obj.address + delete map.address + } + if (obj.type === 'SOA') { + const [one, two, three, four, five, six, seven] = obj.address + .slice(1, -1) + .split("','") + obj.mname = one + obj.rname = two + obj.serial = parseInt(three) + obj.refresh = parseInt(four) + obj.retry = parseInt(five) + obj.expire = parseInt(six) + obj.minimum = parseInt(seven) + delete obj.address + delete map.address + } + for (const [key, value] of Object.entries(map)) { switch (value) { case 'key tag': // DS record case 'port': // SRV + case 'certificate usage': // SMIMEA + case 'algorithm': // IPSECKEY + case 'flags': // KEY + case 'matching type': // TLSA obj[value] = parseInt(obj[key]) break default: @@ -190,6 +250,11 @@ function getMap(rrType) { } case 'HINFO': return { address: 'os', other: 'cpu' } + case 'HTTPS': + return { + address: 'target name', + other: 'params', + } case 'IPSECKEY': return { address: 'gateway', @@ -198,16 +263,72 @@ function getMap(rrType) { priority: 'gateway type', other: 'algorithm', } + case 'KEY': + return { + address: 'publickey', + weight: 'protocol', + priority: 'algorithm', + other: 'flags', + } case 'MX': return { weight: 'preference', address: 'exchange' } case 'NAPTR': return { weight: 'order', priority: 'preference', + address: ['flags', 'service', 'regexp'], description: 'replacement', } case 'NS': return { address: 'dname' } + case 'NSEC': + return { + address: 'next domain', + description: 'type bit maps', + } + case 'NSEC3': + return { + address: [ + 'hash algorithm', + 'flags', + 'iterations', + 'salt', + 'type bit maps', + 'next hashed owner name', + ], + } + case 'NSEC3PARAM': + return { + address: ['hash algorithm', 'flags', 'iterations', 'salt'], + } + case 'NXT': + return { + address: 'next domain', + description: 'type bit map', + } + case 'OPENPGPKEY': + return { address: 'public key' } + case 'PTR': + return { address: 'dname' } + case 'SMIMEA': + return { + address: 'certificate association data', + weight: 'matching type', + priority: 'selector', + other: 'certificate usage', + } + case 'SOA': + return { + address: [ + 'mname', + 'rname', + 'serial', + 'refresh', + 'retry', + 'expire', + 'minimum', + ], + } case 'SPF': return { address: 'data' } case 'SSHFP': @@ -223,6 +344,13 @@ function getMap(rrType) { address: 'target name', other: 'params', } + case 'TLSA': + return { + weight: 'certificate usage', + priority: 'selector', + address: 'certificate association data', + other: 'matching type', + } case 'TXT': return { address: 'data' } case 'URI': diff --git a/lib/zone_record.test.js b/lib/zone_record.test.js index 4519a65..3184bf0 100644 --- a/lib/zone_record.test.js +++ b/lib/zone_record.test.js @@ -13,6 +13,8 @@ after(async () => { describe('zone_record', function () { for (const rrType of fs.readdirSync('./lib/test/rrs')) { + // console.log(rrType) + // if (rrType !== 'tlsa.json') continue describe(`${path.basename(rrType, '.json').toUpperCase()}`, function () { let testCase diff --git a/test.js b/test.js index 9de6e22..8d599df 100644 --- a/test.js +++ b/test.js @@ -8,12 +8,12 @@ import Session from './lib/session.js' import Permission from './lib/permission.js' import Nameserver from './lib/nameserver.js' import Zone from './lib/zone.js' -import ZoneRecord from './lib/zone_record.js' +// import ZoneRecord from './lib/zone_record.js' import groupCase from './lib/test/group.json' with { type: 'json' } import userCase from './lib/test/user.json' with { type: 'json' } import zoneCase from './lib/test/zone.json' with { type: 'json' } -import zrCase from './lib/test/zone_record.json' with { type: 'json' } +// import zrCase from './lib/test/zone_record.json' with { type: 'json' } import groupCaseR from './routes/test/group.json' with { type: 'json' } import userCaseR from './routes/test/user.json' with { type: 'json' } import nsCaseR from './routes/test/nameserver.json' with { type: 'json' } @@ -51,7 +51,7 @@ async function setup() { // } async function teardown() { - await ZoneRecord.destroy({ id: zrCase.id }) + // await ZoneRecord.destroy({ id: zrCase.id }) await Zone.destroy({ id: zoneCase.id }) await Nameserver.destroy({ id: nsCaseR.id }) await Nameserver.destroy({ id: nsCaseR.id - 1 }) From d4ababacb3dca830058789c92e1bd4a10769b8e1 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sun, 10 Mar 2024 23:46:43 -0700 Subject: [PATCH 13/13] ci --- CHANGELOG.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a137077..c0e8b08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - feat: default GET sets deleted=false - group, nameserver, permission, user, zone - sql: return indicative boolean for delete +- test(zr): added maps from NT SQL 2 to dns-rr std formats ### [3.0.0-alpha.4] - 2024-03-05 diff --git a/package.json b/package.json index 59d48dd..ecefb71 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@hapi/hoek": "^11.0.4", "@hapi/inert": "^7.1.0", "@hapi/vision": "^7.0.3", - "@nictool/dns-resource-record": "^1.2.0", + "@nictool/dns-resource-record": "^1.2.1", "@nictool/validate": "^0.8.0", "hapi-swagger": "^17.2.1", "mysql2": "^3.9.2",