diff --git a/README.md b/README.md index bfb74e3..5174f02 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,23 @@ # IcyDNS HTTP API + HTTP API for managing BIND zone files. ## Running + This application is intended to be run behind a proxy. Requires node v14+ for `fs/promises`. Also requires `bind-tools` for checking zone files. -* `npm install` -* `npm run build` -* `npm start` +- `npm install` +- `npm run build` +- `npm start` ### Environment variables -* `PORT` - server port -* `ZONEFILES` - path to zone files -* `CACHE_TTL` - internal zone cache time-to-live -* `RNDC_SERVER` - RNDC host -* `RNDC_PORT` - RNDC port -* `RNDC_KEYFILE` - location of RNDC's key file + +- `PORT` - server port +- `ZONEFILES` - path to zone files +- `CACHE_TTL` - internal zone cache time-to-live +- `RNDC_SERVER` - RNDC host +- `RNDC_PORT` - RNDC port +- `RNDC_KEYFILE` - location of RNDC's key file Zones are automatically reloaded using `rndc` after updates. If you do not have rndc configured, you will need to reload the zones manually, but the files still get updated. @@ -22,12 +25,14 @@ Zones are automatically reloaded using `rndc` after updates. If you do not have **All requests are prefixed with `/api/v1`.** Authorization is by bearer token, i.e. `-H 'Authorization: Bearer '`. `?` denotes optional parameter. -### `GET /zone/:domain` -Returns all of `:domain`'s DNS records. +### `GET /zone/{domain}` + +Returns all of `{domain}`'s DNS records. **Query:** None **Response:** + ```typescript { ttl: number; @@ -41,17 +46,20 @@ Returns all of `:domain`'s DNS records. } ``` -### `GET /zone/:domain/download` -Provides `:domain`'s records as a file. +### `GET /zone/{domain}/download` + +Provides `{domain}`'s records as a file. **Query:** None **Response:** BIND zone file -### `POST /zone/:domain` -Reloads `:domain`'s zone file. Optionally changes the zone file's TTL value. +### `POST /zone/{domain}` + +Reloads `{domain}`'s zone file. Optionally changes the zone file's TTL value. **Body:** + ```typescript { ttl?: number; @@ -59,6 +67,7 @@ Reloads `:domain`'s zone file. Optionally changes the zone file's TTL value. ``` **Response:** + ```typescript { success: boolean; @@ -67,15 +76,18 @@ Reloads `:domain`'s zone file. Optionally changes the zone file's TTL value. } ``` -### `GET /zone/records/:domain` -Returns all of `:domain`'s DNS records or performs a search based on provided query parameters. +### `GET /zone/records/{domain}` + +Returns all of `{domain}`'s DNS records or performs a search based on provided query parameters. **Query:** -* `name?` -* `type?` -* `value?` + +- `name?` +- `type?` +- `value?` **Response:** + ```typescript [ [index]: { @@ -87,10 +99,12 @@ Returns all of `:domain`'s DNS records or performs a search based on provided qu ] ``` -### `POST /zone/records/:domain` -Updates or marks for deletion a single or multiple DNS records of `:domain` at `index`. **Warning:** `setIndex` will cause your other records to shift around, so it is currently only recommended to use for a single record at a time. +### `PATCH /zone/records/{domain}` + +Updates or marks for deletion a single or multiple DNS records of `{domain}` at `index`. **Warning:** `setIndex` will cause your other records to shift around, so it is currently only recommended to use for a single record at a time. **Body:** + ```typescript { record: { @@ -105,6 +119,7 @@ Updates or marks for deletion a single or multiple DNS records of `:domain` at ` ``` **Response:** + ```typescript { success: boolean; @@ -117,10 +132,12 @@ Updates or marks for deletion a single or multiple DNS records of `:domain` at ` } ``` -### `PUT /zone/records/:domain` -Creates a single or multiple new DNS records for `:domain`. +### `POST /zone/records/{domain}` + +Creates a single or multiple new DNS records for `{domain}`. **Body:** + ```typescript { record: { @@ -133,6 +150,7 @@ Creates a single or multiple new DNS records for `:domain`. ``` **Response:** + ```typescript { success: boolean; @@ -145,10 +163,12 @@ Creates a single or multiple new DNS records for `:domain`. } ``` -### `DELETE /zone/records/:domain` -Deletes a single or multiple DNS records from `:domain` at `index`. **Warning:** Deleting an index that is not at the end of the record causes following records' indexes to shift back by one. Refresh your indexes after every addition and deletion! +### `DELETE /zone/records/{domain}` + +Deletes a single or multiple DNS records from `{domain}` at `index`. **Warning:** Deleting an index that is not at the end of the record causes following records' indexes to shift back by one. Refresh your indexes after every addition and deletion! **Body:** + ```typescript { index: number | number[]; @@ -156,6 +176,7 @@ Deletes a single or multiple DNS records from `:domain` at `index`. **Warning:** ``` **Response:** + ```typescript { success: boolean; @@ -168,10 +189,12 @@ Deletes a single or multiple DNS records from `:domain` at `index`. **Warning:** } ``` -### `POST /set-ip/:domain` -Quickly updates the `:domain`'s IP address (first occurences of `A` and `AAAA` records of `@` or `subdomain`). One of the IP addresses is taken from the request, so it's a good idea to use curl with `-4` to automatically set the IPv4 address and provide the IPv6 address with a body parameter. +### `POST /set-ip/{domain}` + +Quickly updates the `{domain}`'s IP address (first occurences of `A` and `AAAA` records of `@` or `subdomain`). One of the IP addresses is taken from the request, so it's a good idea to use curl with `-4` to automatically set the IPv4 address and provide the IPv6 address with a body parameter. **Body:** + ```typescript { ipv4?: string; @@ -182,6 +205,7 @@ Quickly updates the `:domain`'s IP address (first occurences of `A` and `AAAA` r ``` **Response:** + ```typescript { success: boolean; diff --git a/package-lock.json b/package-lock.json index bf248e2..5de34ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "icy-dyndns", + "name": "icydns", "version": "1.0.0", "lockfileVersion": 2, "requires": true, @@ -8,10 +8,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "cors": "^2.8.5", "express": "^4.17.1", "express-async-errors": "^3.1.1" }, "devDependencies": { + "@types/cors": "^2.8.10", "@types/express": "^4.17.11", "@types/node": "^15.3.0", "typescript": "^4.2.4" @@ -36,6 +38,12 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==", + "dev": true + }, "node_modules/@types/express": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", @@ -170,6 +178,18 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -401,6 +421,14 @@ "node": ">= 0.6" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -610,6 +638,12 @@ "@types/node": "*" } }, + "@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==", + "dev": true + }, "@types/express": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", @@ -726,6 +760,15 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -904,6 +947,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", diff --git a/package.json b/package.json index b1d316d..19f9a9a 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,12 @@ "author": "", "license": "ISC", "dependencies": { + "cors": "^2.8.5", "express": "^4.17.1", "express-async-errors": "^3.1.1" }, "devDependencies": { + "@types/cors": "^2.8.10", "@types/express": "^4.17.11", "@types/node": "^15.3.0", "typescript": "^4.2.4" diff --git a/src/index.ts b/src/index.ts index 1652301..9f008b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import express, { ErrorRequestHandler, NextFunction, Request, RequestHandler, Response } from 'express'; +import cors from 'cors'; import 'express-async-errors'; import path from 'path'; import { DNSCache } from './dns/cache'; @@ -20,6 +21,11 @@ const api = express.Router(); app.use(express.json()); app.enable('trust proxy'); +app.use(cors({ + origin: '*', + credentials: true +})); + const keys = new Keys(); const rndc = ReloadExecutor.fromEnvironment(); const validator = new ValidatorExecutor(); @@ -111,7 +117,7 @@ api.get('/zone/records/:domain', domainAuthorization, async (req, res) => { * forDeletion?: boolean; * }[]; */ -api.post('/zone/records/:domain', domainAuthorization, async (req, res) => { +api.patch('/zone/records/:domain', domainAuthorization, async (req, res) => { const domain = req.params.domain; let setters = req.body.record; @@ -283,7 +289,7 @@ api.delete('/zone/records/:domain', domainAuthorization, async (req, res) => { * index?: number; * }[]; */ -api.put('/zone/records/:domain', domainAuthorization, async (req, res) => { +api.post('/zone/records/:domain', domainAuthorization, async (req, res) => { const domain = req.params.domain; let setters = req.body.record; diff --git a/tsconfig.json b/tsconfig.json index d275478..9db3d2c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./dist", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */