Compare commits

..

65 Commits

Author SHA1 Message Date
Jamie Curnow
32208f3864 More Persian lang updates 2025-11-03 08:12:52 +10:00
Jamie Curnow
52ab4844dc Persian Locale 2025-11-02 22:52:43 +10:00
jc21
24216f1f2f Merge pull request #4785 from NginxProxyManager/react
v2.13.0 React UI
2025-11-02 22:48:16 +10:00
Jamie Curnow
52e528f217 Remove incomplete languages and cleanup 2025-11-02 21:28:25 +10:00
Jamie Curnow
4709f9826c Permissions polish for restricted users 2025-10-31 12:50:54 +10:00
Jamie Curnow
74a8c5d806 Fix app crash when do unautorized things 2025-10-30 15:03:01 +10:00
Jamie Curnow
82a1a86c3a Log in as user support 2025-10-30 14:45:22 +10:00
Jamie Curnow
95957a192c Re-add dns_provider_credentials to swagger schema 2025-10-30 12:24:17 +10:00
Jamie Curnow
906ce8ced2 Swagger/openapi schema mega fixes and Cypress validation/enforcement 2025-10-30 11:50:51 +10:00
Jamie Curnow
89abb9d559 Fix bugs from feedback 2025-10-29 08:48:29 +10:00
Jamie Curnow
5d6916dcf0 Tidy up
- Add help docs for most sections
- Add translations documentation
- Fix up todos
- Remove german translation
2025-10-28 15:41:11 +10:00
Jamie Curnow
0f718570d6 Use status components for true/false things 2025-10-28 14:18:52 +10:00
Jamie Curnow
fac5f2cbc5 Cert column provider tweaks 2025-10-28 11:51:27 +10:00
Jamie Curnow
3b9beaeae5 Various tweaks and backend improvements 2025-10-28 11:38:26 +10:00
Jamie Curnow
7331cb3675 Audit log tweaks for certificates 2025-10-28 10:38:05 +10:00
Jamie Curnow
678593111e Settings polish 2025-10-28 08:53:01 +10:00
Jamie Curnow
c08b1be3cb Use code edit for dns provider config dialog 2025-10-27 19:42:58 +10:00
Jamie Curnow
ca3c9aa39a Show cert expiry date in yellow when < 30 days 2025-10-27 19:34:25 +10:00
Jamie Curnow
e4e5fb3b58 Update biome 2025-10-27 19:29:14 +10:00
Jamie Curnow
83a2c79e16 Custom certificate upload 2025-10-27 19:26:33 +10:00
Jamie Curnow
0de26f2950 Certificates react work
- renewal and download
- table columns rendering
- searching
- deleting
2025-10-27 18:08:37 +10:00
Jamie Curnow
7b5c70ed35 Fix cert renewal backend bug after refactor 2025-10-27 18:04:58 +10:00
Jamie Curnow
e4d9f48870 Fix creating wrong cert type when trying dns 2025-10-27 18:04:29 +10:00
jc21
2893ffb1e4 Merge pull request #4801 from sopex/react
QoL: Link to dashboard 2.13
2025-10-27 09:52:50 +10:00
Jamie Curnow
1a117a267c Fix to postgres 17 2025-10-27 08:13:03 +10:00
Jamie Curnow
c303b69649 Update deps, the safe ones 2025-10-26 00:39:06 +10:00
Jamie Curnow
bb6c9c8daf Certificates section react work 2025-10-26 00:28:39 +10:00
Jamie Curnow
5b7013b8d5 Moved certrbot plugin list to backend
frontend doesn't include when building in react version
adds swagger for existing dns-providers endpoint
2025-10-26 00:28:03 +10:00
Konstantinos Spartalis
b8e3e594fb ;) 2025-10-17 16:00:59 +03:00
Konstantinos Spartalis
71251d2a0d :) 2025-10-17 13:51:06 +03:00
Jamie Curnow
f2b5b19a83 More react
- consolidated lang items
- proxy host paths work
2025-10-16 18:59:19 +10:00
Jamie Curnow
7af01d0fc7 Use a modal manager 2025-10-14 17:49:56 +10:00
Jamie Curnow
e6f7ae3fba Move from docker-compose to docker compose 2025-10-14 07:54:25 +10:00
Jamie Curnow
43599b4028 Access list modal polish 2025-10-09 22:14:54 +10:00
Jamie Curnow
227e818040 Wrap intl in span identifying translation 2025-10-02 23:06:51 +10:00
Jamie Curnow
fcb08d3003 Bump version 2025-10-02 08:57:46 +10:00
Jamie Curnow
d0767baafa Proxy host modal basis, other improvements 2025-10-02 08:12:37 +10:00
Jamie Curnow
abdf8866e0 Auto sorting of locale files 2025-10-02 08:12:37 +10:00
Jamie Curnow
e36c1b99a5 Redirection hosts ui 2025-10-02 08:12:37 +10:00
Jamie Curnow
9339626933 Streams polish 2025-10-02 08:12:37 +10:00
Jamie Curnow
100a7e3ff8 Streams modal 2025-10-02 08:12:37 +10:00
Jamie Curnow
4866988772 Fix stream creation with new ssl cert 2025-10-02 08:12:37 +10:00
Jamie Curnow
8884e3b261 TZ for dev db 2025-10-02 08:12:37 +10:00
Jamie Curnow
a3d17249d0 User table polish and audit log updates 2025-10-02 08:12:37 +10:00
Jamie Curnow
fc8a5e8b97 404 hosts search 2025-10-02 08:12:37 +10:00
Jamie Curnow
da68fe29ac 404 hosts polish 2025-10-02 08:12:37 +10:00
Jamie Curnow
18537b9288 404 hosts add update complete, fix certbot renewals
and remove the need for email and agreement on cert requests
2025-10-02 08:12:37 +10:00
Jamie Curnow
d85e515ab9 Dark UI for react-select 2025-10-02 08:12:37 +10:00
Jamie Curnow
94375bbc5f DNS Provider configuration 2025-10-02 08:12:37 +10:00
Jamie Curnow
54e036276a API lib cleanup, 404 hosts WIP 2025-10-02 08:12:36 +10:00
Jamie Curnow
058f49ceea Certificates react table basis 2025-10-02 08:12:33 +10:00
Jamie Curnow
efcefe0c17 Fix custom cert writes, fix schema 2025-10-02 08:12:33 +10:00
Jamie Curnow
429046f32e Audit log table and modal 2025-10-02 08:12:33 +10:00
Jamie Curnow
8ad95c5695 Set password for users 2025-10-02 08:12:31 +10:00
Jamie Curnow
038de3e5f9 Refactor from Promises to async/await 2025-10-02 08:12:28 +10:00
Jamie Curnow
1928e554fd Fix proxy hosts routes throwing errors 2025-10-02 08:12:28 +10:00
Jamie Curnow
d40e290a89 Biome update 2025-10-02 08:12:24 +10:00
Jamie Curnow
fb2708d81d Fix cypress tests following user wizard changes 2025-10-02 08:12:09 +10:00
Jamie Curnow
7a6efd8ebb User Permissions Modal 2025-10-02 08:12:09 +10:00
Jamie Curnow
0b2fa826e0 Introducing the Setup Wizard for creating the first user
- no longer setup a default
- still able to do that with env vars however
2025-10-02 08:12:05 +10:00
Jamie Curnow
6ab7198e61 User table polishing, user delete modal 2025-10-02 08:11:17 +10:00
Jamie Curnow
61a92906f3 Notification toasts, nicer loading, add new user support 2025-10-02 08:11:14 +10:00
Jamie Curnow
fadec9751e React 2025-10-02 08:10:42 +10:00
Jamie Curnow
330993f028 Convert backend to ESM
- About 5 years overdue
- Remove eslint, use bomejs instead
2025-10-02 08:10:18 +10:00
Jamie Curnow
487fa6d31b Attempt to fix frontend build for node 22
All checks were successful
Close stale issues and PRs / stale (push) Successful in 19s
replaced node-sass with sass
2025-09-10 10:38:21 +10:00
357 changed files with 15436 additions and 5492 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_Store .DS_Store
.idea .idea
.qodo
._* ._*
.vscode .vscode
certbot-help.txt certbot-help.txt

View File

@@ -1 +1 @@
2.12.6 2.13.0

44
Jenkinsfile vendored
View File

@@ -119,13 +119,13 @@ pipeline {
always { always {
// Dumps to analyze later // Dumps to analyze later
sh 'mkdir -p debug/sqlite' sh 'mkdir -p debug/sqlite'
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/sqlite/docker_fullstack.log 2>&1' sh 'docker logs $(docker compose ps --all -q fullstack) > debug/sqlite/docker_fullstack.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/sqlite/docker_stepca.log 2>&1' sh 'docker logs $(docker compose ps --all -q stepca) > debug/sqlite/docker_stepca.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/sqlite/docker_pdns.log 2>&1' sh 'docker logs $(docker compose ps --all -q pdns) > debug/sqlite/docker_pdns.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/sqlite/docker_pdns-db.log 2>&1' sh 'docker logs $(docker compose ps --all -q pdns-db) > debug/sqlite/docker_pdns-db.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/sqlite/docker_dnsrouter.log 2>&1' sh 'docker logs $(docker compose ps --all -q dnsrouter) > debug/sqlite/docker_dnsrouter.log 2>&1'
junit 'test/results/junit/*' junit 'test/results/junit/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true' sh 'docker compose down --remove-orphans --volumes -t 30 || true'
} }
unstable { unstable {
dir(path: 'test/results') { dir(path: 'test/results') {
@@ -152,13 +152,13 @@ pipeline {
always { always {
// Dumps to analyze later // Dumps to analyze later
sh 'mkdir -p debug/mysql' sh 'mkdir -p debug/mysql'
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/mysql/docker_fullstack.log 2>&1' sh 'docker logs $(docker compose ps --all -q fullstack) > debug/mysql/docker_fullstack.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/mysql/docker_stepca.log 2>&1' sh 'docker logs $(docker compose ps --all -q stepca) > debug/mysql/docker_stepca.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/mysql/docker_pdns.log 2>&1' sh 'docker logs $(docker compose ps --all -q pdns) > debug/mysql/docker_pdns.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/mysql/docker_pdns-db.log 2>&1' sh 'docker logs $(docker compose ps --all -q pdns-db) > debug/mysql/docker_pdns-db.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/mysql/docker_dnsrouter.log 2>&1' sh 'docker logs $(docker compose ps --all -q dnsrouter) > debug/mysql/docker_dnsrouter.log 2>&1'
junit 'test/results/junit/*' junit 'test/results/junit/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true' sh 'docker compose down --remove-orphans --volumes -t 30 || true'
} }
unstable { unstable {
dir(path: 'test/results') { dir(path: 'test/results') {
@@ -185,18 +185,18 @@ pipeline {
always { always {
// Dumps to analyze later // Dumps to analyze later
sh 'mkdir -p debug/postgres' sh 'mkdir -p debug/postgres'
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1' sh 'docker logs $(docker compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1' sh 'docker logs $(docker compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1' sh 'docker logs $(docker compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1' sh 'docker logs $(docker compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1' sh 'docker logs $(docker compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1' sh 'docker logs $(docker compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1' sh 'docker logs $(docker compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1' sh 'docker logs $(docker compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1' sh 'docker logs $(docke rcompose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1'
junit 'test/results/junit/*' junit 'test/results/junit/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true' sh 'docker compose down --remove-orphans --volumes -t 30 || true'
} }
unstable { unstable {
dir(path: 'test/results') { dir(path: 'test/results') {

View File

@@ -1,7 +1,7 @@
<p align="center"> <p align="center">
<img src="https://nginxproxymanager.com/github.png"> <img src="https://nginxproxymanager.com/github.png">
<br><br> <br><br>
<img src="https://img.shields.io/badge/version-2.12.6-green.svg?style=for-the-badge"> <img src="https://img.shields.io/badge/version-2.13.0-green.svg?style=for-the-badge">
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager"> <a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge"> <img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
</a> </a>
@@ -74,11 +74,7 @@ This is the bare minimum configuration required. See the [documentation](https:/
3. Bring up your stack by running 3. Bring up your stack by running
```bash ```bash
docker-compose up -d
# If using docker-compose-plugin
docker compose up -d docker compose up -d
``` ```
4. Log in to the Admin UI 4. Log in to the Admin UI
@@ -88,14 +84,6 @@ Sometimes this can take a little bit because of the entropy of keys.
[http://127.0.0.1:81](http://127.0.0.1:81) [http://127.0.0.1:81](http://127.0.0.1:81)
Default Admin User:
```
Email: admin@example.com
Password: changeme
```
Immediately after logging in with this default user you will be asked to modify your details and change your password.
## Contributing ## Contributing

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.2.3/schema.json", "$schema": "https://biomejs.dev/schemas/2.3.1/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",

View File

@@ -1,4 +1,4 @@
# certbot-dns-plugins # Certbot dns-plugins
This file contains info about available Certbot DNS plugins. This file contains info about available Certbot DNS plugins.
This only works for plugins which use the standard argument structure, so: This only works for plugins which use the standard argument structure, so:

View File

@@ -21,88 +21,74 @@ const internalAccessList = {
* @param {Object} data * @param {Object} data
* @returns {Promise} * @returns {Promise}
*/ */
create: (access, data) => { create: async (access, data) => {
return access await access.can("access_lists:create", data);
.can("access_lists:create", data) const row = await accessListModel
.then((/*access_data*/) => { .query()
return accessListModel .insertAndFetch({
.query() name: data.name,
.insertAndFetch({ satisfy_any: data.satisfy_any,
name: data.name, pass_auth: data.pass_auth,
satisfy_any: data.satisfy_any, owner_user_id: access.token.getUserId(1),
pass_auth: data.pass_auth,
owner_user_id: access.token.getUserId(1),
})
.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then(utils.omitRow(omissions()));
data.id = row.id;
const promises = []; data.id = row.id;
// Now add the items const promises = [];
data.items.map((item) => { // Items
promises.push( data.items.map((item) => {
accessListAuthModel.query().insert({ promises.push(
access_list_id: row.id, accessListAuthModel.query().insert({
username: item.username, access_list_id: row.id,
password: item.password, username: item.username,
}), password: item.password,
); }),
return true; );
}); return true;
});
// Now add the clients // Clients
if (typeof data.clients !== "undefined" && data.clients) { data.clients?.map((client) => {
data.clients.map((client) => { promises.push(
promises.push( accessListClientModel.query().insert({
accessListClientModel.query().insert({ access_list_id: row.id,
access_list_id: row.id, address: client.address,
address: client.address, directive: client.directive,
directive: client.directive, }),
}), );
); return true;
return true; });
});
}
return Promise.all(promises); await Promise.all(promises);
})
.then(() => {
// re-fetch with expansions
return internalAccessList.get(
access,
{
id: data.id,
expand: ["owner", "items", "clients", "proxy_hosts.access_list.[clients,items]"],
},
true /* <- skip masking */,
);
})
.then((row) => {
// Audit log
data.meta = _.assign({}, data.meta || {}, row.meta);
return internalAccessList // re-fetch with expansions
.build(row) const freshRow = await internalAccessList.get(
.then(() => { access,
if (Number.parseInt(row.proxy_host_count, 10)) { {
return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); id: data.id,
} expand: ["owner", "items", "clients", "proxy_hosts.access_list.[clients,items]"],
}) },
.then(() => { true // skip masking
// Add to audit log );
return internalAuditLog.add(access, {
action: "created", // Audit log
object_type: "access-list", data.meta = _.assign({}, data.meta || {}, freshRow.meta);
object_id: row.id, await internalAccessList.build(freshRow);
meta: internalAccessList.maskItems(data),
}); if (Number.parseInt(freshRow.proxy_host_count, 10)) {
}) await internalNginx.bulkGenerateConfigs("proxy_host", freshRow.proxy_hosts);
.then(() => { }
return internalAccessList.maskItems(row);
}); // Add to audit log
}); await internalAuditLog.add(access, {
action: "created",
object_type: "access-list",
object_id: freshRow.id,
meta: internalAccessList.maskItems(data),
});
return internalAccessList.maskItems(freshRow);
}, },
/** /**
@@ -113,127 +99,107 @@ const internalAccessList = {
* @param {String} [data.items] * @param {String} [data.items]
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: async (access, data) => {
return access await access.can("access_lists:update", data.id);
.can("access_lists:update", data.id) const row = await internalAccessList.get(access, { id: data.id });
.then((/*access_data*/) => { if (row.id !== data.id) {
return internalAccessList.get(access, { id: data.id }); // Sanity check that something crazy hasn't happened
}) throw new errs.InternalValidationError(
.then((row) => { `Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`,
if (row.id !== data.id) { );
// Sanity check that something crazy hasn't happened }
throw new errs.InternalValidationError(
`Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`, // patch name if specified
if (typeof data.name !== "undefined" && data.name) {
await accessListModel.query().where({ id: data.id }).patch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
});
}
// Check for items and add/update/remove them
if (typeof data.items !== "undefined" && data.items) {
const promises = [];
const itemsToKeep = [];
data.items.map((item) => {
if (item.password) {
promises.push(
accessListAuthModel.query().insert({
access_list_id: data.id,
username: item.username,
password: item.password,
}),
);
} else {
// This was supplied with an empty password, which means keep it but don't change the password
itemsToKeep.push(item.username);
}
return true;
});
const query = accessListAuthModel.query().delete().where("access_list_id", data.id);
if (itemsToKeep.length) {
query.andWhere("username", "NOT IN", itemsToKeep);
}
await query;
// Add new items
if (promises.length) {
await Promise.all(promises);
}
}
// Check for clients and add/update/remove them
if (typeof data.clients !== "undefined" && data.clients) {
const clientPromises = [];
data.clients.map((client) => {
if (client.address) {
clientPromises.push(
accessListClientModel.query().insert({
access_list_id: data.id,
address: client.address,
directive: client.directive,
}),
); );
} }
}) return true;
.then(() => {
// patch name if specified
if (typeof data.name !== "undefined" && data.name) {
return accessListModel.query().where({ id: data.id }).patch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
});
}
})
.then(() => {
// Check for items and add/update/remove them
if (typeof data.items !== "undefined" && data.items) {
const promises = [];
const items_to_keep = [];
data.items.map((item) => {
if (item.password) {
promises.push(
accessListAuthModel.query().insert({
access_list_id: data.id,
username: item.username,
password: item.password,
}),
);
} else {
// This was supplied with an empty password, which means keep it but don't change the password
items_to_keep.push(item.username);
}
return true;
});
const query = accessListAuthModel.query().delete().where("access_list_id", data.id);
if (items_to_keep.length) {
query.andWhere("username", "NOT IN", items_to_keep);
}
return query.then(() => {
// Add new items
if (promises.length) {
return Promise.all(promises);
}
});
}
})
.then(() => {
// Check for clients and add/update/remove them
if (typeof data.clients !== "undefined" && data.clients) {
const promises = [];
data.clients.map((client) => {
if (client.address) {
promises.push(
accessListClientModel.query().insert({
access_list_id: data.id,
address: client.address,
directive: client.directive,
}),
);
}
return true;
});
const query = accessListClientModel.query().delete().where("access_list_id", data.id);
return query.then(() => {
// Add new items
if (promises.length) {
return Promise.all(promises);
}
});
}
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: "updated",
object_type: "access-list",
object_id: data.id,
meta: internalAccessList.maskItems(data),
});
})
.then(() => {
// re-fetch with expansions
return internalAccessList.get(
access,
{
id: data.id,
expand: ["owner", "items", "clients", "proxy_hosts.[certificate,access_list.[clients,items]]"],
},
true /* <- skip masking */,
);
})
.then((row) => {
return internalAccessList
.build(row)
.then(() => {
if (Number.parseInt(row.proxy_host_count, 10)) {
return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts);
}
})
.then(internalNginx.reload)
.then(() => {
return internalAccessList.maskItems(row);
});
}); });
const query = accessListClientModel.query().delete().where("access_list_id", data.id);
await query;
// Add new clitens
if (clientPromises.length) {
await Promise.all(clientPromises);
}
}
// Add to audit log
await internalAuditLog.add(access, {
action: "updated",
object_type: "access-list",
object_id: data.id,
meta: internalAccessList.maskItems(data),
});
// re-fetch with expansions
const freshRow = await internalAccessList.get(
access,
{
id: data.id,
expand: ["owner", "items", "clients", "proxy_hosts.[certificate,access_list.[clients,items]]"],
},
true // skip masking
);
await internalAccessList.build(freshRow)
if (Number.parseInt(freshRow.proxy_host_count, 10)) {
await internalNginx.bulkGenerateConfigs("proxy_host", freshRow.proxy_hosts);
}
await internalNginx.reload();
return internalAccessList.maskItems(freshRow);
}, },
/** /**
@@ -242,55 +208,50 @@ const internalAccessList = {
* @param {Integer} data.id * @param {Integer} data.id
* @param {Array} [data.expand] * @param {Array} [data.expand]
* @param {Array} [data.omit] * @param {Array} [data.omit]
* @param {Boolean} [skip_masking] * @param {Boolean} [skipMasking]
* @return {Promise} * @return {Promise}
*/ */
get: (access, data, skip_masking) => { get: async (access, data, skipMasking) => {
const thisData = data || {}; const thisData = data || {};
const accessData = await access.can("access_lists:get", thisData.id)
return access const query = accessListModel
.can("access_lists:get", thisData.id) .query()
.then((accessData) => { .select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count"))
const query = accessListModel .leftJoin("proxy_host", function () {
.query() this.on("proxy_host.access_list_id", "=", "access_list.id").andOn(
.select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) "proxy_host.is_deleted",
.leftJoin("proxy_host", function () { "=",
this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( 0,
"proxy_host.is_deleted", );
"=",
0,
);
})
.where("access_list.is_deleted", 0)
.andWhere("access_list.id", thisData.id)
.groupBy("access_list.id")
.allowGraph("[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]")
.first();
if (accessData.permission_visibility !== "all") {
query.andWhere("access_list.owner_user_id", access.token.getUserId(1));
}
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
query.withGraphFetched(`[${thisData.expand.join(", ")}]`);
}
return query.then(utils.omitRow(omissions()));
}) })
.then((row) => { .where("access_list.is_deleted", 0)
let thisRow = row; .andWhere("access_list.id", thisData.id)
if (!row || !row.id) { .groupBy("access_list.id")
throw new errs.ItemNotFoundError(thisData.id); .allowGraph("[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]")
} .first();
if (!skip_masking && typeof thisRow.items !== "undefined" && thisRow.items) {
thisRow = internalAccessList.maskItems(thisRow); if (accessData.permission_visibility !== "all") {
} query.andWhere("access_list.owner_user_id", access.token.getUserId(1));
// Custom omissions }
if (typeof data.omit !== "undefined" && data.omit !== null) {
thisRow = _.omit(thisRow, data.omit); if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
} query.withGraphFetched(`[${thisData.expand.join(", ")}]`);
return thisRow; }
});
let row = await query.then(utils.omitRow(omissions()));
if (!row || !row.id) {
throw new errs.ItemNotFoundError(thisData.id);
}
if (!skipMasking && typeof row.items !== "undefined" && row.items) {
row = internalAccessList.maskItems(row);
}
// Custom omissions
if (typeof data.omit !== "undefined" && data.omit !== null) {
row = _.omit(row, data.omit);
}
return row;
}, },
/** /**
@@ -300,75 +261,64 @@ const internalAccessList = {
* @param {String} [data.reason] * @param {String} [data.reason]
* @returns {Promise} * @returns {Promise}
*/ */
delete: (access, data) => { delete: async (access, data) => {
return access await access.can("access_lists:delete", data.id);
.can("access_lists:delete", data.id) const row = await internalAccessList.get(access, {
.then(() => { id: data.id,
return internalAccessList.get(access, { id: data.id, expand: ["proxy_hosts", "items", "clients"] }); expand: ["proxy_hosts", "items", "clients"],
}) });
.then((row) => {
if (!row || !row.id) {
throw new errs.ItemNotFoundError(data.id);
}
// 1. update row to be deleted if (!row || !row.id) {
// 2. update any proxy hosts that were using it (ignoring permissions) throw new errs.ItemNotFoundError(data.id);
// 3. reconfigure those hosts }
// 4. audit log
// 1. update row to be deleted // 1. update row to be deleted
return accessListModel // 2. update any proxy hosts that were using it (ignoring permissions)
.query() // 3. reconfigure those hosts
.where("id", row.id) // 4. audit log
.patch({
is_deleted: 1,
})
.then(() => {
// 2. update any proxy hosts that were using it (ignoring permissions)
if (row.proxy_hosts) {
return proxyHostModel
.query()
.where("access_list_id", "=", row.id)
.patch({ access_list_id: 0 })
.then(() => {
// 3. reconfigure those hosts, then reload nginx
// set the access_list_id to zero for these items // 1. update row to be deleted
row.proxy_hosts.map((_val, idx) => { await accessListModel
row.proxy_hosts[idx].access_list_id = 0; .query()
return true; .where("id", row.id)
}); .patch({
is_deleted: 1,
});
return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); // 2. update any proxy hosts that were using it (ignoring permissions)
}) if (row.proxy_hosts) {
.then(() => { await proxyHostModel
return internalNginx.reload(); .query()
}); .where("access_list_id", "=", row.id)
} .patch({ access_list_id: 0 });
})
.then(() => {
// delete the htpasswd file
const htpasswd_file = internalAccessList.getFilename(row);
try { // 3. reconfigure those hosts, then reload nginx
fs.unlinkSync(htpasswd_file); // set the access_list_id to zero for these items
} catch (_err) { row.proxy_hosts.map((_val, idx) => {
// do nothing row.proxy_hosts[idx].access_list_id = 0;
}
})
.then(() => {
// 4. audit log
return internalAuditLog.add(access, {
action: "deleted",
object_type: "access-list",
object_id: row.id,
meta: _.omit(internalAccessList.maskItems(row), ["is_deleted", "proxy_hosts"]),
});
});
})
.then(() => {
return true; return true;
}); });
await internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts);
}
await internalNginx.reload();
// delete the htpasswd file
try {
fs.unlinkSync(internalAccessList.getFilename(row));
} catch (_err) {
// do nothing
}
// 4. audit log
await internalAuditLog.add(access, {
action: "deleted",
object_type: "access-list",
object_id: row.id,
meta: _.omit(internalAccessList.maskItems(row), ["is_deleted", "proxy_hosts"]),
});
return true;
}, },
/** /**
@@ -376,76 +326,73 @@ const internalAccessList = {
* *
* @param {Access} access * @param {Access} access
* @param {Array} [expand] * @param {Array} [expand]
* @param {String} [search_query] * @param {String} [searchQuery]
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: async (access, expand, searchQuery) => {
return access const accessData = await access.can("access_lists:list");
.can("access_lists:list")
.then((access_data) => {
const query = accessListModel
.query()
.select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count"))
.leftJoin("proxy_host", function () {
this.on("proxy_host.access_list_id", "=", "access_list.id").andOn(
"proxy_host.is_deleted",
"=",
0,
);
})
.where("access_list.is_deleted", 0)
.groupBy("access_list.id")
.allowGraph("[owner,items,clients]")
.orderBy("access_list.name", "ASC");
if (access_data.permission_visibility !== "all") { const query = accessListModel
query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); .query()
} .select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count"))
.leftJoin("proxy_host", function () {
// Query is used for searching this.on("proxy_host.access_list_id", "=", "access_list.id").andOn(
if (typeof search_query === "string") { "proxy_host.is_deleted",
query.where(function () { "=",
this.where("name", "like", `%${search_query}%`); 0,
}); );
}
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query.then(utils.omitRows(omissions()));
}) })
.then((rows) => { .where("access_list.is_deleted", 0)
if (rows) { .groupBy("access_list.id")
rows.map((row, idx) => { .allowGraph("[owner,items,clients]")
if (typeof row.items !== "undefined" && row.items) { .orderBy("access_list.name", "ASC");
rows[idx] = internalAccessList.maskItems(row);
}
return true;
});
}
return rows; if (accessData.permission_visibility !== "all") {
query.andWhere("access_list.owner_user_id", access.token.getUserId(1));
}
// Query is used for searching
if (typeof searchQuery === "string") {
query.where(function () {
this.where("name", "like", `%${searchQuery}%`);
}); });
}
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
const rows = await query.then(utils.omitRows(omissions()));
if (rows) {
rows.map((row, idx) => {
if (typeof row.items !== "undefined" && row.items) {
rows[idx] = internalAccessList.maskItems(row);
}
return true;
});
}
return rows;
}, },
/** /**
* Report use * Count is used in reports
* *
* @param {Integer} user_id * @param {Integer} userId
* @param {String} visibility * @param {String} visibility
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: async (userId, visibility) => {
const query = accessListModel.query().count("id as count").where("is_deleted", 0); const query = accessListModel
.query()
.count("id as count")
.where("is_deleted", 0);
if (visibility !== "all") { if (visibility !== "all") {
query.andWhere("owner_user_id", user_id); query.andWhere("owner_user_id", userId);
} }
return query.first().then((row) => { const row = await query.first();
return Number.parseInt(row.count, 10); return Number.parseInt(row.count, 10);
});
}, },
/** /**
@@ -455,20 +402,19 @@ const internalAccessList = {
maskItems: (list) => { maskItems: (list) => {
if (list && typeof list.items !== "undefined") { if (list && typeof list.items !== "undefined") {
list.items.map((val, idx) => { list.items.map((val, idx) => {
let repeat_for = 8; let repeatFor = 8;
let first_char = "*"; let firstChar = "*";
if (typeof val.password !== "undefined" && val.password) { if (typeof val.password !== "undefined" && val.password) {
repeat_for = val.password.length - 1; repeatFor = val.password.length - 1;
first_char = val.password.charAt(0); firstChar = val.password.charAt(0);
} }
list.items[idx].hint = first_char + "*".repeat(repeat_for); list.items[idx].hint = firstChar + "*".repeat(repeatFor);
list.items[idx].password = ""; list.items[idx].password = "";
return true; return true;
}); });
} }
return list; return list;
}, },
@@ -488,66 +434,55 @@ const internalAccessList = {
* @param {Array} list.items * @param {Array} list.items
* @returns {Promise} * @returns {Promise}
*/ */
build: (list) => { build: async (list) => {
logger.info(`Building Access file #${list.id} for: ${list.name}`); logger.info(`Building Access file #${list.id} for: ${list.name}`);
return new Promise((resolve, reject) => { const htpasswdFile = internalAccessList.getFilename(list);
const htpasswd_file = internalAccessList.getFilename(list);
// 1. remove any existing access file // 1. remove any existing access file
try { try {
fs.unlinkSync(htpasswd_file); fs.unlinkSync(htpasswdFile);
} catch (_err) { } catch (_err) {
// do nothing // do nothing
} }
// 2. create empty access file // 2. create empty access file
try { fs.writeFileSync(htpasswdFile, '', {encoding: 'utf8'});
fs.writeFileSync(htpasswd_file, "", { encoding: "utf8" });
resolve(htpasswd_file);
} catch (err) {
reject(err);
}
}).then((htpasswd_file) => {
// 3. generate password for each user
if (list.items.length) {
return new Promise((resolve, reject) => {
batchflow(list.items)
.sequential()
.each((_i, item, next) => {
if (typeof item.password !== "undefined" && item.password.length) {
logger.info(`Adding: ${item.username}`);
utils // 3. generate password for each user
.execFile("openssl", ["passwd", "-apr1", item.password]) if (list.items.length) {
.then((res) => { await new Promise((resolve, reject) => {
try { batchflow(list.items).sequential()
fs.appendFileSync(htpasswd_file, `${item.username}:${res}\n`, { .each((_i, item, next) => {
encoding: "utf8", if (item.password?.length) {
}); logger.info(`Adding: ${item.username}`);
} catch (err) {
reject(err); utils.execFile('openssl', ['passwd', '-apr1', item.password])
} .then((res) => {
next(); try {
}) fs.appendFileSync(htpasswdFile, `${item.username}:${res}\n`, {encoding: 'utf8'});
.catch((err) => { } catch (err) {
logger.error(err); reject(err);
next(err); }
}); next();
} })
}) .catch((err) => {
.error((err) => { logger.error(err);
logger.error(err); next(err);
reject(err); });
}) }
.end((results) => { })
logger.success(`Built Access file #${list.id} for: ${list.name}`); .error((err) => {
resolve(results); logger.error(err);
}); reject(err);
}); })
} .end((results) => {
}); logger.success(`Built Access file #${list.id} for: ${list.name}`);
}, resolve(results);
}; });
});
}
}
}
export default internalAccessList; export default internalAccessList;

View File

@@ -9,31 +9,60 @@ const internalAuditLog = {
* *
* @param {Access} access * @param {Access} access
* @param {Array} [expand] * @param {Array} [expand]
* @param {String} [search_query] * @param {String} [searchQuery]
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: async (access, expand, searchQuery) => {
return access.can("auditlog:list").then(() => { await access.can("auditlog:list");
const query = auditLogModel
.query()
.orderBy("created_on", "DESC")
.orderBy("id", "DESC")
.limit(100)
.allowGraph("[user]");
// Query is used for searching const query = auditLogModel
if (typeof search_query === "string" && search_query.length > 0) { .query()
query.where(function () { .orderBy("created_on", "DESC")
this.where(castJsonIfNeed("meta"), "like", `%${search_query}`); .orderBy("id", "DESC")
}); .limit(100)
} .allowGraph("[user]");
if (typeof expand !== "undefined" && expand !== null) { // Query is used for searching
query.withGraphFetched(`[${expand.join(", ")}]`); if (typeof searchQuery === "string" && searchQuery.length > 0) {
} query.where(function () {
this.where(castJsonIfNeed("meta"), "like", `%${searchQuery}`);
});
}
return query; if (typeof expand !== "undefined" && expand !== null) {
}); query.withGraphFetched(`[${expand.join(", ")}]`);
}
return await query;
},
/**
* @param {Access} access
* @param {Object} [data]
* @param {Integer} [data.id] Defaults to the token user
* @param {Array} [data.expand]
* @return {Promise}
*/
get: async (access, data) => {
await access.can("auditlog:list");
const query = auditLogModel
.query()
.andWhere("id", data.id)
.allowGraph("[user]")
.first();
if (typeof data.expand !== "undefined" && data.expand !== null) {
query.withGraphFetched(`[${data.expand.join(", ")}]`);
}
const row = await query;
if (!row?.id) {
throw new errs.ItemNotFoundError(data.id);
}
return row;
}, },
/** /**
@@ -50,27 +79,22 @@ const internalAuditLog = {
* @param {Object} [data.meta] * @param {Object} [data.meta]
* @returns {Promise} * @returns {Promise}
*/ */
add: (access, data) => { add: async (access, data) => {
return new Promise((resolve, reject) => { if (typeof data.user_id === "undefined" || !data.user_id) {
// Default the user id data.user_id = access.token.getUserId(1);
if (typeof data.user_id === "undefined" || !data.user_id) { }
data.user_id = access.token.getUserId(1);
}
if (typeof data.action === "undefined" || !data.action) { if (typeof data.action === "undefined" || !data.action) {
reject(new errs.InternalValidationError("Audit log entry must contain an Action")); throw new errs.InternalValidationError("Audit log entry must contain an Action");
} else { }
// Make sure at least 1 of the IDs are set and action
resolve( // Make sure at least 1 of the IDs are set and action
auditLogModel.query().insert({ return await auditLogModel.query().insert({
user_id: data.user_id, user_id: data.user_id,
action: data.action, action: data.action,
object_type: data.object_type || "", object_type: data.object_type || "",
object_id: data.object_id || 0, object_id: data.object_id || 0,
meta: data.meta || {}, meta: data.meta || {},
}),
);
}
}); });
}, },
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -18,91 +18,79 @@ const internalDeadHost = {
* @param {Object} data * @param {Object} data
* @returns {Promise} * @returns {Promise}
*/ */
create: (access, data) => { create: async (access, data) => {
const createCertificate = data.certificate_id === "new"; const createCertificate = data.certificate_id === "new";
if (createCertificate) { if (createCertificate) {
delete data.certificate_id; delete data.certificate_id;
} }
return access await access.can("dead_hosts:create", data);
.can("dead_hosts:create", data)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
const domain_name_check_promises = [];
data.domain_names.map((domain_name) => { // Get a list of the domain names and check each of them against existing records
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); const domainNameCheckPromises = [];
return true;
});
return Promise.all(domain_name_check_promises).then((check_results) => { data.domain_names.map((domain_name) => {
check_results.map((result) => { domainNameCheckPromises.push(internalHost.isHostnameTaken(domain_name));
if (result.is_taken) { return true;
throw new errs.ValidationError(`${result.hostname} is already in use`); });
}
return true;
});
});
})
.then(() => {
// At this point the domains should have been checked
data.owner_user_id = access.token.getUserId(1);
const thisData = internalHost.cleanSslHstsData(data);
// Fix for db field not having a default value await Promise.all(domainNameCheckPromises).then((check_results) => {
// for this optional field. check_results.map((result) => {
if (typeof data.advanced_config === "undefined") { if (result.is_taken) {
thisData.advanced_config = ""; throw new errs.ValidationError(`${result.hostname} is already in use`);
} }
return true;
return deadHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions()));
})
.then((row) => {
if (createCertificate) {
return internalCertificate
.createQuickCertificate(access, data)
.then((cert) => {
// update host with cert id
return internalDeadHost.update(access, {
id: row.id,
certificate_id: cert.id,
});
})
.then(() => {
return row;
});
}
return row;
})
.then((row) => {
// re-fetch with cert
return internalDeadHost.get(access, {
id: row.id,
expand: ["certificate", "owner"],
});
})
.then((row) => {
// Configure nginx
return internalNginx.configure(deadHostModel, "dead_host", row).then(() => {
return row;
});
})
.then((row) => {
data.meta = _.assign({}, data.meta || {}, row.meta);
// Add to audit log
return internalAuditLog
.add(access, {
action: "created",
object_type: "dead-host",
object_id: row.id,
meta: data,
})
.then(() => {
return row;
});
}); });
});
// At this point the domains should have been checked
data.owner_user_id = access.token.getUserId(1);
const thisData = internalHost.cleanSslHstsData(data);
// Fix for db field not having a default value
// for this optional field.
if (typeof data.advanced_config === "undefined") {
thisData.advanced_config = "";
}
const row = await deadHostModel.query()
.insertAndFetch(thisData)
.then(utils.omitRow(omissions()));
// Add to audit log
await internalAuditLog.add(access, {
action: "created",
object_type: "dead-host",
object_id: row.id,
meta: thisData,
});
if (createCertificate) {
const cert = await internalCertificate.createQuickCertificate(access, data);
// update host with cert id
await internalDeadHost.update(access, {
id: row.id,
certificate_id: cert.id,
});
}
// re-fetch with cert
const freshRow = await internalDeadHost.get(access, {
id: row.id,
expand: ["certificate", "owner"],
});
// Sanity check
if (createCertificate && !freshRow.certificate_id) {
throw new errs.InternalValidationError("The host was created but the Certificate creation failed.");
}
// Configure nginx
await internalNginx.configure(deadHostModel, "dead_host", freshRow);
return freshRow;
}, },
/** /**
@@ -111,107 +99,85 @@ const internalDeadHost = {
* @param {Number} data.id * @param {Number} data.id
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: async (access, data) => {
let thisData = data; const createCertificate = data.certificate_id === "new";
const createCertificate = thisData.certificate_id === "new";
if (createCertificate) { if (createCertificate) {
delete thisData.certificate_id; delete data.certificate_id;
} }
return access await access.can("dead_hosts:update", data.id);
.can("dead_hosts:update", thisData.id)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
const domain_name_check_promises = [];
if (typeof thisData.domain_names !== "undefined") { // Get a list of the domain names and check each of them against existing records
thisData.domain_names.map((domain_name) => { const domainNameCheckPromises = [];
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, "dead", data.id)); if (typeof data.domain_names !== "undefined") {
return true; data.domain_names.map((domainName) => {
}); domainNameCheckPromises.push(internalHost.isHostnameTaken(domainName, "dead", data.id));
return true;
return Promise.all(domain_name_check_promises).then((check_results) => {
check_results.map((result) => {
if (result.is_taken) {
throw new errs.ValidationError(`${result.hostname} is already in use`);
}
return true;
});
});
}
})
.then(() => {
return internalDeadHost.get(access, { id: thisData.id });
})
.then((row) => {
if (row.id !== thisData.id) {
// Sanity check that something crazy hasn't happened
throw new errs.InternalValidationError(
`404 Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`,
);
}
if (createCertificate) {
return internalCertificate
.createQuickCertificate(access, {
domain_names: thisData.domain_names || row.domain_names,
meta: _.assign({}, row.meta, thisData.meta),
})
.then((cert) => {
// update host with cert id
thisData.certificate_id = cert.id;
})
.then(() => {
return row;
});
}
return row;
})
.then((row) => {
// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
thisData = _.assign(
{},
{
domain_names: row.domain_names,
},
data,
);
thisData = internalHost.cleanSslHstsData(thisData, row);
return deadHostModel
.query()
.where({ id: thisData.id })
.patch(thisData)
.then((saved_row) => {
// Add to audit log
return internalAuditLog
.add(access, {
action: "updated",
object_type: "dead-host",
object_id: row.id,
meta: thisData,
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
})
.then(() => {
return internalDeadHost
.get(access, {
id: thisData.id,
expand: ["owner", "certificate"],
})
.then((row) => {
// Configure nginx
return internalNginx.configure(deadHostModel, "dead_host", row).then((new_meta) => {
row.meta = new_meta;
return _.omit(internalHost.cleanRowCertificateMeta(row), omissions());
});
});
}); });
const checkResults = await Promise.all(domainNameCheckPromises);
checkResults.map((result) => {
if (result.is_taken) {
throw new errs.ValidationError(`${result.hostname} is already in use`);
}
return true;
});
}
const row = await internalDeadHost.get(access, { id: data.id });
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new errs.InternalValidationError(
`404 Host could not be updated, IDs do not match: ${row.id} !== ${data.id}`,
);
}
if (createCertificate) {
const cert = await internalCertificate.createQuickCertificate(access, {
domain_names: data.domain_names || row.domain_names,
meta: _.assign({}, row.meta, data.meta),
});
// update host with cert id
data.certificate_id = cert.id;
}
// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
let thisData = _.assign(
{},
{
domain_names: row.domain_names,
},
data,
);
thisData = internalHost.cleanSslHstsData(thisData, row);
// do the row update
await deadHostModel
.query()
.where({id: data.id})
.patch(data);
// Add to audit log
await internalAuditLog.add(access, {
action: "updated",
object_type: "dead-host",
object_id: row.id,
meta: thisData,
});
const thisRow = await internalDeadHost
.get(access, {
id: thisData.id,
expand: ["owner", "certificate"],
});
// Configure nginx
const newMeta = await internalNginx.configure(deadHostModel, "dead_host", row);
row.meta = newMeta;
return _.omit(internalHost.cleanRowCertificateMeta(thisRow), omissions());
}, },
/** /**
@@ -222,39 +188,32 @@ const internalDeadHost = {
* @param {Array} [data.omit] * @param {Array} [data.omit]
* @return {Promise} * @return {Promise}
*/ */
get: (access, data) => { get: async (access, data) => {
const thisData = data || {}; const accessData = await access.can("dead_hosts:get", data.id);
const query = deadHostModel
.query()
.where("is_deleted", 0)
.andWhere("id", data.id)
.allowGraph("[owner,certificate]")
.first();
return access if (accessData.permission_visibility !== "all") {
.can("dead_hosts:get", thisData.id) query.andWhere("owner_user_id", access.token.getUserId(1));
.then((access_data) => { }
const query = deadHostModel
.query()
.where("is_deleted", 0)
.andWhere("id", dthisDataata.id)
.allowGraph("[owner,certificate]")
.first();
if (access_data.permission_visibility !== "all") { if (typeof data.expand !== "undefined" && data.expand !== null) {
query.andWhere("owner_user_id", access.token.getUserId(1)); query.withGraphFetched(`[${data.expand.join(", ")}]`);
} }
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { const row = await query.then(utils.omitRow(omissions()));
query.withGraphFetched(`[${data.expand.join(", ")}]`); if (!row || !row.id) {
} throw new errs.ItemNotFoundError(data.id);
}
return query.then(utils.omitRow(omissions())); // Custom omissions
}) if (typeof data.omit !== "undefined" && data.omit !== null) {
.then((row) => { return _.omit(row, data.omit);
if (!row || !row.id) { }
throw new errs.ItemNotFoundError(thisData.id); return row;
}
// Custom omissions
if (typeof thisData.omit !== "undefined" && thisData.omit !== null) {
return _.omit(row, thisData.omit);
}
return row;
});
}, },
/** /**
@@ -264,42 +223,32 @@ const internalDeadHost = {
* @param {String} [data.reason] * @param {String} [data.reason]
* @returns {Promise} * @returns {Promise}
*/ */
delete: (access, data) => { delete: async (access, data) => {
return access await access.can("dead_hosts:delete", data.id)
.can("dead_hosts:delete", data.id) const row = await internalDeadHost.get(access, { id: data.id });
.then(() => { if (!row || !row.id) {
return internalDeadHost.get(access, { id: data.id }); throw new errs.ItemNotFoundError(data.id);
}) }
.then((row) => {
if (!row || !row.id) {
throw new errs.ItemNotFoundError(data.id);
}
return deadHostModel await deadHostModel
.query() .query()
.where("id", row.id) .where("id", row.id)
.patch({ .patch({
is_deleted: 1, is_deleted: 1,
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig("dead_host", row).then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: "deleted",
object_type: "dead-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
.then(() => {
return true;
}); });
// Delete Nginx Config
await internalNginx.deleteConfig("dead_host", row);
await internalNginx.reload();
// Add to audit log
await internalAuditLog.add(access, {
action: "deleted",
object_type: "dead-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
return true;
}, },
/** /**
@@ -309,48 +258,39 @@ const internalDeadHost = {
* @param {String} [data.reason] * @param {String} [data.reason]
* @returns {Promise} * @returns {Promise}
*/ */
enable: (access, data) => { enable: async (access, data) => {
return access await access.can("dead_hosts:update", data.id)
.can("dead_hosts:update", data.id) const row = await internalDeadHost.get(access, {
.then(() => { id: data.id,
return internalDeadHost.get(access, { expand: ["certificate", "owner"],
id: data.id, });
expand: ["certificate", "owner"], if (!row || !row.id) {
}); throw new errs.ItemNotFoundError(data.id);
}) }
.then((row) => { if (row.enabled) {
if (!row || !row.id) { throw new errs.ValidationError("Host is already enabled");
throw new errs.ItemNotFoundError(data.id); }
}
if (row.enabled) {
throw new errs.ValidationError("Host is already enabled");
}
row.enabled = 1; row.enabled = 1;
return deadHostModel await deadHostModel
.query() .query()
.where("id", row.id) .where("id", row.id)
.patch({ .patch({
enabled: 1, enabled: 1,
})
.then(() => {
// Configure nginx
return internalNginx.configure(deadHostModel, "dead_host", row);
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: "enabled",
object_type: "dead-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
.then(() => {
return true;
}); });
// Configure nginx
await internalNginx.configure(deadHostModel, "dead_host", row);
// Add to audit log
await internalAuditLog.add(access, {
action: "enabled",
object_type: "dead-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
return true;
}, },
/** /**
@@ -360,47 +300,37 @@ const internalDeadHost = {
* @param {String} [data.reason] * @param {String} [data.reason]
* @returns {Promise} * @returns {Promise}
*/ */
disable: (access, data) => { disable: async (access, data) => {
return access await access.can("dead_hosts:update", data.id)
.can("dead_hosts:update", data.id) const row = await internalDeadHost.get(access, { id: data.id });
.then(() => { if (!row || !row.id) {
return internalDeadHost.get(access, { id: data.id }); throw new errs.ItemNotFoundError(data.id);
}) }
.then((row) => { if (!row.enabled) {
if (!row || !row.id) { throw new errs.ValidationError("Host is already disabled");
throw new errs.ItemNotFoundError(data.id); }
}
if (!row.enabled) {
throw new errs.ValidationError("Host is already disabled");
}
row.enabled = 0; row.enabled = 0;
return deadHostModel await deadHostModel
.query() .query()
.where("id", row.id) .where("id", row.id)
.patch({ .patch({
enabled: 0, enabled: 0,
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig("dead_host", row).then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: "disabled",
object_type: "dead-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
.then(() => {
return true;
}); });
// Delete Nginx Config
await internalNginx.deleteConfig("dead_host", row);
await internalNginx.reload();
// Add to audit log
await internalAuditLog.add(access, {
action: "disabled",
object_type: "dead-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
return true;
}, },
/** /**
@@ -408,44 +338,38 @@ const internalDeadHost = {
* *
* @param {Access} access * @param {Access} access
* @param {Array} [expand] * @param {Array} [expand]
* @param {String} [search_query] * @param {String} [searchQuery]
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: async (access, expand, searchQuery) => {
return access const accessData = await access.can("dead_hosts:list")
.can("dead_hosts:list") const query = deadHostModel
.then((access_data) => { .query()
const query = deadHostModel .where("is_deleted", 0)
.query() .groupBy("id")
.where("is_deleted", 0) .allowGraph("[owner,certificate]")
.groupBy("id") .orderBy(castJsonIfNeed("domain_names"), "ASC");
.allowGraph("[owner,certificate]")
.orderBy(castJsonIfNeed("domain_names"), "ASC");
if (access_data.permission_visibility !== "all") { if (accessData.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1)); query.andWhere("owner_user_id", access.token.getUserId(1));
} }
// Query is used for searching // Query is used for searching
if (typeof search_query === "string" && search_query.length > 0) { if (typeof searchQuery === "string" && searchQuery.length > 0) {
query.where(function () { query.where(function () {
this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`); this.where(castJsonIfNeed("domain_names"), "like", `%${searchQuery}%`);
});
}
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
}); });
}
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
const rows = await query.then(utils.omitRows(omissions()));
if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
}, },
/** /**
@@ -455,16 +379,15 @@ const internalDeadHost = {
* @param {String} visibility * @param {String} visibility
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: async (user_id, visibility) => {
const query = deadHostModel.query().count("id as count").where("is_deleted", 0); const query = deadHostModel.query().count("id as count").where("is_deleted", 0);
if (visibility !== "all") { if (visibility !== "all") {
query.andWhere("owner_user_id", user_id); query.andWhere("owner_user_id", user_id);
} }
return query.first().then((row) => { const row = await query.first();
return Number.parseInt(row.count, 10); return Number.parseInt(row.count, 10);
});
}, },
}; };

View File

@@ -65,50 +65,33 @@ const internalHost = {
}, },
/** /**
* This returns all the host types with any domain listed in the provided domain_names array. * This returns all the host types with any domain listed in the provided domainNames array.
* This is used by the certificates to temporarily disable any host that is using the domain * This is used by the certificates to temporarily disable any host that is using the domain
* *
* @param {Array} domain_names * @param {Array} domainNames
* @returns {Promise} * @returns {Promise}
*/ */
getHostsWithDomains: (domain_names) => { getHostsWithDomains: async (domainNames) => {
const promises = [ const responseObject = {
proxyHostModel.query().where("is_deleted", 0), total_count: 0,
redirectionHostModel.query().where("is_deleted", 0), dead_hosts: [],
deadHostModel.query().where("is_deleted", 0), proxy_hosts: [],
]; redirection_hosts: [],
};
return Promise.all(promises).then((promises_results) => { const proxyRes = await proxyHostModel.query().where("is_deleted", 0);
const response_object = { responseObject.proxy_hosts = internalHost._getHostsWithDomains(proxyRes, domainNames);
total_count: 0, responseObject.total_count += responseObject.proxy_hosts.length;
dead_hosts: [],
proxy_hosts: [],
redirection_hosts: [],
};
if (promises_results[0]) { const redirRes = await redirectionHostModel.query().where("is_deleted", 0);
// Proxy Hosts responseObject.redirection_hosts = internalHost._getHostsWithDomains(redirRes, domainNames);
response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names); responseObject.total_count += responseObject.redirection_hosts.length;
response_object.total_count += response_object.proxy_hosts.length;
}
if (promises_results[1]) { const deadRes = await deadHostModel.query().where("is_deleted", 0);
// Redirection Hosts responseObject.dead_hosts = internalHost._getHostsWithDomains(deadRes, domainNames);
response_object.redirection_hosts = internalHost._getHostsWithDomains( responseObject.total_count += responseObject.dead_hosts.length;
promises_results[1],
domain_names,
);
response_object.total_count += response_object.redirection_hosts.length;
}
if (promises_results[2]) { return responseObject;
// Dead Hosts
response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names);
response_object.total_count += response_object.dead_hosts.length;
}
return response_object;
});
}, },
/** /**

View File

@@ -301,8 +301,11 @@ const internalNginx = {
* @param {String} filename * @param {String} filename
*/ */
deleteFile: (filename) => { deleteFile: (filename) => {
logger.debug(`Deleting file: ${filename}`); if (!fs.existsSync(filename)) {
return;
}
try { try {
logger.debug(`Deleting file: ${filename}`);
fs.unlinkSync(filename); fs.unlinkSync(filename);
} catch (err) { } catch (err) {
logger.debug("Could not delete file:", JSON.stringify(err, null, 2)); logger.debug("Could not delete file:", JSON.stringify(err, null, 2));
@@ -378,14 +381,14 @@ const internalNginx = {
}, },
/** /**
* @param {String} host_type * @param {String} hostType
* @param {Array} hosts * @param {Array} hosts
* @returns {Promise} * @returns {Promise}
*/ */
bulkGenerateConfigs: (host_type, hosts) => { bulkGenerateConfigs: (hostType, hosts) => {
const promises = []; const promises = [];
hosts.map((host) => { hosts.map((host) => {
promises.push(internalNginx.generateConfig(host_type, host)); promises.push(internalNginx.generateConfig(hostType, host));
return true; return true;
}); });

View File

@@ -420,41 +420,35 @@ const internalProxyHost = {
* @param {String} [search_query] * @param {String} [search_query]
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: async (access, expand, searchQuery) => {
return access const accessData = await access.can("proxy_hosts:list");
.can("proxy_hosts:list") const query = proxyHostModel
.then((access_data) => { .query()
const query = proxyHostModel .where("is_deleted", 0)
.query() .groupBy("id")
.where("is_deleted", 0) .allowGraph("[owner,access_list,certificate]")
.groupBy("id") .orderBy(castJsonIfNeed("domain_names"), "ASC");
.allowGraph("[owner,access_list,certificate]")
.orderBy(castJsonIfNeed("domain_names"), "ASC");
if (access_data.permission_visibility !== "all") { if (accessData.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1)); query.andWhere("owner_user_id", access.token.getUserId(1));
} }
// Query is used for searching // Query is used for searching
if (typeof search_query === "string" && search_query.length > 0) { if (typeof searchQuery === "string" && searchQuery.length > 0) {
query.where(function () { query.where(function () {
this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`); this.where(castJsonIfNeed("domain_names"), "like", `%${searchQuery}%`);
});
}
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
}); });
}
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
const rows = await query.then(utils.omitRows(omissions()));
if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
}, },
/** /**

View File

@@ -348,7 +348,7 @@ const internalStream = {
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: "disabled", action: "disabled",
object_type: "stream-host", object_type: "stream",
object_id: row.id, object_id: row.id,
meta: _.omit(row, omissions()), meta: _.omit(row, omissions()),
}); });

View File

@@ -9,7 +9,7 @@ import internalAuditLog from "./audit-log.js";
import internalToken from "./token.js"; import internalToken from "./token.js";
const omissions = () => { const omissions = () => {
return ["is_deleted"]; return ["is_deleted", "permissions.id", "permissions.user_id", "permissions.created_on", "permissions.modified_on"];
}; };
const DEFAULT_AVATAR = gravatar.url("admin@example.com", { default: "mm" }); const DEFAULT_AVATAR = gravatar.url("admin@example.com", { default: "mm" });
@@ -131,7 +131,7 @@ const internalUser = {
action: "updated", action: "updated",
object_type: "user", object_type: "user",
object_id: user.id, object_id: user.id,
meta: data, meta: { ...data, id: user.id, name: user.name },
}) })
.then(() => { .then(() => {
return user; return user;
@@ -250,6 +250,14 @@ const internalUser = {
}); });
}, },
deleteAll: async () => {
await userModel
.query()
.patch({
is_deleted: 1,
});
},
/** /**
* This will only count the users * This will only count the users
* *

View File

@@ -107,7 +107,6 @@ export default function (tokenString) {
} }
const tokenUserId = tokenData.attrs.id ? tokenData.attrs.id : 0; const tokenUserId = tokenData.attrs.id ? tokenData.attrs.id : 0;
let query;
if (typeof objectCache[objectType] !== "undefined") { if (typeof objectCache[objectType] !== "undefined") {
objects = objectCache[objectType]; objects = objectCache[objectType];
@@ -120,15 +119,19 @@ export default function (tokenString) {
// Proxy Hosts // Proxy Hosts
case "proxy_hosts": { case "proxy_hosts": {
query = proxyHostModel.query().select("id").andWhere("is_deleted", 0); const query = proxyHostModel
.query()
.select("id")
.andWhere("is_deleted", 0);
if (permissions.visibility === "user") { if (permissions.visibility === "user") {
query.andWhere("owner_user_id", tokenUserId); query.andWhere("owner_user_id", tokenUserId);
} }
const rows = await query(); const rows = await query;
objects = []; objects = [];
_.forEach(rows, (ruleRow) => { _.forEach(rows, (ruleRow) => {
result.push(ruleRow.id); objects.push(ruleRow.id);
}); });
// enum should not have less than 1 item // enum should not have less than 1 item
@@ -141,7 +144,6 @@ export default function (tokenString) {
objectCache[objectType] = objects; objectCache[objectType] = objects;
} }
} }
return objects; return objects;
}; };
@@ -263,7 +265,7 @@ export default function (tokenString) {
schemas: [roleSchema, permsSchema, objectSchema, permissionSchema], schemas: [roleSchema, permsSchema, objectSchema, permissionSchema],
}); });
const valid = ajv.validate("permissions", dataSchema); const valid = await ajv.validate("permissions", dataSchema);
return valid && dataSchema[permission]; return valid && dataSchema[permission];
} catch (err) { } catch (err) {
err.permission = permission; err.permission = permission;

View File

@@ -1,54 +1,14 @@
import batchflow from "batchflow"; import batchflow from "batchflow";
import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" };
import { certbot as logger } from "../logger.js"; import { certbot as logger } from "../logger.js";
import errs from "./error.js"; import errs from "./error.js";
import utils from "./utils.js"; import utils from "./utils.js";
const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0-9]+)+')"; const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0-9]+)+')";
/**
* @param {array} pluginKeys
*/
const installPlugins = async (pluginKeys) => {
let hasErrors = false;
return new Promise((resolve, reject) => {
if (pluginKeys.length === 0) {
resolve();
return;
}
batchflow(pluginKeys)
.sequential()
.each((_i, pluginKey, next) => {
certbot
.installPlugin(pluginKey)
.then(() => {
next();
})
.catch((err) => {
hasErrors = true;
next(err);
});
})
.error((err) => {
logger.error(err.message);
})
.end(() => {
if (hasErrors) {
reject(
new errs.CommandError("Some plugins failed to install. Please check the logs above", 1),
);
} else {
resolve();
}
});
});
};
/** /**
* Installs a cerbot plugin given the key for the object from * Installs a cerbot plugin given the key for the object from
* ../global/certbot-dns-plugins.json * ../certbot/dns-plugins.json
* *
* @param {string} pluginKey * @param {string} pluginKey
* @returns {Object} * @returns {Object}
@@ -84,4 +44,43 @@ const installPlugin = async (pluginKey) => {
}); });
}; };
/**
* @param {array} pluginKeys
*/
const installPlugins = async (pluginKeys) => {
let hasErrors = false;
return new Promise((resolve, reject) => {
if (pluginKeys.length === 0) {
resolve();
return;
}
batchflow(pluginKeys)
.sequential()
.each((_i, pluginKey, next) => {
installPlugin(pluginKey)
.then(() => {
next();
})
.catch((err) => {
hasErrors = true;
next(err);
});
})
.error((err) => {
logger.error(err.message);
})
.end(() => {
if (hasErrors) {
reject(
new errs.CommandError("Some plugins failed to install. Please check the logs above", 1),
);
} else {
resolve();
}
});
});
};
export { installPlugins, installPlugin }; export { installPlugins, installPlugin };

View File

@@ -199,6 +199,13 @@ const isPostgres = () => {
*/ */
const isDebugMode = () => !!process.env.DEBUG; const isDebugMode = () => !!process.env.DEBUG;
/**
* Are we running in CI?
*
* @returns {boolean}
*/
const isCI = () => process.env.CI === 'true' && process.env.DEBUG === 'true';
/** /**
* Returns a public key * Returns a public key
* *
@@ -234,4 +241,4 @@ const useLetsencryptServer = () => {
return null; return null;
}; };
export { configHas, configGet, isSqlite, isMysql, isPostgres, isDebugMode, getPrivateKey, getPublicKey, useLetsencryptStaging, useLetsencryptServer }; export { isCI, configHas, configGet, isSqlite, isMysql, isPostgres, isDebugMode, getPrivateKey, getPublicKey, useLetsencryptStaging, useLetsencryptServer };

View File

@@ -14,7 +14,10 @@ const errs = {
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name; this.name = this.constructor.name;
this.previous = previous; this.previous = previous;
this.message = `Item Not Found - ${id}`; this.message = "Not Found";
if (id) {
this.message = `Not Found - ${id}`;
}
this.public = true; this.public = true;
this.status = 404; this.status = 404;
}, },

View File

@@ -24,16 +24,21 @@ const apiValidator = async (schema, payload /*, description*/) => {
throw new errs.ValidationError("Payload is undefined"); throw new errs.ValidationError("Payload is undefined");
} }
const validate = ajv.compile(schema); const validate = ajv.compile(schema);
const valid = validate(payload); const valid = validate(payload);
if (valid && !validate.errors) { if (valid && !validate.errors) {
return payload; return payload;
} }
const message = ajv.errorsText(validate.errors); const message = ajv.errorsText(validate.errors);
const err = new errs.ValidationError(message); const err = new errs.ValidationError(message);
err.debug = [validate.errors, payload]; err.debug = {validationErrors: validate.errors, payload};
throw err; throw err;
}; };

View File

@@ -8,6 +8,7 @@ import deadHostModel from "./dead_host.js";
import now from "./now_helper.js"; import now from "./now_helper.js";
import proxyHostModel from "./proxy_host.js"; import proxyHostModel from "./proxy_host.js";
import redirectionHostModel from "./redirection_host.js"; import redirectionHostModel from "./redirection_host.js";
import streamModel from "./stream.js";
import userModel from "./user.js"; import userModel from "./user.js";
Model.knex(db); Model.knex(db);
@@ -114,6 +115,17 @@ class Certificate extends Model {
qb.where("redirection_host.is_deleted", 0); qb.where("redirection_host.is_deleted", 0);
}, },
}, },
streams: {
relation: Model.HasManyRelation,
modelClass: streamModel,
join: {
from: "certificate.id",
to: "stream.certificate_id",
},
modify: (qb) => {
qb.where("stream.is_deleted", 0);
},
},
}; };
} }
} }

View File

@@ -128,7 +128,7 @@ export default () => {
*/ */
getUserId: (defaultValue) => { getUserId: (defaultValue) => {
const attrs = self.get("attrs"); const attrs = self.get("attrs");
if (attrs && typeof attrs.id !== "undefined" && attrs.id) { if (attrs?.id) {
return attrs.id; return attrs.id;
} }

View File

@@ -3,5 +3,5 @@
"ignore": [ "ignore": [
"data" "data"
], ],
"ext": "js json ejs" "ext": "js json ejs cjs"
} }

View File

@@ -20,25 +20,25 @@
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
"compression": "^1.7.4", "compression": "^1.7.4",
"express": "^4.20.0", "express": "^4.20.0",
"express-fileupload": "^1.1.9", "express-fileupload": "^1.5.2",
"gravatar": "^1.8.0", "gravatar": "^1.8.2",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.2",
"knex": "2.4.2", "knex": "2.4.2",
"liquidjs": "10.6.1", "liquidjs": "10.6.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.30.1",
"mysql2": "^3.11.1", "mysql2": "^3.15.3",
"node-rsa": "^1.0.8", "node-rsa": "^1.1.1",
"objection": "3.0.1", "objection": "3.0.1",
"path": "^0.12.7", "path": "^0.12.7",
"pg": "^8.13.1", "pg": "^8.16.3",
"signale": "1.4.0", "signale": "1.4.0",
"sqlite3": "5.1.6", "sqlite3": "^5.1.7",
"temp-write": "^4.0.0" "temp-write": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@apidevtools/swagger-parser": "^10.1.0", "@apidevtools/swagger-parser": "^10.1.0",
"@biomejs/biome": "^2.2.3", "@biomejs/biome": "^2.3.2",
"chalk": "4.1.2", "chalk": "4.1.2",
"nodemon": "^2.0.2" "nodemon": "^2.0.2"
}, },

View File

@@ -2,6 +2,7 @@ import express from "express";
import internalAuditLog from "../internal/audit-log.js"; import internalAuditLog from "../internal/audit-log.js";
import jwtdecode from "../lib/express/jwt-decode.js"; import jwtdecode from "../lib/express/jwt-decode.js";
import validator from "../lib/validator/index.js"; import validator from "../lib/validator/index.js";
import { express as logger } from "../logger.js";
const router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
@@ -24,31 +25,83 @@ router
* *
* Retrieve all logs * Retrieve all logs
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: "common#/properties/expand", expand: {
}, $ref: "common#/properties/expand",
query: { },
$ref: "common#/properties/query", query: {
$ref: "common#/properties/query",
},
}, },
}, },
}, {
{ expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, query: typeof req.query.query === "string" ? req.query.query : null,
query: typeof req.query.query === "string" ? req.query.query : null, },
}, );
) const rows = await internalAuditLog.getAll(res.locals.access, data.expand, data.query);
.then((data) => { res.status(200).send(rows);
return internalAuditLog.getAll(res.locals.access, data.expand, data.query); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((rows) => { next(err);
res.status(200).send(rows); }
}) });
.catch(next);
/**
* Specific audit log entry
*
* /api/audit-log/123
*/
router
.route("/:event_id")
.options((_, res) => {
res.sendStatus(204);
})
.all(jwtdecode())
/**
* GET /api/audit-log/123
*
* Retrieve a specific entry
*/
.get(async (req, res, next) => {
try {
const data = await validator(
{
required: ["event_id"],
additionalProperties: false,
properties: {
event_id: {
$ref: "common#/properties/id",
},
expand: {
$ref: "common#/properties/expand",
},
},
},
{
event_id: req.params.event_id,
expand:
typeof req.query.expand === "string"
? req.query.expand.split(",")
: null,
},
);
const item = await internalAuditLog.get(res.locals.access, {
id: data.event_id,
expand: data.expand,
});
res.status(200).send(item);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; export default router;

View File

@@ -3,6 +3,7 @@ import internalAccessList from "../../internal/access-list.js";
import jwtdecode from "../../lib/express/jwt-decode.js"; import jwtdecode from "../../lib/express/jwt-decode.js";
import apiValidator from "../../lib/validator/api.js"; import apiValidator from "../../lib/validator/api.js";
import validator from "../../lib/validator/index.js"; import validator from "../../lib/validator/index.js";
import { express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js"; import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ const router = express.Router({
@@ -26,31 +27,31 @@ router
* *
* Retrieve all access-lists * Retrieve all access-lists
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: "common#/properties/expand", expand: {
}, $ref: "common#/properties/expand",
query: { },
$ref: "common#/properties/query", query: {
$ref: "common#/properties/query",
},
}, },
}, },
}, {
{ expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, query: typeof req.query.query === "string" ? req.query.query : null,
query: typeof req.query.query === "string" ? req.query.query : null, },
}, );
) const rows = await internalAccessList.getAll(res.locals.access, data.expand, data.query);
.then((data) => { res.status(200).send(rows);
return internalAccessList.getAll(res.locals.access, data.expand, data.query); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((rows) => { next(err);
res.status(200).send(rows); }
})
.catch(next);
}) })
/** /**
@@ -58,15 +59,15 @@ router
* *
* Create a new access-list * Create a new access-list
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/access-lists", "post"), req.body) try {
.then((payload) => { const payload = await apiValidator(getValidationSchema("/nginx/access-lists", "post"), req.body);
return internalAccessList.create(res.locals.access, payload); const result = await internalAccessList.create(res.locals.access, payload);
}) res.status(201).send(result);
.then((result) => { } catch (err) {
res.status(201).send(result); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
}) next(err);
.catch(next); }
}); });
/** /**
@@ -86,35 +87,35 @@ router
* *
* Retrieve a specific access-list * Retrieve a specific access-list
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
required: ["list_id"], {
additionalProperties: false, required: ["list_id"],
properties: { additionalProperties: false,
list_id: { properties: {
$ref: "common#/properties/id", list_id: {
}, $ref: "common#/properties/id",
expand: { },
$ref: "common#/properties/expand", expand: {
$ref: "common#/properties/expand",
},
}, },
}, },
}, {
{ list_id: req.params.list_id,
list_id: req.params.list_id, expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, },
}, );
) const row = await internalAccessList.get(res.locals.access, {
.then((data) => { id: Number.parseInt(data.list_id, 10),
return internalAccessList.get(res.locals.access, { expand: data.expand,
id: Number.parseInt(data.list_id, 10), });
expand: data.expand, res.status(200).send(row);
}); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((row) => { next(err);
res.status(200).send(row); }
})
.catch(next);
}) })
/** /**
@@ -122,16 +123,16 @@ router
* *
* Update and existing access-list * Update and existing access-list
*/ */
.put((req, res, next) => { .put(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/access-lists/{listID}", "put"), req.body) try {
.then((payload) => { const payload = await apiValidator(getValidationSchema("/nginx/access-lists/{listID}", "put"), req.body);
payload.id = Number.parseInt(req.params.list_id, 10); payload.id = Number.parseInt(req.params.list_id, 10);
return internalAccessList.update(res.locals.access, payload); const result = await internalAccessList.update(res.locals.access, payload);
}) res.status(200).send(result);
.then((result) => { } catch (err) {
res.status(200).send(result); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
}) next(err);
.catch(next); }
}) })
/** /**
@@ -139,13 +140,16 @@ router
* *
* Delete and existing access-list * Delete and existing access-list
*/ */
.delete((req, res, next) => { .delete(async (req, res, next) => {
internalAccessList try {
.delete(res.locals.access, { id: Number.parseInt(req.params.list_id, 10) }) const result = await internalAccessList.delete(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.list_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; export default router;

View File

@@ -1,9 +1,11 @@
import express from "express"; import express from "express";
import dnsPlugins from "../../certbot/dns-plugins.json" with { type: "json" };
import internalCertificate from "../../internal/certificate.js"; import internalCertificate from "../../internal/certificate.js";
import errs from "../../lib/error.js"; import errs from "../../lib/error.js";
import jwtdecode from "../../lib/express/jwt-decode.js"; import jwtdecode from "../../lib/express/jwt-decode.js";
import apiValidator from "../../lib/validator/api.js"; import apiValidator from "../../lib/validator/api.js";
import validator from "../../lib/validator/index.js"; import validator from "../../lib/validator/index.js";
import { express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js"; import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ const router = express.Router({
@@ -27,31 +29,38 @@ router
* *
* Retrieve all certificates * Retrieve all certificates
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: "common#/properties/expand", expand: {
}, $ref: "common#/properties/expand",
query: { },
$ref: "common#/properties/query", query: {
$ref: "common#/properties/query",
},
}, },
}, },
}, {
{ expand:
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, typeof req.query.expand === "string"
query: typeof req.query.query === "string" ? req.query.query : null, ? req.query.expand.split(",")
}, : null,
) query: typeof req.query.query === "string" ? req.query.query : null,
.then((data) => { },
return internalCertificate.getAll(res.locals.access, data.expand, data.query); );
}) const rows = await internalCertificate.getAll(
.then((rows) => { res.locals.access,
res.status(200).send(rows); data.expand,
}) data.query,
.catch(next); );
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}) })
/** /**
@@ -59,16 +68,56 @@ router
* *
* Create a new certificate * Create a new certificate
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/certificates", "post"), req.body) try {
.then((payload) => { const payload = await apiValidator(
req.setTimeout(900000); // 15 minutes timeout getValidationSchema("/nginx/certificates", "post"),
return internalCertificate.create(res.locals.access, payload); req.body,
}) );
.then((result) => { req.setTimeout(900000); // 15 minutes timeout
res.status(201).send(result); const result = await internalCertificate.create(
}) res.locals.access,
.catch(next); payload,
);
res.status(201).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
/**
* /api/nginx/certificates/dns-providers
*/
router
.route("/dns-providers")
.options((_, res) => {
res.sendStatus(204);
})
.all(jwtdecode())
/**
* GET /api/nginx/certificates/dns-providers
*
* Get list of all supported DNS providers
*/
.get(async (req, res, next) => {
try {
if (!res.locals.access.token.getUserId()) {
throw new errs.PermissionError("Login required");
}
const clean = Object.keys(dnsPlugins).map((key) => ({
id: key,
name: dnsPlugins[key].name,
credentials: dnsPlugins[key].credentials,
}));
clean.sort((a, b) => a.name.localeCompare(b.name));
res.status(200).send(clean);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -84,22 +133,61 @@ router
.all(jwtdecode()) .all(jwtdecode())
/** /**
* GET /api/nginx/certificates/test-http * POST /api/nginx/certificates/test-http
* *
* Test HTTP challenge for domains * Test HTTP challenge for domains
*/ */
.get((req, res, next) => { .post(async (req, res, next) => {
if (req.query.domains === undefined) { try {
next(new errs.ValidationError("Domains are required as query parameters")); const payload = await apiValidator(
getValidationSchema("/nginx/certificates/test-http", "post"),
req.body,
);
req.setTimeout(60000); // 1 minute timeout
const result = await internalCertificate.testHttpsChallenge(
res.locals.access,
payload,
);
res.status(200).send(result);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
/**
* Validate Certs before saving
*
* /api/nginx/certificates/validate
*/
router
.route("/validate")
.options((_, res) => {
res.sendStatus(204);
})
.all(jwtdecode())
/**
* POST /api/nginx/certificates/validate
*
* Validate certificates
*/
.post(async (req, res, next) => {
if (!req.files) {
res.status(400).send({ error: "No files were uploaded" });
return; return;
} }
internalCertificate try {
.testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains)) const result = await internalCertificate.validate({
.then((result) => { files: req.files,
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -119,35 +207,38 @@ router
* *
* Retrieve a specific certificate * Retrieve a specific certificate
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
required: ["certificate_id"], {
additionalProperties: false, required: ["certificate_id"],
properties: { additionalProperties: false,
certificate_id: { properties: {
$ref: "common#/properties/id", certificate_id: {
}, $ref: "common#/properties/id",
expand: { },
$ref: "common#/properties/expand", expand: {
$ref: "common#/properties/expand",
},
}, },
}, },
}, {
{ certificate_id: req.params.certificate_id,
certificate_id: req.params.certificate_id, expand:
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, typeof req.query.expand === "string"
}, ? req.query.expand.split(",")
) : null,
.then((data) => { },
return internalCertificate.get(res.locals.access, { );
id: Number.parseInt(data.certificate_id, 10), const row = await internalCertificate.get(res.locals.access, {
expand: data.expand, id: Number.parseInt(data.certificate_id, 10),
}); expand: data.expand,
}) });
.then((row) => { res.status(200).send(row);
res.status(200).send(row); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.catch(next); next(err);
}
}) })
/** /**
@@ -155,13 +246,16 @@ router
* *
* Update and existing certificate * Update and existing certificate
*/ */
.delete((req, res, next) => { .delete(async (req, res, next) => {
internalCertificate try {
.delete(res.locals.access, { id: Number.parseInt(req.params.certificate_id, 10) }) const result = await internalCertificate.delete(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.certificate_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -181,19 +275,21 @@ router
* *
* Upload certificates * Upload certificates
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
if (!req.files) { if (!req.files) {
res.status(400).send({ error: "No files were uploaded" }); res.status(400).send({ error: "No files were uploaded" });
} else { return;
internalCertificate }
.upload(res.locals.access, {
id: Number.parseInt(req.params.certificate_id, 10), try {
files: req.files, const result = await internalCertificate.upload(res.locals.access, {
}) id: Number.parseInt(req.params.certificate_id, 10),
.then((result) => { files: req.files,
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}); });
@@ -214,16 +310,17 @@ router
* *
* Renew certificate * Renew certificate
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
req.setTimeout(900000); // 15 minutes timeout req.setTimeout(900000); // 15 minutes timeout
internalCertificate try {
.renew(res.locals.access, { const result = await internalCertificate.renew(res.locals.access, {
id: Number.parseInt(req.params.certificate_id, 10), id: Number.parseInt(req.params.certificate_id, 10),
}) });
.then((result) => { res.status(200).send(result);
res.status(200).send(result); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.catch(next); next(err);
}
}); });
/** /**
@@ -243,46 +340,15 @@ router
* *
* Renew certificate * Renew certificate
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
internalCertificate try {
.download(res.locals.access, { const result = await internalCertificate.download(res.locals.access, {
id: Number.parseInt(req.params.certificate_id, 10), id: Number.parseInt(req.params.certificate_id, 10),
}) });
.then((result) => { res.status(200).download(result.fileName);
res.status(200).download(result.fileName); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.catch(next); next(err);
});
/**
* Validate Certs before saving
*
* /api/nginx/certificates/validate
*/
router
.route("/validate")
.options((_, res) => {
res.sendStatus(204);
})
.all(jwtdecode())
/**
* POST /api/nginx/certificates/validate
*
* Validate certificates
*/
.post((req, res, next) => {
if (!req.files) {
res.status(400).send({ error: "No files were uploaded" });
} else {
internalCertificate
.validate({
files: req.files,
})
.then((result) => {
res.status(200).send(result);
})
.catch(next);
} }
}); });

View File

@@ -3,6 +3,7 @@ import internalDeadHost from "../../internal/dead-host.js";
import jwtdecode from "../../lib/express/jwt-decode.js"; import jwtdecode from "../../lib/express/jwt-decode.js";
import apiValidator from "../../lib/validator/api.js"; import apiValidator from "../../lib/validator/api.js";
import validator from "../../lib/validator/index.js"; import validator from "../../lib/validator/index.js";
import { express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js"; import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ const router = express.Router({
@@ -26,31 +27,31 @@ router
* *
* Retrieve all dead-hosts * Retrieve all dead-hosts
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: "common#/properties/expand", expand: {
}, $ref: "common#/properties/expand",
query: { },
$ref: "common#/properties/query", query: {
$ref: "common#/properties/query",
},
}, },
}, },
}, {
{ expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, query: typeof req.query.query === "string" ? req.query.query : null,
query: typeof req.query.query === "string" ? req.query.query : null, },
}, );
) const rows = await internalDeadHost.getAll(res.locals.access, data.expand, data.query);
.then((data) => { res.status(200).send(rows);
return internalDeadHost.getAll(res.locals.access, data.expand, data.query); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((rows) => { next(err);
res.status(200).send(rows); }
})
.catch(next);
}) })
/** /**
@@ -58,15 +59,15 @@ router
* *
* Create a new dead-host * Create a new dead-host
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/dead-hosts", "post"), req.body) try {
.then((payload) => { const payload = await apiValidator(getValidationSchema("/nginx/dead-hosts", "post"), req.body);
return internalDeadHost.create(res.locals.access, payload); const result = await internalDeadHost.create(res.locals.access, payload);
}) res.status(201).send(result);
.then((result) => { } catch (err) {
res.status(201).send(result); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
}) next(err);
.catch(next); }
}); });
/** /**
@@ -86,66 +87,69 @@ router
* *
* Retrieve a specific dead-host * Retrieve a specific dead-host
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
required: ["host_id"], {
additionalProperties: false, required: ["host_id"],
properties: { additionalProperties: false,
host_id: { properties: {
$ref: "common#/properties/id", host_id: {
}, $ref: "common#/properties/id",
expand: { },
$ref: "common#/properties/expand", expand: {
$ref: "common#/properties/expand",
},
}, },
}, },
}, {
{ host_id: req.params.host_id,
host_id: req.params.host_id, expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, },
}, );
) const row = await internalDeadHost.get(res.locals.access, {
.then((data) => { id: Number.parseInt(data.host_id, 10),
return internalDeadHost.get(res.locals.access, { expand: data.expand,
id: Number.parseInt(data.host_id, 10), });
expand: data.expand, res.status(200).send(row);
}); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((row) => { next(err);
res.status(200).send(row); }
})
.catch(next);
}) })
/** /**
* PUT /api/nginx/dead-hosts/123 * PUT /api/nginx/dead-hosts/123
* *
* Update and existing dead-host * Update an existing dead-host
*/ */
.put((req, res, next) => { .put(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/dead-hosts/{hostID}", "put"), req.body) try {
.then((payload) => { const payload = await apiValidator(getValidationSchema("/nginx/dead-hosts/{hostID}", "put"), req.body);
payload.id = Number.parseInt(req.params.host_id, 10); payload.id = Number.parseInt(req.params.host_id, 10);
return internalDeadHost.update(res.locals.access, payload); const result = await internalDeadHost.update(res.locals.access, payload);
}) res.status(200).send(result);
.then((result) => { } catch (err) {
res.status(200).send(result); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
}) next(err);
.catch(next); }
}) })
/** /**
* DELETE /api/nginx/dead-hosts/123 * DELETE /api/nginx/dead-hosts/123
* *
* Update and existing dead-host * Delete a dead-host
*/ */
.delete((req, res, next) => { .delete(async (req, res, next) => {
internalDeadHost try {
.delete(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = await internalDeadHost.delete(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.host_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -163,13 +167,16 @@ router
/** /**
* POST /api/nginx/dead-hosts/123/enable * POST /api/nginx/dead-hosts/123/enable
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
internalDeadHost try {
.enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = await internalDeadHost.enable(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.host_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -188,12 +195,13 @@ router
* POST /api/nginx/dead-hosts/123/disable * POST /api/nginx/dead-hosts/123/disable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
internalDeadHost try {
.disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = internalDeadHost.disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) });
.then((result) => { res.status(200).send(result);
res.status(200).send(result); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.catch(next); next(err);
}
}); });
export default router; export default router;

View File

@@ -3,6 +3,7 @@ import internalProxyHost from "../../internal/proxy-host.js";
import jwtdecode from "../../lib/express/jwt-decode.js"; import jwtdecode from "../../lib/express/jwt-decode.js";
import apiValidator from "../../lib/validator/api.js"; import apiValidator from "../../lib/validator/api.js";
import validator from "../../lib/validator/index.js"; import validator from "../../lib/validator/index.js";
import { express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js"; import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ const router = express.Router({
@@ -26,31 +27,31 @@ router
* *
* Retrieve all proxy-hosts * Retrieve all proxy-hosts
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: "common#/properties/expand", expand: {
}, $ref: "common#/properties/expand",
query: { },
$ref: "common#/properties/query", query: {
$ref: "common#/properties/query",
},
}, },
}, },
}, {
{ expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, query: typeof req.query.query === "string" ? req.query.query : null,
query: typeof req.query.query === "string" ? req.query.query : null, },
}, );
) const rows = await internalProxyHost.getAll(res.locals.access, data.expand, data.query);
.then((data) => { res.status(200).send(rows);
return internalProxyHost.getAll(res.locals.access, data.expand, data.query); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((rows) => { next(err);
res.status(200).send(rows); }
})
.catch(next);
}) })
/** /**
@@ -58,15 +59,15 @@ router
* *
* Create a new proxy-host * Create a new proxy-host
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/proxy-hosts", "post"), req.body) try {
.then((payload) => { const payload = await apiValidator(getValidationSchema("/nginx/proxy-hosts", "post"), req.body);
return internalProxyHost.create(res.locals.access, payload); const result = await internalProxyHost.create(res.locals.access, payload);
}) res.status(201).send(result);
.then((result) => { } catch (err) {
res.status(201).send(result); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err} ${JSON.stringify(err.debug, null, 2)}`);
}) next(err);
.catch(next); }
}); });
/** /**
@@ -86,35 +87,35 @@ router
* *
* Retrieve a specific proxy-host * Retrieve a specific proxy-host
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
required: ["host_id"], {
additionalProperties: false, required: ["host_id"],
properties: { additionalProperties: false,
host_id: { properties: {
$ref: "common#/properties/id", host_id: {
}, $ref: "common#/properties/id",
expand: { },
$ref: "common#/properties/expand", expand: {
$ref: "common#/properties/expand",
},
}, },
}, },
}, {
{ host_id: req.params.host_id,
host_id: req.params.host_id, expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, },
}, );
) const row = await internalProxyHost.get(res.locals.access, {
.then((data) => { id: Number.parseInt(data.host_id, 10),
return internalProxyHost.get(res.locals.access, { expand: data.expand,
id: Number.parseInt(data.host_id, 10), });
expand: data.expand, res.status(200).send(row);
}); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((row) => { next(err);
res.status(200).send(row); }
})
.catch(next);
}) })
/** /**
@@ -122,16 +123,16 @@ router
* *
* Update and existing proxy-host * Update and existing proxy-host
*/ */
.put((req, res, next) => { .put(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/proxy-hosts/{hostID}", "put"), req.body) try {
.then((payload) => { const payload = await apiValidator(getValidationSchema("/nginx/proxy-hosts/{hostID}", "put"), req.body);
payload.id = Number.parseInt(req.params.host_id, 10); payload.id = Number.parseInt(req.params.host_id, 10);
return internalProxyHost.update(res.locals.access, payload); const result = await internalProxyHost.update(res.locals.access, payload);
}) res.status(200).send(result);
.then((result) => { } catch (err) {
res.status(200).send(result); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
}) next(err);
.catch(next); }
}) })
/** /**
@@ -139,13 +140,16 @@ router
* *
* Update and existing proxy-host * Update and existing proxy-host
*/ */
.delete((req, res, next) => { .delete(async (req, res, next) => {
internalProxyHost try {
.delete(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = await internalProxyHost.delete(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.host_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -163,13 +167,16 @@ router
/** /**
* POST /api/nginx/proxy-hosts/123/enable * POST /api/nginx/proxy-hosts/123/enable
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
internalProxyHost try {
.enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = await internalProxyHost.enable(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.host_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -187,13 +194,16 @@ router
/** /**
* POST /api/nginx/proxy-hosts/123/disable * POST /api/nginx/proxy-hosts/123/disable
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
internalProxyHost try {
.disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = await internalProxyHost.disable(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.host_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; export default router;

View File

@@ -3,6 +3,7 @@ import internalRedirectionHost from "../../internal/redirection-host.js";
import jwtdecode from "../../lib/express/jwt-decode.js"; import jwtdecode from "../../lib/express/jwt-decode.js";
import apiValidator from "../../lib/validator/api.js"; import apiValidator from "../../lib/validator/api.js";
import validator from "../../lib/validator/index.js"; import validator from "../../lib/validator/index.js";
import { express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js"; import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ const router = express.Router({
@@ -26,31 +27,31 @@ router
* *
* Retrieve all redirection-hosts * Retrieve all redirection-hosts
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: "common#/properties/expand", expand: {
}, $ref: "common#/properties/expand",
query: { },
$ref: "common#/properties/query", query: {
$ref: "common#/properties/query",
},
}, },
}, },
}, {
{ expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, query: typeof req.query.query === "string" ? req.query.query : null,
query: typeof req.query.query === "string" ? req.query.query : null, },
}, );
) const rows = await internalRedirectionHost.getAll(res.locals.access, data.expand, data.query);
.then((data) => { res.status(200).send(rows);
return internalRedirectionHost.getAll(res.locals.access, data.expand, data.query); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((rows) => { next(err);
res.status(200).send(rows); }
})
.catch(next);
}) })
/** /**
@@ -58,15 +59,15 @@ router
* *
* Create a new redirection-host * Create a new redirection-host
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/redirection-hosts", "post"), req.body) try {
.then((payload) => { const payload = await apiValidator(getValidationSchema("/nginx/redirection-hosts", "post"), req.body);
return internalRedirectionHost.create(res.locals.access, payload); const result = await internalRedirectionHost.create(res.locals.access, payload);
}) res.status(201).send(result);
.then((result) => { } catch (err) {
res.status(201).send(result); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
}) next(err);
.catch(next); }
}); });
/** /**
@@ -86,35 +87,35 @@ router
* *
* Retrieve a specific redirection-host * Retrieve a specific redirection-host
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
required: ["host_id"], {
additionalProperties: false, required: ["host_id"],
properties: { additionalProperties: false,
host_id: { properties: {
$ref: "common#/properties/id", host_id: {
}, $ref: "common#/properties/id",
expand: { },
$ref: "common#/properties/expand", expand: {
$ref: "common#/properties/expand",
},
}, },
}, },
}, {
{ host_id: req.params.host_id,
host_id: req.params.host_id, expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, },
}, );
) const row = await internalRedirectionHost.get(res.locals.access, {
.then((data) => { id: Number.parseInt(data.host_id, 10),
return internalRedirectionHost.get(res.locals.access, { expand: data.expand,
id: Number.parseInt(data.host_id, 10), });
expand: data.expand, res.status(200).send(row);
}); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((row) => { next(err);
res.status(200).send(row); }
})
.catch(next);
}) })
/** /**
@@ -122,16 +123,19 @@ router
* *
* Update and existing redirection-host * Update and existing redirection-host
*/ */
.put((req, res, next) => { .put(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/redirection-hosts/{hostID}", "put"), req.body) try {
.then((payload) => { const payload = await apiValidator(
payload.id = Number.parseInt(req.params.host_id, 10); getValidationSchema("/nginx/redirection-hosts/{hostID}", "put"),
return internalRedirectionHost.update(res.locals.access, payload); req.body,
}) );
.then((result) => { payload.id = Number.parseInt(req.params.host_id, 10);
res.status(200).send(result); const result = await internalRedirectionHost.update(res.locals.access, payload);
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}) })
/** /**
@@ -139,13 +143,16 @@ router
* *
* Update and existing redirection-host * Update and existing redirection-host
*/ */
.delete((req, res, next) => { .delete(async (req, res, next) => {
internalRedirectionHost try {
.delete(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = await internalRedirectionHost.delete(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.host_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -163,13 +170,16 @@ router
/** /**
* POST /api/nginx/redirection-hosts/123/enable * POST /api/nginx/redirection-hosts/123/enable
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
internalRedirectionHost try {
.enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = await internalRedirectionHost.enable(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.host_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -187,13 +197,16 @@ router
/** /**
* POST /api/nginx/redirection-hosts/123/disable * POST /api/nginx/redirection-hosts/123/disable
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
internalRedirectionHost try {
.disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = await internalRedirectionHost.disable(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.host_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; export default router;

View File

@@ -3,6 +3,7 @@ import internalStream from "../../internal/stream.js";
import jwtdecode from "../../lib/express/jwt-decode.js"; import jwtdecode from "../../lib/express/jwt-decode.js";
import apiValidator from "../../lib/validator/api.js"; import apiValidator from "../../lib/validator/api.js";
import validator from "../../lib/validator/index.js"; import validator from "../../lib/validator/index.js";
import { express as logger } from "../../logger.js";
import { getValidationSchema } from "../../schema/index.js"; import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ const router = express.Router({
@@ -26,31 +27,31 @@ router
* *
* Retrieve all streams * Retrieve all streams
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: "common#/properties/expand", expand: {
}, $ref: "common#/properties/expand",
query: { },
$ref: "common#/properties/query", query: {
$ref: "common#/properties/query",
},
}, },
}, },
}, {
{ expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, query: typeof req.query.query === "string" ? req.query.query : null,
query: typeof req.query.query === "string" ? req.query.query : null, },
}, );
) const rows = await internalStream.getAll(res.locals.access, data.expand, data.query);
.then((data) => { res.status(200).send(rows);
return internalStream.getAll(res.locals.access, data.expand, data.query); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((rows) => { next(err);
res.status(200).send(rows); }
})
.catch(next);
}) })
/** /**
@@ -58,15 +59,15 @@ router
* *
* Create a new stream * Create a new stream
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/streams", "post"), req.body) try {
.then((payload) => { const payload = await apiValidator(getValidationSchema("/nginx/streams", "post"), req.body);
return internalStream.create(res.locals.access, payload); const result = await internalStream.create(res.locals.access, payload);
}) res.status(201).send(result);
.then((result) => { } catch (err) {
res.status(201).send(result); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
}) next(err);
.catch(next); }
}); });
/** /**
@@ -86,35 +87,35 @@ router
* *
* Retrieve a specific stream * Retrieve a specific stream
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
required: ["stream_id"], {
additionalProperties: false, required: ["stream_id"],
properties: { additionalProperties: false,
stream_id: { properties: {
$ref: "common#/properties/id", stream_id: {
}, $ref: "common#/properties/id",
expand: { },
$ref: "common#/properties/expand", expand: {
$ref: "common#/properties/expand",
},
}, },
}, },
}, {
{ stream_id: req.params.stream_id,
stream_id: req.params.stream_id, expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, },
}, );
) const row = await internalStream.get(res.locals.access, {
.then((data) => { id: Number.parseInt(data.stream_id, 10),
return internalStream.get(res.locals.access, { expand: data.expand,
id: Number.parseInt(data.stream_id, 10), });
expand: data.expand, res.status(200).send(row);
}); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((row) => { next(err);
res.status(200).send(row); }
})
.catch(next);
}) })
/** /**
@@ -122,16 +123,16 @@ router
* *
* Update and existing stream * Update and existing stream
*/ */
.put((req, res, next) => { .put(async (req, res, next) => {
apiValidator(getValidationSchema("/nginx/streams/{streamID}", "put"), req.body) try {
.then((payload) => { const payload = await apiValidator(getValidationSchema("/nginx/streams/{streamID}", "put"), req.body);
payload.id = Number.parseInt(req.params.stream_id, 10); payload.id = Number.parseInt(req.params.stream_id, 10);
return internalStream.update(res.locals.access, payload); const result = await internalStream.update(res.locals.access, payload);
}) res.status(200).send(result);
.then((result) => { } catch (err) {
res.status(200).send(result); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
}) next(err);
.catch(next); }
}) })
/** /**
@@ -139,13 +140,16 @@ router
* *
* Update and existing stream * Update and existing stream
*/ */
.delete((req, res, next) => { .delete(async (req, res, next) => {
internalStream try {
.delete(res.locals.access, { id: Number.parseInt(req.params.stream_id, 10) }) const result = await internalStream.delete(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.stream_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -163,13 +167,16 @@ router
/** /**
* POST /api/nginx/streams/123/enable * POST /api/nginx/streams/123/enable
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
internalStream try {
.enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = await internalStream.enable(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.host_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -187,13 +194,16 @@ router
/** /**
* POST /api/nginx/streams/123/disable * POST /api/nginx/streams/123/disable
*/ */
.post((req, res, next) => { .post(async (req, res, next) => {
internalStream try {
.disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }) const result = await internalStream.disable(res.locals.access, {
.then((result) => { id: Number.parseInt(req.params.host_id, 10),
res.status(200).send(result); });
}) res.status(200).send(result);
.catch(next); } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; export default router;

View File

@@ -1,6 +1,7 @@
import express from "express"; import express from "express";
import internalReport from "../internal/report.js"; import internalReport from "../internal/report.js";
import jwtdecode from "../lib/express/jwt-decode.js"; import jwtdecode from "../lib/express/jwt-decode.js";
import { express as logger } from "../logger.js";
const router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
@@ -13,17 +14,19 @@ router
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode())
/** /**
* GET /reports/hosts * GET /reports/hosts
*/ */
.get(jwtdecode(), (_, res, next) => { .get(async (req, res, next) => {
internalReport try {
.getHostsReport(res.locals.access) const data = await internalReport.getHostsReport(res.locals.access);
.then((data) => { res.status(200).send(data);
res.status(200).send(data); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.catch(next); next(err);
}
}); });
export default router; export default router;

View File

@@ -1,4 +1,5 @@
import express from "express"; import express from "express";
import { express as logger } from "../logger.js";
import PACKAGE from "../package.json" with { type: "json" }; import PACKAGE from "../package.json" with { type: "json" };
import { getCompiledSchema } from "../schema/index.js"; import { getCompiledSchema } from "../schema/index.js";
@@ -18,21 +19,26 @@ router
* GET /schema * GET /schema
*/ */
.get(async (req, res) => { .get(async (req, res) => {
const swaggerJSON = await getCompiledSchema(); try {
const swaggerJSON = await getCompiledSchema();
let proto = req.protocol; let proto = req.protocol;
if (typeof req.headers["x-forwarded-proto"] !== "undefined" && req.headers["x-forwarded-proto"]) { if (typeof req.headers["x-forwarded-proto"] !== "undefined" && req.headers["x-forwarded-proto"]) {
proto = req.headers["x-forwarded-proto"]; proto = req.headers["x-forwarded-proto"];
}
let origin = `${proto}://${req.hostname}`;
if (typeof req.headers.origin !== "undefined" && req.headers.origin) {
origin = req.headers.origin;
}
swaggerJSON.info.version = PACKAGE.version;
swaggerJSON.servers[0].url = `${origin}/api`;
res.status(200).send(swaggerJSON);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
let origin = `${proto}://${req.hostname}`;
if (typeof req.headers.origin !== "undefined" && req.headers.origin) {
origin = req.headers.origin;
}
swaggerJSON.info.version = PACKAGE.version;
swaggerJSON.servers[0].url = `${origin}/api`;
res.status(200).send(swaggerJSON);
}); });
export default router; export default router;

View File

@@ -3,6 +3,7 @@ import internalSetting from "../internal/setting.js";
import jwtdecode from "../lib/express/jwt-decode.js"; import jwtdecode from "../lib/express/jwt-decode.js";
import apiValidator from "../lib/validator/api.js"; import apiValidator from "../lib/validator/api.js";
import validator from "../lib/validator/index.js"; import validator from "../lib/validator/index.js";
import { express as logger } from "../logger.js";
import { getValidationSchema } from "../schema/index.js"; import { getValidationSchema } from "../schema/index.js";
const router = express.Router({ const router = express.Router({
@@ -26,13 +27,14 @@ router
* *
* Retrieve all settings * Retrieve all settings
*/ */
.get((_, res, next) => { .get(async (req, res, next) => {
internalSetting try {
.getAll(res.locals.access) const rows = await internalSetting.getAll(res.locals.access);
.then((rows) => { res.status(200).send(rows);
res.status(200).send(rows); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.catch(next); next(err);
}
}); });
/** /**
@@ -52,31 +54,31 @@ router
* *
* Retrieve a specific setting * Retrieve a specific setting
*/ */
.get((req, res, next) => { .get(async (req, res, next) => {
validator( try {
{ const data = await validator(
required: ["setting_id"], {
additionalProperties: false, required: ["setting_id"],
properties: { additionalProperties: false,
setting_id: { properties: {
type: "string", setting_id: {
minLength: 1, type: "string",
minLength: 1,
},
}, },
}, },
}, {
{ setting_id: req.params.setting_id,
setting_id: req.params.setting_id, },
}, );
) const row = await internalSetting.get(res.locals.access, {
.then((data) => { id: data.setting_id,
return internalSetting.get(res.locals.access, { });
id: data.setting_id, res.status(200).send(row);
}); } catch (err) {
}) logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
.then((row) => { next(err);
res.status(200).send(row); }
})
.catch(next);
}) })
/** /**
@@ -84,16 +86,16 @@ router
* *
* Update and existing setting * Update and existing setting
*/ */
.put((req, res, next) => { .put(async (req, res, next) => {
apiValidator(getValidationSchema("/settings/{settingID}", "put"), req.body) try {
.then((payload) => { const payload = await apiValidator(getValidationSchema("/settings/{settingID}", "put"), req.body);
payload.id = req.params.setting_id; payload.id = req.params.setting_id;
return internalSetting.update(res.locals.access, payload); const result = await internalSetting.update(res.locals.access, payload);
}) res.status(200).send(result);
.then((result) => { } catch (err) {
res.status(200).send(result); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
}) next(err);
.catch(next); }
}); });
export default router; export default router;

View File

@@ -1,6 +1,8 @@
import express from "express"; import express from "express";
import internalUser from "../internal/user.js"; import internalUser from "../internal/user.js";
import Access from "../lib/access.js"; import Access from "../lib/access.js";
import { isCI } from "../lib/config.js";
import errs from "../lib/error.js";
import jwtdecode from "../lib/express/jwt-decode.js"; import jwtdecode from "../lib/express/jwt-decode.js";
import userIdFromMe from "../lib/express/user-id-from-me.js"; import userIdFromMe from "../lib/express/user-id-from-me.js";
import apiValidator from "../lib/validator/api.js"; import apiValidator from "../lib/validator/api.js";
@@ -45,11 +47,18 @@ router
}, },
}, },
{ {
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, expand:
typeof req.query.expand === "string"
? req.query.expand.split(",")
: null,
query: typeof req.query.query === "string" ? req.query.query : null, query: typeof req.query.query === "string" ? req.query.query : null,
}, },
); );
const users = await internalUser.getAll(res.locals.access, data.expand, data.query); const users = await internalUser.getAll(
res.locals.access,
data.expand,
data.query,
);
res.status(200).send(users); res.status(200).send(users);
} catch (err) { } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
@@ -85,13 +94,43 @@ router
} }
} }
const payload = await apiValidator(getValidationSchema("/users", "post"), body); const payload = await apiValidator(
getValidationSchema("/users", "post"),
body,
);
const user = await internalUser.create(res.locals.access, payload); const user = await internalUser.create(res.locals.access, payload);
res.status(201).send(user); res.status(201).send(user);
} catch (err) { } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err); next(err);
} }
})
/**
* DELETE /api/users
*
* Deletes ALL users. This is NOT GENERALLY AVAILABLE!
* (!) It is NOT an authenticated endpoint.
* (!) Only CI should be able to call this endpoint. As a result,
*
* it will only work when the env vars DEBUG=true and CI=true
*
* Do NOT set those env vars in a production environment!
*/
.delete(async (_, res, next) => {
if (isCI()) {
try {
logger.warn("Deleting all users - CI environment detected, allowing this operation");
await internalUser.deleteAll();
res.status(200).send(true);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
return;
}
next(new errs.ItemNotFoundError());
}); });
/** /**
@@ -129,14 +168,20 @@ router
}, },
{ {
user_id: req.params.user_id, user_id: req.params.user_id,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, expand:
typeof req.query.expand === "string"
? req.query.expand.split(",")
: null,
}, },
); );
const user = await internalUser.get(res.locals.access, { const user = await internalUser.get(res.locals.access, {
id: data.user_id, id: data.user_id,
expand: data.expand, expand: data.expand,
omit: internalUser.getUserOmisionsByAccess(res.locals.access, data.user_id), omit: internalUser.getUserOmisionsByAccess(
res.locals.access,
data.user_id,
),
}); });
res.status(200).send(user); res.status(200).send(user);
} catch (err) { } catch (err) {
@@ -152,7 +197,10 @@ router
*/ */
.put(async (req, res, next) => { .put(async (req, res, next) => {
try { try {
const payload = await apiValidator(getValidationSchema("/users/{userID}", "put"), req.body); const payload = await apiValidator(
getValidationSchema("/users/{userID}", "put"),
req.body,
);
payload.id = req.params.user_id; payload.id = req.params.user_id;
const result = await internalUser.update(res.locals.access, payload); const result = await internalUser.update(res.locals.access, payload);
res.status(200).send(result); res.status(200).send(result);
@@ -169,7 +217,9 @@ router
*/ */
.delete(async (req, res, next) => { .delete(async (req, res, next) => {
try { try {
const result = await internalUser.delete(res.locals.access, { id: req.params.user_id }); const result = await internalUser.delete(res.locals.access, {
id: req.params.user_id,
});
res.status(200).send(result); res.status(200).send(result);
} catch (err) { } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
@@ -197,7 +247,10 @@ router
*/ */
.put(async (req, res, next) => { .put(async (req, res, next) => {
try { try {
const payload = await apiValidator(getValidationSchema("/users/{userID}/auth", "put"), req.body); const payload = await apiValidator(
getValidationSchema("/users/{userID}/auth", "put"),
req.body,
);
payload.id = req.params.user_id; payload.id = req.params.user_id;
const result = await internalUser.setPassword(res.locals.access, payload); const result = await internalUser.setPassword(res.locals.access, payload);
res.status(200).send(result); res.status(200).send(result);
@@ -227,9 +280,15 @@ router
*/ */
.put(async (req, res, next) => { .put(async (req, res, next) => {
try { try {
const payload = await apiValidator(getValidationSchema("/users/{userID}/permissions", "put"), req.body); const payload = await apiValidator(
getValidationSchema("/users/{userID}/permissions", "put"),
req.body,
);
payload.id = req.params.user_id; payload.id = req.params.user_id;
const result = await internalUser.setPermissions(res.locals.access, payload); const result = await internalUser.setPermissions(
res.locals.access,
payload,
);
res.status(200).send(result); res.status(200).send(result);
} catch (err) { } catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);

View File

@@ -7,7 +7,8 @@
"description": "Unique identifier", "description": "Unique identifier",
"readOnly": true, "readOnly": true,
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1,
"example": 11
}, },
"expand": { "expand": {
"anyOf": [ "anyOf": [
@@ -38,35 +39,42 @@
"created_on": { "created_on": {
"description": "Date and time of creation", "description": "Date and time of creation",
"readOnly": true, "readOnly": true,
"type": "string" "type": "string",
"example": "2025-10-28T04:17:54.000Z"
}, },
"modified_on": { "modified_on": {
"description": "Date and time of last update", "description": "Date and time of last update",
"readOnly": true, "readOnly": true,
"type": "string" "type": "string",
"example": "2025-10-28T04:17:54.000Z"
}, },
"user_id": { "user_id": {
"description": "User ID", "description": "User ID",
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1,
"example": 2
}, },
"certificate_id": { "certificate_id": {
"description": "Certificate ID", "description": "Certificate ID",
"anyOf": [ "anyOf": [
{ {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0,
"example": 5
}, },
{ {
"type": "string", "type": "string",
"pattern": "^new$" "pattern": "^new$",
"example": "new"
} }
] ],
"example": 5
}, },
"access_list_id": { "access_list_id": {
"description": "Access List ID", "description": "Access List ID",
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0,
"example": 3
}, },
"domain_names": { "domain_names": {
"description": "Domain Names separated by a comma", "description": "Domain Names separated by a comma",
@@ -77,44 +85,157 @@
"items": { "items": {
"type": "string", "type": "string",
"pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$" "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$"
} },
"example": ["example.com", "www.example.com"]
}, },
"enabled": { "enabled": {
"description": "Is Enabled", "description": "Is Enabled",
"type": "boolean" "type": "boolean",
"example": false
}, },
"ssl_forced": { "ssl_forced": {
"description": "Is SSL Forced", "description": "Is SSL Forced",
"type": "boolean" "type": "boolean",
"example": true
}, },
"hsts_enabled": { "hsts_enabled": {
"description": "Is HSTS Enabled", "description": "Is HSTS Enabled",
"type": "boolean" "type": "boolean",
"example": true
}, },
"hsts_subdomains": { "hsts_subdomains": {
"description": "Is HSTS applicable to all subdomains", "description": "Is HSTS applicable to all subdomains",
"type": "boolean" "type": "boolean",
"example": true
}, },
"ssl_provider": { "ssl_provider": {
"type": "string", "type": "string",
"pattern": "^(letsencrypt|other)$" "pattern": "^(letsencrypt|other)$",
"example": "letsencrypt"
}, },
"http2_support": { "http2_support": {
"description": "HTTP2 Protocol Support", "description": "HTTP2 Protocol Support",
"type": "boolean" "type": "boolean",
"example": true
}, },
"block_exploits": { "block_exploits": {
"description": "Should we block common exploits", "description": "Should we block common exploits",
"type": "boolean" "type": "boolean",
"example": false
}, },
"caching_enabled": { "caching_enabled": {
"description": "Should we cache assets", "description": "Should we cache assets",
"type": "boolean" "type": "boolean",
"example": true
}, },
"email": { "email": {
"description": "Email address", "description": "Email address",
"type": "string", "type": "string",
"pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" "pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$",
"example": "me@example.com"
},
"directive": {
"type": "string",
"enum": ["allow", "deny"],
"example": "allow"
},
"address": {
"oneOf": [
{
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
},
{
"type": "string",
"pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
},
{
"type": "string",
"pattern": "^all$"
}
],
"example": "192.168.0.11"
},
"access_items": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"username": {
"type": "string",
"minLength": 1
},
"password": {
"type": "string"
}
},
"example": {
"username": "admin",
"password": "pass"
}
},
"example": [
{
"username": "admin",
"password": "pass"
}
]
},
"access_clients": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"address": {
"$ref": "#/properties/address"
},
"directive": {
"$ref": "#/properties/directive"
}
},
"example": {
"directive": "allow",
"address": "192.168.0.0/24"
}
},
"example": [
{
"directive": "allow",
"address": "192.168.0.0/24"
}
]
},
"certificate_files": {
"description": "Certificate Files",
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["certificate", "certificate_key"],
"properties": {
"certificate": {
"type": "string",
"example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----"
},
"certificate_key": {
"type": "string",
"example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----"
},
"intermediate_certificate": {
"type": "string",
"example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----"
}
}
},
"example": {
"certificate": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----",
"certificate_key": "-----BEGIN PRIVATE\nMIID...-----END CERTIFICATE-----"
}
}
}
} }
} }
} }

View File

@@ -1,8 +1,7 @@
{ {
"type": "object", "type": "object",
"description": "Access List object", "description": "Access List object",
"required": ["id", "created_on", "modified_on", "owner_user_id", "name", "directive", "address", "satisfy_any", "pass_auth", "meta"], "required": ["id", "created_on", "modified_on", "owner_user_id", "name", "meta", "satisfy_any", "pass_auth", "proxy_host_count"],
"additionalProperties": false,
"properties": { "properties": {
"id": { "id": {
"$ref": "../common.json#/properties/id" "$ref": "../common.json#/properties/id"
@@ -18,36 +17,25 @@
}, },
"name": { "name": {
"type": "string", "type": "string",
"minLength": 1 "minLength": 1,
}, "example": "My Access List"
"directive": {
"type": "string",
"enum": ["allow", "deny"]
},
"address": {
"oneOf": [
{
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
},
{
"type": "string",
"pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
},
{
"type": "string",
"pattern": "^all$"
}
]
},
"satisfy_any": {
"type": "boolean"
},
"pass_auth": {
"type": "boolean"
}, },
"meta": { "meta": {
"type": "object" "type": "object",
"example": {}
},
"satisfy_any": {
"type": "boolean",
"example": true
},
"pass_auth": {
"type": "boolean",
"example": false
},
"proxy_host_count": {
"type": "integer",
"minimum": 0,
"example": 3
} }
} }
} }

View File

@@ -0,0 +1,7 @@
{
"type": "array",
"description": "Audit Log list",
"items": {
"$ref": "./audit-log-object.json"
}
}

View File

@@ -1,7 +1,16 @@
{ {
"type": "object", "type": "object",
"description": "Audit Log object", "description": "Audit Log object",
"required": ["id", "created_on", "modified_on", "user_id", "object_type", "object_id", "action", "meta"], "required": [
"id",
"created_on",
"modified_on",
"user_id",
"object_type",
"object_id",
"action",
"meta"
],
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"id": { "id": {
@@ -17,16 +26,22 @@
"$ref": "../common.json#/properties/user_id" "$ref": "../common.json#/properties/user_id"
}, },
"object_type": { "object_type": {
"type": "string" "type": "string",
"example": "certificate"
}, },
"object_id": { "object_id": {
"$ref": "../common.json#/properties/id" "$ref": "../common.json#/properties/id"
}, },
"action": { "action": {
"type": "string" "type": "string",
"example": "created"
}, },
"meta": { "meta": {
"type": "object" "type": "object",
"example": {}
},
"user": {
"$ref": "./user-object.json"
} }
} }
} }

View File

@@ -21,7 +21,8 @@
}, },
"nice_name": { "nice_name": {
"type": "string", "type": "string",
"description": "Nice Name for the custom certificate" "description": "Nice Name for the custom certificate",
"example": "My Custom Cert"
}, },
"domain_names": { "domain_names": {
"description": "Domain Names separated by a comma", "description": "Domain Names separated by a comma",
@@ -31,12 +32,14 @@
"items": { "items": {
"type": "string", "type": "string",
"pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$" "pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$"
} },
"example": ["example.com", "www.example.com"]
}, },
"expires_on": { "expires_on": {
"description": "Date and time of expiration", "description": "Date and time of expiration",
"readOnly": true, "readOnly": true,
"type": "string" "type": "string",
"example": "2025-10-28T04:17:54.000Z"
}, },
"owner": { "owner": {
"$ref": "./user-object.json" "$ref": "./user-object.json"
@@ -56,25 +59,22 @@
"dns_challenge": { "dns_challenge": {
"type": "boolean" "type": "boolean"
}, },
"dns_provider": {
"type": "string"
},
"dns_provider_credentials": { "dns_provider_credentials": {
"type": "string" "type": "string"
}, },
"letsencrypt_agree": { "dns_provider": {
"type": "boolean" "type": "string"
}, },
"letsencrypt_certificate": { "letsencrypt_certificate": {
"type": "object" "type": "object"
}, },
"letsencrypt_email": {
"$ref": "../common.json#/properties/email"
},
"propagation_seconds": { "propagation_seconds": {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0
} }
},
"example": {
"dns_challenge": false
} }
} }
} }

View File

@@ -35,13 +35,30 @@
"$ref": "../common.json#/properties/http2_support" "$ref": "../common.json#/properties/http2_support"
}, },
"advanced_config": { "advanced_config": {
"type": "string" "type": "string",
"example": ""
}, },
"enabled": { "enabled": {
"$ref": "../common.json#/properties/enabled" "$ref": "../common.json#/properties/enabled"
}, },
"meta": { "meta": {
"type": "object" "type": "object",
"example": {}
},
"certificate": {
"oneOf": [
{
"type": "null",
"example": null
},
{
"$ref": "./certificate-object.json"
}
],
"example": null
},
"owner": {
"$ref": "./user-object.json"
} }
} }
} }

View File

@@ -0,0 +1,23 @@
{
"type": "array",
"description": "DNS Providers list",
"items": {
"type": "object",
"required": ["id", "name", "credentials"],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the DNS provider, matching the python package"
},
"name": {
"type": "string",
"description": "Human-readable name of the DNS provider"
},
"credentials": {
"type": "string",
"description": "Instructions on how to format the credentials for this DNS provider"
}
}
}
}

View File

@@ -5,10 +5,12 @@
"required": ["code", "message"], "required": ["code", "message"],
"properties": { "properties": {
"code": { "code": {
"type": "integer" "type": "integer",
"example": 400
}, },
"message": { "message": {
"type": "string" "type": "string",
"example": "Bad Request"
} }
} }
} }

View File

@@ -9,6 +9,11 @@
"description": "Healthy", "description": "Healthy",
"example": "OK" "example": "OK"
}, },
"setup": {
"type": "boolean",
"description": "Whether the initial setup has been completed",
"example": true
},
"version": { "version": {
"type": "object", "type": "object",
"description": "The version object", "description": "The version object",
@@ -22,15 +27,18 @@
"properties": { "properties": {
"major": { "major": {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0,
"example": 2
}, },
"minor": { "minor": {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0,
"example": 10
}, },
"revision": { "revision": {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0,
"example": 1
} }
} }
} }

View File

@@ -5,37 +5,44 @@
"visibility": { "visibility": {
"type": "string", "type": "string",
"description": "Visibility Type", "description": "Visibility Type",
"enum": ["all", "user"] "enum": ["all", "user"],
"example": "all"
}, },
"access_lists": { "access_lists": {
"type": "string", "type": "string",
"description": "Access Lists Permissions", "description": "Access Lists Permissions",
"enum": ["hidden", "view", "manage"] "enum": ["hidden", "view", "manage"],
"example": "view"
}, },
"dead_hosts": { "dead_hosts": {
"type": "string", "type": "string",
"description": "404 Hosts Permissions", "description": "404 Hosts Permissions",
"enum": ["hidden", "view", "manage"] "enum": ["hidden", "view", "manage"],
"example": "manage"
}, },
"proxy_hosts": { "proxy_hosts": {
"type": "string", "type": "string",
"description": "Proxy Hosts Permissions", "description": "Proxy Hosts Permissions",
"enum": ["hidden", "view", "manage"] "enum": ["hidden", "view", "manage"],
"example": "hidden"
}, },
"redirection_hosts": { "redirection_hosts": {
"type": "string", "type": "string",
"description": "Redirection Permissions", "description": "Redirection Permissions",
"enum": ["hidden", "view", "manage"] "enum": ["hidden", "view", "manage"],
"example": "view"
}, },
"streams": { "streams": {
"type": "string", "type": "string",
"description": "Streams Permissions", "description": "Streams Permissions",
"enum": ["hidden", "view", "manage"] "enum": ["hidden", "view", "manage"],
"example": "manage"
}, },
"certificates": { "certificates": {
"type": "string", "type": "string",
"description": "Certificates Permissions", "description": "Certificates Permissions",
"enum": ["hidden", "view", "manage"] "enum": ["hidden", "view", "manage"],
"example": "hidden"
} }
} }
} }

View File

@@ -24,7 +24,6 @@
"hsts_enabled", "hsts_enabled",
"hsts_subdomains" "hsts_subdomains"
], ],
"additionalProperties": false,
"properties": { "properties": {
"id": { "id": {
"$ref": "../common.json#/properties/id" "$ref": "../common.json#/properties/id"
@@ -44,12 +43,14 @@
"forward_host": { "forward_host": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
"maxLength": 255 "maxLength": 255,
"example": "127.0.0.1"
}, },
"forward_port": { "forward_port": {
"type": "integer", "type": "integer",
"minimum": 1, "minimum": 1,
"maximum": 65535 "maximum": 65535,
"example": 8080
}, },
"access_list_id": { "access_list_id": {
"$ref": "../common.json#/properties/access_list_id" "$ref": "../common.json#/properties/access_list_id"
@@ -67,22 +68,28 @@
"$ref": "../common.json#/properties/block_exploits" "$ref": "../common.json#/properties/block_exploits"
}, },
"advanced_config": { "advanced_config": {
"type": "string" "type": "string",
"example": ""
}, },
"meta": { "meta": {
"type": "object" "type": "object",
"example": {
"nginx_online": true,
"nginx_err": null
}
}, },
"allow_websocket_upgrade": { "allow_websocket_upgrade": {
"description": "Allow Websocket Upgrade for all paths", "description": "Allow Websocket Upgrade for all paths",
"example": true, "type": "boolean",
"type": "boolean" "example": true
}, },
"http2_support": { "http2_support": {
"$ref": "../common.json#/properties/http2_support" "$ref": "../common.json#/properties/http2_support"
}, },
"forward_scheme": { "forward_scheme": {
"type": "string", "type": "string",
"enum": ["http", "https"] "enum": ["http", "https"],
"example": "http"
}, },
"enabled": { "enabled": {
"$ref": "../common.json#/properties/enabled" "$ref": "../common.json#/properties/enabled"
@@ -118,7 +125,15 @@
"type": "string" "type": "string"
} }
} }
} },
"example": [
{
"path": "/app",
"forward_scheme": "http",
"forward_host": "example.com",
"forward_port": 80
}
]
}, },
"hsts_enabled": { "hsts_enabled": {
"$ref": "../common.json#/properties/hsts_enabled" "$ref": "../common.json#/properties/hsts_enabled"
@@ -129,12 +144,14 @@
"certificate": { "certificate": {
"oneOf": [ "oneOf": [
{ {
"type": "null" "type": "null",
"example": null
}, },
{ {
"$ref": "./certificate-object.json" "$ref": "./certificate-object.json"
} }
] ],
"example": null
}, },
"owner": { "owner": {
"$ref": "./user-object.json" "$ref": "./user-object.json"
@@ -142,12 +159,14 @@
"access_list": { "access_list": {
"oneOf": [ "oneOf": [
{ {
"type": "null" "type": "null",
"example": null
}, },
{ {
"$ref": "./access-list-object.json" "$ref": "./access-list-object.json"
} }
] ],
"example": null
} }
} }
} }

View File

@@ -1,7 +1,26 @@
{ {
"type": "object", "type": "object",
"description": "Redirection Host object", "description": "Redirection Host object",
"required": ["id", "created_on", "modified_on", "owner_user_id", "domain_names", "forward_http_code", "forward_scheme", "forward_domain_name", "preserve_path", "certificate_id", "ssl_forced", "hsts_enabled", "hsts_subdomains", "http2_support", "block_exploits", "advanced_config", "enabled", "meta"], "required": [
"id",
"created_on",
"modified_on",
"owner_user_id",
"domain_names",
"forward_http_code",
"forward_scheme",
"forward_domain_name",
"preserve_path",
"certificate_id",
"ssl_forced",
"hsts_enabled",
"hsts_subdomains",
"http2_support",
"block_exploits",
"advanced_config",
"enabled",
"meta"
],
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"id": { "id": {
@@ -21,25 +40,30 @@
}, },
"forward_http_code": { "forward_http_code": {
"description": "Redirect HTTP Status Code", "description": "Redirect HTTP Status Code",
"example": 302,
"type": "integer", "type": "integer",
"minimum": 300, "minimum": 300,
"maximum": 308 "maximum": 308,
"example": 302
}, },
"forward_scheme": { "forward_scheme": {
"type": "string", "type": "string",
"enum": ["auto", "http", "https"] "enum": [
"auto",
"http",
"https"
],
"example": "http"
}, },
"forward_domain_name": { "forward_domain_name": {
"description": "Domain Name", "description": "Domain Name",
"example": "jc21.com",
"type": "string", "type": "string",
"pattern": "^(?:[^.*]+\\.?)+[^.]$" "pattern": "^(?:[^.*]+\\.?)+[^.]$",
"example": "jc21.com"
}, },
"preserve_path": { "preserve_path": {
"description": "Should the path be preserved", "description": "Should the path be preserved",
"example": true, "type": "boolean",
"type": "boolean" "example": true
}, },
"certificate_id": { "certificate_id": {
"$ref": "../common.json#/properties/certificate_id" "$ref": "../common.json#/properties/certificate_id"
@@ -60,13 +84,33 @@
"$ref": "../common.json#/properties/block_exploits" "$ref": "../common.json#/properties/block_exploits"
}, },
"advanced_config": { "advanced_config": {
"type": "string" "type": "string",
"example": ""
}, },
"enabled": { "enabled": {
"$ref": "../common.json#/properties/enabled" "$ref": "../common.json#/properties/enabled"
}, },
"meta": { "meta": {
"type": "object" "type": "object",
"example": {
"nginx_online": true,
"nginx_err": null
}
},
"certificate": {
"oneOf": [
{
"type": "null",
"example": null
},
{
"$ref": "./certificate-object.json"
}
],
"example": null
},
"owner": {
"$ref": "./user-object.json"
} }
} }
} }

View File

@@ -1,6 +1,8 @@
{ {
"BearerAuth": { "bearerAuth": {
"type": "http", "type": "http",
"scheme": "bearer" "scheme": "bearer",
"bearerFormat": "JWT",
"description": "JWT Bearer Token authentication"
} }
} }

View File

@@ -1,7 +1,19 @@
{ {
"type": "object", "type": "object",
"description": "Stream object", "description": "Stream object",
"required": ["id", "created_on", "modified_on", "owner_user_id", "incoming_port", "forwarding_host", "forwarding_port", "tcp_forwarding", "udp_forwarding", "enabled", "meta"], "required": [
"id",
"created_on",
"modified_on",
"owner_user_id",
"incoming_port",
"forwarding_host",
"forwarding_port",
"tcp_forwarding",
"udp_forwarding",
"enabled",
"meta"
],
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"id": { "id": {
@@ -19,36 +31,41 @@
"incoming_port": { "incoming_port": {
"type": "integer", "type": "integer",
"minimum": 1, "minimum": 1,
"maximum": 65535 "maximum": 65535,
"example": 9090
}, },
"forwarding_host": { "forwarding_host": {
"anyOf": [ "anyOf": [
{ {
"description": "Domain Name", "description": "Domain Name",
"example": "jc21.com",
"type": "string", "type": "string",
"pattern": "^(?:[^.*]+\\.?)+[^.]$" "pattern": "^(?:[^.*]+\\.?)+[^.]$",
"example": "example.com"
}, },
{ {
"type": "string", "type": "string",
"format": "ipv4" "format": "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$"
}, },
{ {
"type": "string", "type": "string",
"format": "ipv6" "format": "ipv6"
} }
] ],
"example": "example.com"
}, },
"forwarding_port": { "forwarding_port": {
"type": "integer", "type": "integer",
"minimum": 1, "minimum": 1,
"maximum": 65535 "maximum": 65535,
"example": 80
}, },
"tcp_forwarding": { "tcp_forwarding": {
"type": "boolean" "type": "boolean",
"example": true
}, },
"udp_forwarding": { "udp_forwarding": {
"type": "boolean" "type": "boolean",
"example": false
}, },
"enabled": { "enabled": {
"$ref": "../common.json#/properties/enabled" "$ref": "../common.json#/properties/enabled"
@@ -57,10 +74,8 @@
"$ref": "../common.json#/properties/certificate_id" "$ref": "../common.json#/properties/certificate_id"
}, },
"meta": { "meta": {
"type": "object" "type": "object",
}, "example": {}
"owner": {
"$ref": "./user-object.json"
}, },
"certificate": { "certificate": {
"oneOf": [ "oneOf": [
@@ -70,7 +85,11 @@
{ {
"$ref": "./certificate-object.json" "$ref": "./certificate-object.json"
} }
] ],
"example": null
},
"owner": {
"$ref": "./user-object.json"
} }
} }
} }

View File

@@ -54,6 +54,63 @@
"items": { "items": {
"type": "string" "type": "string"
} }
},
"permissions": {
"type": "object",
"description": "Permissions if expanded in request",
"required": [
"visibility",
"proxy_hosts",
"redirection_hosts",
"dead_hosts",
"streams",
"access_lists",
"certificates"
],
"properties": {
"visibility": {
"type": "string",
"description": "Visibility level",
"example": "all",
"pattern": "^(all|user)$"
},
"proxy_hosts": {
"type": "string",
"description": "Proxy Hosts access level",
"example": "manage",
"pattern": "^(manage|view|hidden)$"
},
"redirection_hosts": {
"type": "string",
"description": "Redirection Hosts access level",
"example": "manage",
"pattern": "^(manage|view|hidden)$"
},
"dead_hosts": {
"type": "string",
"description": "Dead Hosts access level",
"example": "manage",
"pattern": "^(manage|view|hidden)$"
},
"streams": {
"type": "string",
"description": "Streams access level",
"example": "manage",
"pattern": "^(manage|view|hidden)$"
},
"access_lists": {
"type": "string",
"description": "Access Lists access level",
"example": "hidden",
"pattern": "^(manage|view|hidden)$"
},
"certificates": {
"type": "string",
"description": "Certificates access level",
"example": "view",
"pattern": "^(manage|view|hidden)$"
}
}
} }
} }
} }

View File

@@ -1,10 +1,10 @@
{ {
"operationId": "getAuditLog", "operationId": "getAuditLogs",
"summary": "Get Audit Log", "summary": "Get Audit Logs",
"tags": ["Audit Log"], "tags": ["audit-log"],
"security": [ "security": [
{ {
"BearerAuth": ["audit-log"] "bearerAuth": ["admin"]
} }
], ],
"responses": { "responses": {
@@ -44,7 +44,7 @@
} }
}, },
"schema": { "schema": {
"$ref": "../../components/audit-log-object.json" "$ref": "../../components/audit-log-list.json"
} }
} }
} }

View File

@@ -0,0 +1,72 @@
{
"operationId": "getAuditLog",
"summary": "Get Audit Log Event",
"tags": ["audit-log"],
"security": [
{
"bearerAuth": [
"admin"
]
}
],
"parameters": [
{
"in": "path",
"name": "id",
"description": "Audit Log Event ID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
"example": 1
}
],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"id": 1,
"created_on": "2025-09-15T17:27:45.000Z",
"modified_on": "2025-09-15T17:27:45.000Z",
"user_id": 1,
"object_type": "user",
"object_id": 1,
"action": "created",
"meta": {
"id": 1,
"created_on": "2025-09-15T17:27:45.000Z",
"modified_on": "2025-09-15T17:27:45.000Z",
"is_disabled": false,
"email": "jc@jc21.com",
"name": "Jamie",
"nickname": "Jamie",
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
"roles": [
"admin"
],
"permissions": {
"visibility": "all",
"proxy_hosts": "manage",
"redirection_hosts": "manage",
"dead_hosts": "manage",
"streams": "manage",
"access_lists": "manage",
"certificates": "manage"
}
}
}
}
},
"schema": {
"$ref": "../../../components/audit-log-object.json"
}
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
{ {
"operationId": "health", "operationId": "health",
"summary": "Returns the API health status", "summary": "Returns the API health status",
"tags": ["Public"], "tags": ["public"],
"responses": { "responses": {
"200": { "200": {
"description": "200 response", "description": "200 response",
@@ -11,6 +11,7 @@
"default": { "default": {
"value": { "value": {
"status": "OK", "status": "OK",
"setup": true,
"version": { "version": {
"major": 2, "major": 2,
"minor": 1, "minor": 1,

View File

@@ -1,10 +1,12 @@
{ {
"operationId": "getAccessLists", "operationId": "getAccessLists",
"summary": "Get all access lists", "summary": "Get all access lists",
"tags": ["Access Lists"], "tags": ["access-lists"],
"security": [ "security": [
{ {
"BearerAuth": ["access_lists"] "bearerAuth": [
"access_lists.view"
]
} }
], ],
"parameters": [ "parameters": [
@@ -14,7 +16,12 @@
"description": "Expansions", "description": "Expansions",
"schema": { "schema": {
"type": "string", "type": "string",
"enum": ["owner", "items", "clients", "proxy_hosts"] "enum": [
"owner",
"items",
"clients",
"proxy_hosts"
]
} }
} }
], ],
@@ -23,22 +30,16 @@
"description": "200 response", "description": "200 response",
"content": { "content": {
"application/json": { "application/json": {
"examples": { "example": {
"default": { "id": 1,
"value": [ "created_on": "2024-10-08T22:15:40.000Z",
{ "modified_on": "2024-10-08T22:15:40.000Z",
"id": 1, "owner_user_id": 1,
"created_on": "2024-10-08T22:15:40.000Z", "name": "test1234",
"modified_on": "2024-10-08T22:15:40.000Z", "meta": {},
"owner_user_id": 1, "satisfy_any": true,
"name": "test1234", "pass_auth": false,
"meta": {}, "proxy_host_count": 0
"satisfy_any": true,
"pass_auth": false,
"proxy_host_count": 0
}
]
}
}, },
"schema": { "schema": {
"$ref": "../../../components/access-list-object.json" "$ref": "../../../components/access-list-object.json"

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "deleteAccessList", "operationId": "deleteAccessList",
"summary": "Delete a Access List", "summary": "Delete a Access List",
"tags": ["Access Lists"], "tags": ["access-lists"],
"security": [ "security": [
{ {
"BearerAuth": ["access_lists"] "bearerAuth": ["access_lists.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "listID", "name": "listID",
"description": "Access List ID",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,49 +1,54 @@
{ {
"operationId": "getAccessList", "operationId": "getAccessList",
"summary": "Get a access List", "summary": "Get a access List",
"tags": ["Access Lists"], "tags": [
"security": [ "access-lists"
{ ],
"BearerAuth": ["access_lists"] "security": [
} {
], "bearerAuth": [
"parameters": [ "access_lists.view"
{ ]
"in": "path", }
"name": "listID", ],
"schema": { "parameters": [
"type": "integer", {
"minimum": 1 "in": "path",
}, "name": "listID",
"required": true, "description": "Access List ID",
"example": 1 "schema": {
} "type": "integer",
], "minimum": 1
"responses": { },
"200": { "required": true,
"description": "200 response", "example": 1
"content": { }
"application/json": { ],
"examples": { "responses": {
"default": { "200": {
"value": { "description": "200 response",
"id": 1, "content": {
"created_on": "2020-01-30T09:36:08.000Z", "application/json": {
"modified_on": "2020-01-30T09:41:04.000Z", "examples": {
"is_disabled": false, "default": {
"email": "jc@jc21.com", "value": {
"name": "Jamie Curnow", "id": 1,
"nickname": "James", "created_on": "2025-10-28T04:06:55.000Z",
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", "modified_on": "2025-10-29T22:48:20.000Z",
"roles": ["admin"] "owner_user_id": 1,
} "name": "My Access List",
} "meta": {},
}, "satisfy_any": false,
"schema": { "pass_auth": false,
"$ref": "../../../../components/access-list-object.json" "proxy_host_count": 1
} }
} }
} },
} "schema": {
} "$ref": "../../../../components/access-list-object.json"
}
}
}
}
}
} }

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "updateAccessList", "operationId": "updateAccessList",
"summary": "Update a Access List", "summary": "Update a Access List",
"tags": ["Access Lists"], "tags": ["access-lists"],
"security": [ "security": [
{ {
"BearerAuth": ["access_lists"] "bearerAuth": ["access_lists.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "listID", "name": "listID",
"description": "Access List ID",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1
@@ -39,50 +40,29 @@
"$ref": "../../../../components/access-list-object.json#/properties/pass_auth" "$ref": "../../../../components/access-list-object.json#/properties/pass_auth"
}, },
"items": { "items": {
"type": "array", "$ref": "../../../../common.json#/properties/access_items"
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"username": {
"type": "string",
"minLength": 1
},
"password": {
"type": "string"
}
}
}
}, },
"clients": { "clients": {
"type": "array", "$ref": "../../../../common.json#/properties/access_clients"
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"address": {
"oneOf": [
{
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
},
{
"type": "string",
"pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
},
{
"type": "string",
"pattern": "^all$"
}
]
},
"directive": {
"$ref": "../../../../components/access-list-object.json#/properties/directive"
}
}
}
} }
} }
},
"example": {
"name": "My Access List",
"satisfy_any": true,
"pass_auth": false,
"items": [
{
"username": "admin2",
"password": "pass2"
}
],
"clients": [
{
"directive": "allow",
"address": "192.168.0.0/24"
}
]
} }
} }
} }
@@ -108,7 +88,6 @@
"id": 1, "id": 1,
"created_on": "2024-10-07T22:43:55.000Z", "created_on": "2024-10-07T22:43:55.000Z",
"modified_on": "2024-10-08T12:52:54.000Z", "modified_on": "2024-10-08T12:52:54.000Z",
"is_deleted": false,
"is_disabled": false, "is_disabled": false,
"email": "admin@example.com", "email": "admin@example.com",
"name": "Administrator", "name": "Administrator",

View File

@@ -1,10 +1,12 @@
{ {
"operationId": "createAccessList", "operationId": "createAccessList",
"summary": "Create a Access List", "summary": "Create a Access List",
"tags": ["Access Lists"], "tags": ["access-lists"],
"security": [ "security": [
{ {
"BearerAuth": ["access_lists"] "bearerAuth": [
"access_lists.manage"
]
} }
], ],
"requestBody": { "requestBody": {
@@ -15,7 +17,9 @@
"schema": { "schema": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["name"], "required": [
"name"
],
"properties": { "properties": {
"name": { "name": {
"$ref": "../../../components/access-list-object.json#/properties/name" "$ref": "../../../components/access-list-object.json#/properties/name"
@@ -27,54 +31,29 @@
"$ref": "../../../components/access-list-object.json#/properties/pass_auth" "$ref": "../../../components/access-list-object.json#/properties/pass_auth"
}, },
"items": { "items": {
"type": "array", "$ref": "../../../common.json#/properties/access_items"
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"username": {
"type": "string",
"minLength": 1
},
"password": {
"type": "string",
"minLength": 1
}
}
}
}, },
"clients": { "clients": {
"type": "array", "$ref": "../../../common.json#/properties/access_clients"
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"address": {
"oneOf": [
{
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
},
{
"type": "string",
"pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
},
{
"type": "string",
"pattern": "^all$"
}
]
},
"directive": {
"$ref": "../../../components/access-list-object.json#/properties/directive"
}
}
}
},
"meta": {
"$ref": "../../../components/access-list-object.json#/properties/meta"
} }
} }
},
"example": {
"name": "My Access List",
"satisfy_any": true,
"pass_auth": false,
"items": [
{
"username": "admin",
"password": "pass"
}
],
"clients": [
{
"directive": "allow",
"address": "192.168.0.0/24"
}
]
} }
} }
} }
@@ -100,13 +79,14 @@
"id": 1, "id": 1,
"created_on": "2024-10-07T22:43:55.000Z", "created_on": "2024-10-07T22:43:55.000Z",
"modified_on": "2024-10-08T12:52:54.000Z", "modified_on": "2024-10-08T12:52:54.000Z",
"is_deleted": false,
"is_disabled": false, "is_disabled": false,
"email": "admin@example.com", "email": "admin@example.com",
"name": "Administrator", "name": "Administrator",
"nickname": "some guy", "nickname": "some guy",
"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm", "avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm",
"roles": ["admin"] "roles": [
"admin"
]
}, },
"items": [ "items": [
{ {

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "deleteCertificate", "operationId": "deleteCertificate",
"summary": "Delete a Certificate", "summary": "Delete a Certificate",
"tags": ["Certificates"], "tags": ["certificates"],
"security": [ "security": [
{ {
"BearerAuth": ["certificates"] "bearerAuth": ["certificates.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "certID", "name": "certID",
"description": "Certificate ID",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "downloadCertificate", "operationId": "downloadCertificate",
"summary": "Downloads a Certificate", "summary": "Downloads a Certificate",
"tags": ["Certificates"], "tags": ["certificates"],
"security": [ "security": [
{ {
"BearerAuth": ["certificates"] "bearerAuth": ["certificates.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "certID", "name": "certID",
"description": "Certificate ID",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "getCertificate", "operationId": "getCertificate",
"summary": "Get a Certificate", "summary": "Get a Certificate",
"tags": ["Certificates"], "tags": ["certificates"],
"security": [ "security": [
{ {
"BearerAuth": ["certificates"] "bearerAuth": ["certificates.view"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "certID", "name": "certID",
"description": "Certificate ID",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1
@@ -36,8 +37,6 @@
"domain_names": ["test.example.com"], "domain_names": ["test.example.com"],
"expires_on": "2025-01-07T04:34:18.000Z", "expires_on": "2025-01-07T04:34:18.000Z",
"meta": { "meta": {
"letsencrypt_email": "jc@jc21.com",
"letsencrypt_agree": true,
"dns_challenge": false "dns_challenge": false
} }
} }

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "renewCertificate", "operationId": "renewCertificate",
"summary": "Renews a Certificate", "summary": "Renews a Certificate",
"tags": ["Certificates"], "tags": ["certificates"],
"security": [ "security": [
{ {
"BearerAuth": ["certificates"] "bearerAuth": ["certificates.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "certID", "name": "certID",
"description": "Certificate ID",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1
@@ -32,13 +33,10 @@
"id": 4, "id": 4,
"created_on": "2024-10-09T05:31:58.000Z", "created_on": "2024-10-09T05:31:58.000Z",
"owner_user_id": 1, "owner_user_id": 1,
"is_deleted": false,
"provider": "letsencrypt", "provider": "letsencrypt",
"nice_name": "My Test Cert", "nice_name": "My Test Cert",
"domain_names": ["test.jc21.supernerd.pro"], "domain_names": ["test.jc21.supernerd.pro"],
"meta": { "meta": {
"letsencrypt_email": "jc@jc21.com",
"letsencrypt_agree": true,
"dns_challenge": false "dns_challenge": false
} }
} }

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "uploadCertificate", "operationId": "uploadCertificate",
"summary": "Uploads a custom Certificate", "summary": "Uploads a custom Certificate",
"tags": ["Certificates"], "tags": ["certificates"],
"security": [ "security": [
{ {
"BearerAuth": ["certificates"] "bearerAuth": ["certificates.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "certID", "name": "certID",
"description": "Certificate ID",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1
@@ -20,28 +21,7 @@
} }
], ],
"requestBody": { "requestBody": {
"description": "Certificate Files", "$ref": "../../../../../common.json#/properties/certificate_files"
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["certificate", "certificate_key"],
"properties": {
"certificate": {
"type": "string"
},
"certificate_key": {
"type": "string"
},
"intermediate_certificate": {
"type": "string"
}
}
}
}
}
}, },
"responses": { "responses": {
"200": { "200": {
@@ -63,15 +43,18 @@
"properties": { "properties": {
"certificate": { "certificate": {
"type": "string", "type": "string",
"minLength": 1 "minLength": 1,
"example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----"
}, },
"certificate_key": { "certificate_key": {
"type": "string", "type": "string",
"minLength": 1 "minLength": 1,
"example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----"
}, },
"intermediate_certificate": { "intermediate_certificate": {
"type": "string", "type": "string",
"minLength": 1 "minLength": 1,
"example": "-----BEGIN CERTIFICATE-----\nMIID...-----END CERTIFICATE-----"
} }
} }
} }

View File

@@ -0,0 +1,48 @@
{
"operationId": "getDNSProviders",
"summary": "Get DNS Providers for Certificates",
"tags": ["certificates"],
"security": [
{
"bearerAuth": ["certificates.view"]
}
],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": [
{
"id": "vultr",
"name": "Vultr",
"credentials": "dns_vultr_key = YOUR_VULTR_API_KEY"
},
{
"id": "websupport",
"name": "Websupport.sk",
"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>"
},
{
"id": "wedos",
"name": "Wedos",
"credentials": "dns_wedos_user = <wedos_registration>\ndns_wedos_auth = <wapi_password>"
},
{
"id": "zoneedit",
"name": "ZoneEdit",
"credentials": "dns_zoneedit_user = <login-user-id>\ndns_zoneedit_token = <dyn-authentication-token>"
}
]
}
},
"schema": {
"$ref": "../../../../components/dns-providers-list.json"
}
}
}
}
}
}

View File

@@ -1,10 +1,10 @@
{ {
"operationId": "getCertificates", "operationId": "getCertificates",
"summary": "Get all certificates", "summary": "Get all certificates",
"tags": ["Certificates"], "tags": ["certificates"],
"security": [ "security": [
{ {
"BearerAuth": ["certificates"] "bearerAuth": ["certificates.view"]
} }
], ],
"parameters": [ "parameters": [
@@ -36,8 +36,6 @@
"domain_names": ["test.example.com"], "domain_names": ["test.example.com"],
"expires_on": "2025-01-07T04:34:18.000Z", "expires_on": "2025-01-07T04:34:18.000Z",
"meta": { "meta": {
"letsencrypt_email": "jc@jc21.com",
"letsencrypt_agree": true,
"dns_challenge": false "dns_challenge": false
} }
} }

View File

@@ -1,10 +1,10 @@
{ {
"operationId": "createCertificate", "operationId": "createCertificate",
"summary": "Create a Certificate", "summary": "Create a Certificate",
"tags": ["Certificates"], "tags": ["certificates"],
"security": [ "security": [
{ {
"BearerAuth": ["certificates"] "bearerAuth": ["certificates.manage"]
} }
], ],
"requestBody": { "requestBody": {
@@ -30,6 +30,13 @@
"$ref": "../../../components/certificate-object.json#/properties/meta" "$ref": "../../../components/certificate-object.json#/properties/meta"
} }
} }
},
"example": {
"provider": "letsencrypt",
"domain_names": ["test.example.com"],
"meta": {
"dns_challenge": false
}
} }
} }
} }
@@ -47,13 +54,10 @@
"id": 5, "id": 5,
"created_on": "2024-10-09 05:28:35", "created_on": "2024-10-09 05:28:35",
"owner_user_id": 1, "owner_user_id": 1,
"is_deleted": false,
"provider": "letsencrypt", "provider": "letsencrypt",
"nice_name": "test.example.com", "nice_name": "test.example.com",
"domain_names": ["test.example.com"], "domain_names": ["test.example.com"],
"meta": { "meta": {
"letsencrypt_email": "jc@jc21.com",
"letsencrypt_agree": true,
"dns_challenge": false, "dns_challenge": false,
"letsencrypt_certificate": { "letsencrypt_certificate": {
"cn": "test.example.com", "cn": "test.example.com",

View File

@@ -1,24 +1,30 @@
{ {
"operationId": "testHttpReach", "operationId": "testHttpReach",
"summary": "Test HTTP Reachability", "summary": "Test HTTP Reachability",
"tags": ["Certificates"], "tags": ["certificates"],
"security": [ "security": [
{ {
"BearerAuth": ["certificates"] "bearerAuth": ["certificates.view"]
} }
], ],
"parameters": [ "requestBody": {
{ "description": "Test Payload",
"in": "query", "required": true,
"name": "domains", "content": {
"description": "Expansions", "application/json": {
"required": true, "schema": {
"schema": { "type": "object",
"type": "string", "additionalProperties": false,
"example": "[\"test.example.ord\",\"test.example.com\",\"nonexistent.example.com\"]" "required": ["domains"],
"properties": {
"domains": {
"$ref": "../../../../common.json#/properties/domain_names"
}
}
}
} }
} }
], },
"responses": { "responses": {
"200": { "200": {
"description": "200 response", "description": "200 response",

View File

@@ -1,35 +1,14 @@
{ {
"operationId": "validateCertificates", "operationId": "validateCertificates",
"summary": "Validates given Custom Certificates", "summary": "Validates given Custom Certificates",
"tags": ["Certificates"], "tags": ["certificates"],
"security": [ "security": [
{ {
"BearerAuth": ["certificates"] "bearerAuth": ["certificates.manage"]
} }
], ],
"requestBody": { "requestBody": {
"description": "Certificate Files", "$ref": "../../../../common.json#/properties/certificate_files"
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["certificate", "certificate_key"],
"properties": {
"certificate": {
"type": "string"
},
"certificate_key": {
"type": "string"
},
"intermediate_certificate": {
"type": "string"
}
}
}
}
}
}, },
"responses": { "responses": {
"200": { "200": {
@@ -62,10 +41,12 @@
"required": ["cn", "issuer", "dates"], "required": ["cn", "issuer", "dates"],
"properties": { "properties": {
"cn": { "cn": {
"type": "string" "type": "string",
"example": "example.com"
}, },
"issuer": { "issuer": {
"type": "string" "type": "string",
"example": "C = US, O = Let's Encrypt, CN = E5"
}, },
"dates": { "dates": {
"type": "object", "type": "object",
@@ -78,12 +59,17 @@
"to": { "to": {
"type": "integer" "type": "integer"
} }
},
"example": {
"from": 1728448218,
"to": 1736224217
} }
} }
} }
}, },
"certificate_key": { "certificate_key": {
"type": "boolean" "type": "boolean",
"example": true
} }
} }
} }

View File

@@ -1,10 +1,10 @@
{ {
"operationId": "getDeadHosts", "operationId": "getDeadHosts",
"summary": "Get all 404 hosts", "summary": "Get all 404 hosts",
"tags": ["404 Hosts"], "tags": ["404-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["dead_hosts"] "bearerAuth": ["dead_hosts.view"]
} }
], ],
"parameters": [ "parameters": [

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "deleteDeadHost", "operationId": "deleteDeadHost",
"summary": "Delete a 404 Host", "summary": "Delete a 404 Host",
"tags": ["404 Hosts"], "tags": ["404-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["dead_hosts"] "bearerAuth": ["dead_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the 404 Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "disableDeadHost", "operationId": "disableDeadHost",
"summary": "Disable a 404 Host", "summary": "Disable a 404 Host",
"tags": ["404 Hosts"], "tags": ["404-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["dead_hosts"] "bearerAuth": ["dead_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the 404 Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "enableDeadHost", "operationId": "enableDeadHost",
"summary": "Enable a 404 Host", "summary": "Enable a 404 Host",
"tags": ["404 Hosts"], "tags": ["404-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["dead_hosts"] "bearerAuth": ["dead_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the 404 Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "getDeadHost", "operationId": "getDeadHost",
"summary": "Get a 404 Host", "summary": "Get a 404 Host",
"tags": ["404 Hosts"], "tags": ["404-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["dead_hosts"] "bearerAuth": ["dead_hosts.view"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the 404 Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "updateDeadHost", "operationId": "updateDeadHost",
"summary": "Update a 404 Host", "summary": "Update a 404 Host",
"tags": ["404 Hosts"], "tags": ["404-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["dead_hosts"] "bearerAuth": ["dead_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the 404 Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1
@@ -86,7 +87,6 @@
"id": 1, "id": 1,
"created_on": "2024-10-09T00:59:56.000Z", "created_on": "2024-10-09T00:59:56.000Z",
"modified_on": "2024-10-09T00:59:56.000Z", "modified_on": "2024-10-09T00:59:56.000Z",
"is_deleted": false,
"is_disabled": false, "is_disabled": false,
"email": "admin@example.com", "email": "admin@example.com",
"name": "Administrator", "name": "Administrator",

View File

@@ -1,10 +1,12 @@
{ {
"operationId": "create404Host", "operationId": "create404Host",
"summary": "Create a 404 Host", "summary": "Create a 404 Host",
"tags": ["404 Hosts"], "tags": ["404-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["dead_hosts"] "bearerAuth": [
"dead_hosts.manage"
]
} }
], ],
"requestBody": { "requestBody": {
@@ -15,7 +17,9 @@
"schema": { "schema": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["domain_names"], "required": [
"domain_names"
],
"properties": { "properties": {
"domain_names": { "domain_names": {
"$ref": "../../../components/dead-host-object.json#/properties/domain_names" "$ref": "../../../components/dead-host-object.json#/properties/domain_names"
@@ -42,6 +46,18 @@
"$ref": "../../../components/dead-host-object.json#/properties/meta" "$ref": "../../../components/dead-host-object.json#/properties/meta"
} }
} }
},
"example": {
"domain_names": [
"test.example.com"
],
"certificate_id": 0,
"ssl_forced": false,
"advanced_config": "",
"http2_support": false,
"hsts_enabled": false,
"hsts_subdomains": false,
"meta": {}
} }
} }
} }
@@ -58,7 +74,9 @@
"created_on": "2024-10-09T01:38:52.000Z", "created_on": "2024-10-09T01:38:52.000Z",
"modified_on": "2024-10-09T01:38:52.000Z", "modified_on": "2024-10-09T01:38:52.000Z",
"owner_user_id": 1, "owner_user_id": 1,
"domain_names": ["test.example.com"], "domain_names": [
"test.example.com"
],
"certificate_id": 0, "certificate_id": 0,
"ssl_forced": false, "ssl_forced": false,
"advanced_config": "", "advanced_config": "",
@@ -72,13 +90,14 @@
"id": 1, "id": 1,
"created_on": "2024-10-09T00:59:56.000Z", "created_on": "2024-10-09T00:59:56.000Z",
"modified_on": "2024-10-09T00:59:56.000Z", "modified_on": "2024-10-09T00:59:56.000Z",
"is_deleted": false,
"is_disabled": false, "is_disabled": false,
"email": "admin@example.com", "email": "admin@example.com",
"name": "Administrator", "name": "Administrator",
"nickname": "Admin", "nickname": "Admin",
"avatar": "", "avatar": "",
"roles": ["admin"] "roles": [
"admin"
]
} }
} }
} }

View File

@@ -1,10 +1,12 @@
{ {
"operationId": "getProxyHosts", "operationId": "getProxyHosts",
"summary": "Get all proxy hosts", "summary": "Get all proxy hosts",
"tags": ["Proxy Hosts"], "tags": ["proxy-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["proxy_hosts"] "bearerAuth": [
"proxy_hosts.view"
]
} }
], ],
"parameters": [ "parameters": [
@@ -14,7 +16,11 @@
"description": "Expansions", "description": "Expansions",
"schema": { "schema": {
"type": "string", "type": "string",
"enum": ["access_list", "owner", "certificate"] "enum": [
"access_list",
"owner",
"certificate"
]
} }
} }
], ],
@@ -28,14 +34,16 @@
"value": [ "value": [
{ {
"id": 1, "id": 1,
"created_on": "2024-10-08T23:23:03.000Z", "created_on": "2025-10-28T01:10:26.000Z",
"modified_on": "2024-10-08T23:23:04.000Z", "modified_on": "2025-10-28T04:07:16.000Z",
"owner_user_id": 1, "owner_user_id": 1,
"domain_names": ["test.example.com"], "domain_names": [
"test.jc21com"
],
"forward_host": "127.0.0.1", "forward_host": "127.0.0.1",
"forward_port": 8989, "forward_port": 8081,
"access_list_id": 0, "access_list_id": 1,
"certificate_id": 0, "certificate_id": 1,
"ssl_forced": false, "ssl_forced": false,
"caching_enabled": false, "caching_enabled": false,
"block_exploits": false, "block_exploits": false,
@@ -48,7 +56,7 @@
"http2_support": false, "http2_support": false,
"forward_scheme": "http", "forward_scheme": "http",
"enabled": true, "enabled": true,
"locations": null, "locations": [],
"hsts_enabled": false, "hsts_enabled": false,
"hsts_subdomains": false "hsts_subdomains": false
} }

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "deleteProxyHost", "operationId": "deleteProxyHost",
"summary": "Delete a Proxy Host", "summary": "Delete a Proxy Host",
"tags": ["Proxy Hosts"], "tags": ["proxy-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["proxy_hosts"] "bearerAuth": ["proxy_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the Proxy Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "disableProxyHost", "operationId": "disableProxyHost",
"summary": "Disable a Proxy Host", "summary": "Disable a Proxy Host",
"tags": ["Proxy Hosts"], "tags": ["proxy-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["proxy_hosts"] "bearerAuth": ["proxy_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the Proxy Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "enableProxyHost", "operationId": "enableProxyHost",
"summary": "Enable a Proxy Host", "summary": "Enable a Proxy Host",
"tags": ["Proxy Hosts"], "tags": ["proxy-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["proxy_hosts"] "bearerAuth": ["proxy_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the Proxy Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,19 @@
{ {
"operationId": "getProxyHost", "operationId": "getProxyHost",
"summary": "Get a Proxy Host", "summary": "Get a Proxy Host",
"tags": ["Proxy Hosts"], "tags": ["proxy-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["proxy_hosts"] "bearerAuth": [
"proxy_hosts.view"
]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the Proxy Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1
@@ -27,13 +30,15 @@
"examples": { "examples": {
"default": { "default": {
"value": { "value": {
"id": 1, "id": 3,
"created_on": "2024-10-08T23:23:03.000Z", "created_on": "2025-10-30T01:12:05.000Z",
"modified_on": "2024-10-08T23:26:38.000Z", "modified_on": "2025-10-30T01:12:05.000Z",
"owner_user_id": 1, "owner_user_id": 1,
"domain_names": ["test.example.com"], "domain_names": [
"forward_host": "192.168.0.10", "test.example.com"
"forward_port": 8989, ],
"forward_host": "127.0.0.1",
"forward_port": 8080,
"access_list_id": 0, "access_list_id": 0,
"certificate_id": 0, "certificate_id": 0,
"ssl_forced": false, "ssl_forced": false,
@@ -48,9 +53,22 @@
"http2_support": false, "http2_support": false,
"forward_scheme": "http", "forward_scheme": "http",
"enabled": true, "enabled": true,
"locations": null, "locations": [],
"hsts_enabled": false, "hsts_enabled": false,
"hsts_subdomains": false "hsts_subdomains": false,
"owner": {
"id": 1,
"created_on": "2025-10-28T00:50:24.000Z",
"modified_on": "2025-10-28T00:50:24.000Z",
"is_disabled": false,
"email": "jc@jc21.com",
"name": "jamiec",
"nickname": "jamiec",
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
"roles": [
"admin"
]
}
} }
} }
}, },

View File

@@ -1,16 +1,19 @@
{ {
"operationId": "updateProxyHost", "operationId": "updateProxyHost",
"summary": "Update a Proxy Host", "summary": "Update a Proxy Host",
"tags": ["Proxy Hosts"], "tags": ["proxy-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["proxy_hosts"] "bearerAuth": [
"proxy_hosts.manage"
]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the Proxy Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1
@@ -93,13 +96,15 @@
"examples": { "examples": {
"default": { "default": {
"value": { "value": {
"id": 1, "id": 3,
"created_on": "2024-10-08T23:23:03.000Z", "created_on": "2025-10-30T01:12:05.000Z",
"modified_on": "2024-10-08T23:26:37.000Z", "modified_on": "2025-10-30T01:17:06.000Z",
"owner_user_id": 1, "owner_user_id": 1,
"domain_names": ["test.example.com"], "domain_names": [
"forward_host": "192.168.0.10", "test.example.com"
"forward_port": 8989, ],
"forward_host": "127.0.0.1",
"forward_port": 8080,
"access_list_id": 0, "access_list_id": 0,
"certificate_id": 0, "certificate_id": 0,
"ssl_forced": false, "ssl_forced": false,
@@ -114,19 +119,21 @@
"http2_support": false, "http2_support": false,
"forward_scheme": "http", "forward_scheme": "http",
"enabled": true, "enabled": true,
"locations": [],
"hsts_enabled": false, "hsts_enabled": false,
"hsts_subdomains": false, "hsts_subdomains": false,
"owner": { "owner": {
"id": 1, "id": 1,
"created_on": "2024-10-07T22:43:55.000Z", "created_on": "2025-10-28T00:50:24.000Z",
"modified_on": "2024-10-08T12:52:54.000Z", "modified_on": "2025-10-28T00:50:24.000Z",
"is_deleted": false,
"is_disabled": false, "is_disabled": false,
"email": "admin@example.com", "email": "jc@jc21.com",
"name": "Administrator", "name": "jamiec",
"nickname": "some guy", "nickname": "jamiec",
"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm", "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
"roles": ["admin"] "roles": [
"admin"
]
}, },
"certificate": null, "certificate": null,
"access_list": null "access_list": null

View File

@@ -1,10 +1,12 @@
{ {
"operationId": "createProxyHost", "operationId": "createProxyHost",
"summary": "Create a Proxy Host", "summary": "Create a Proxy Host",
"tags": ["Proxy Hosts"], "tags": ["proxy-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["proxy_hosts"] "bearerAuth": [
"proxy_hosts.manage"
]
} }
], ],
"requestBody": { "requestBody": {
@@ -15,7 +17,12 @@
"schema": { "schema": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["domain_names", "forward_scheme", "forward_host", "forward_port"], "required": [
"domain_names",
"forward_scheme",
"forward_host",
"forward_port"
],
"properties": { "properties": {
"domain_names": { "domain_names": {
"$ref": "../../../components/proxy-host-object.json#/properties/domain_names" "$ref": "../../../components/proxy-host-object.json#/properties/domain_names"
@@ -69,6 +76,14 @@
"$ref": "../../../components/proxy-host-object.json#/properties/locations" "$ref": "../../../components/proxy-host-object.json#/properties/locations"
} }
} }
},
"example": {
"domain_names": [
"test.example.com"
],
"forward_scheme": "http",
"forward_host": "127.0.0.1",
"forward_port": 8080
} }
} }
} }
@@ -81,13 +96,15 @@
"examples": { "examples": {
"default": { "default": {
"value": { "value": {
"id": 1, "id": 3,
"created_on": "2024-10-08T23:23:03.000Z", "created_on": "2025-10-30T01:12:05.000Z",
"modified_on": "2024-10-08T23:23:03.000Z", "modified_on": "2025-10-30T01:12:05.000Z",
"owner_user_id": 1, "owner_user_id": 1,
"domain_names": ["test.example.com"], "domain_names": [
"test.example.com"
],
"forward_host": "127.0.0.1", "forward_host": "127.0.0.1",
"forward_port": 8989, "forward_port": 8080,
"access_list_id": 0, "access_list_id": 0,
"certificate_id": 0, "certificate_id": 0,
"ssl_forced": false, "ssl_forced": false,
@@ -99,20 +116,22 @@
"http2_support": false, "http2_support": false,
"forward_scheme": "http", "forward_scheme": "http",
"enabled": true, "enabled": true,
"locations": [],
"hsts_enabled": false, "hsts_enabled": false,
"hsts_subdomains": false, "hsts_subdomains": false,
"certificate": null, "certificate": null,
"owner": { "owner": {
"id": 1, "id": 1,
"created_on": "2024-10-07T22:43:55.000Z", "created_on": "2025-10-28T00:50:24.000Z",
"modified_on": "2024-10-08T12:52:54.000Z", "modified_on": "2025-10-28T00:50:24.000Z",
"is_deleted": false,
"is_disabled": false, "is_disabled": false,
"email": "admin@example.com", "email": "jc@jc21.com",
"name": "Administrator", "name": "jamiec",
"nickname": "some guy", "nickname": "jamiec",
"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm", "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
"roles": ["admin"] "roles": [
"admin"
]
}, },
"access_list": null "access_list": null
} }

View File

@@ -1,10 +1,10 @@
{ {
"operationId": "getRedirectionHosts", "operationId": "getRedirectionHosts",
"summary": "Get all Redirection hosts", "summary": "Get all Redirection hosts",
"tags": ["Redirection Hosts"], "tags": ["redirection-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["redirection_hosts"] "bearerAuth": ["redirection_hosts.view"]
} }
], ],
"parameters": [ "parameters": [

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "deleteRedirectionHost", "operationId": "deleteRedirectionHost",
"summary": "Delete a Redirection Host", "summary": "Delete a Redirection Host",
"tags": ["Redirection Hosts"], "tags": ["redirection-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["redirection_hosts"] "bearerAuth": ["redirection_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the Redirection Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "disableRedirectionHost", "operationId": "disableRedirectionHost",
"summary": "Disable a Redirection Host", "summary": "Disable a Redirection Host",
"tags": ["Redirection Hosts"], "tags": ["redirection-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["redirection_hosts"] "bearerAuth": ["redirection_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the Redirection Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "enableRedirectionHost", "operationId": "enableRedirectionHost",
"summary": "Enable a Redirection Host", "summary": "Enable a Redirection Host",
"tags": ["Redirection Hosts"], "tags": ["redirection-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["redirection_hosts"] "bearerAuth": ["redirection_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the Redirection Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "getRedirectionHost", "operationId": "getRedirectionHost",
"summary": "Get a Redirection Host", "summary": "Get a Redirection Host",
"tags": ["Redirection Hosts"], "tags": ["redirection-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["redirection_hosts"] "bearerAuth": ["redirection_hosts.view"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the Redirection Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "updateRedirectionHost", "operationId": "updateRedirectionHost",
"summary": "Update a Redirection Host", "summary": "Update a Redirection Host",
"tags": ["Redirection Hosts"], "tags": ["redirection-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["redirection_hosts"] "bearerAuth": ["redirection_hosts.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "hostID", "name": "hostID",
"description": "The ID of the Redirection Host",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1
@@ -106,7 +107,6 @@
"id": 1, "id": 1,
"created_on": "2024-10-09T00:59:56.000Z", "created_on": "2024-10-09T00:59:56.000Z",
"modified_on": "2024-10-09T00:59:56.000Z", "modified_on": "2024-10-09T00:59:56.000Z",
"is_deleted": false,
"is_disabled": false, "is_disabled": false,
"email": "admin@example.com", "email": "admin@example.com",
"name": "Administrator", "name": "Administrator",

View File

@@ -1,10 +1,12 @@
{ {
"operationId": "createRedirectionHost", "operationId": "createRedirectionHost",
"summary": "Create a Redirection Host", "summary": "Create a Redirection Host",
"tags": ["Redirection Hosts"], "tags": ["redirection-hosts"],
"security": [ "security": [
{ {
"BearerAuth": ["redirection_hosts"] "bearerAuth": [
"redirection_hosts.manage"
]
} }
], ],
"requestBody": { "requestBody": {
@@ -15,7 +17,12 @@
"schema": { "schema": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["domain_names", "forward_scheme", "forward_http_code", "forward_domain_name"], "required": [
"domain_names",
"forward_scheme",
"forward_http_code",
"forward_domain_name"
],
"properties": { "properties": {
"domain_names": { "domain_names": {
"$ref": "../../../components/redirection-host-object.json#/properties/domain_names" "$ref": "../../../components/redirection-host-object.json#/properties/domain_names"
@@ -57,6 +64,23 @@
"$ref": "../../../components/redirection-host-object.json#/properties/meta" "$ref": "../../../components/redirection-host-object.json#/properties/meta"
} }
} }
},
"example": {
"domain_names": [
"test.example.com"
],
"forward_domain_name": "example.com",
"forward_scheme": "auto",
"forward_http_code": 301,
"preserve_path": false,
"block_exploits": false,
"certificate_id": 0,
"ssl_forced": false,
"http2_support": false,
"hsts_enabled": false,
"hsts_subdomains": false,
"advanced_config": "",
"meta": {}
} }
} }
} }
@@ -69,12 +93,14 @@
"examples": { "examples": {
"default": { "default": {
"value": { "value": {
"id": 1, "id": 2,
"created_on": "2024-10-09T01:13:12.000Z", "created_on": "2025-10-30T01:27:04.000Z",
"modified_on": "2024-10-09T01:13:12.000Z", "modified_on": "2025-10-30T01:27:04.000Z",
"owner_user_id": 1, "owner_user_id": 1,
"domain_names": ["test.example.com"], "domain_names": [
"forward_domain_name": "something-else.com", "test.example.com"
],
"forward_domain_name": "example.com",
"preserve_path": false, "preserve_path": false,
"certificate_id": 0, "certificate_id": 0,
"ssl_forced": false, "ssl_forced": false,
@@ -85,20 +111,21 @@
"enabled": true, "enabled": true,
"hsts_enabled": false, "hsts_enabled": false,
"hsts_subdomains": false, "hsts_subdomains": false,
"forward_scheme": "http", "forward_scheme": "auto",
"forward_http_code": 301, "forward_http_code": 301,
"certificate": null, "certificate": null,
"owner": { "owner": {
"id": 1, "id": 1,
"created_on": "2024-10-09T00:59:56.000Z", "created_on": "2025-10-28T00:50:24.000Z",
"modified_on": "2024-10-09T00:59:56.000Z", "modified_on": "2025-10-28T00:50:24.000Z",
"is_deleted": false,
"is_disabled": false, "is_disabled": false,
"email": "admin@example.com", "email": "jc@jc21.com",
"name": "Administrator", "name": "jamiec",
"nickname": "Admin", "nickname": "jamiec",
"avatar": "", "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
"roles": ["admin"] "roles": [
"admin"
]
} }
} }
} }

View File

@@ -1,10 +1,10 @@
{ {
"operationId": "getStreams", "operationId": "getStreams",
"summary": "Get all streams", "summary": "Get all streams",
"tags": ["Streams"], "tags": ["streams"],
"security": [ "security": [
{ {
"BearerAuth": ["streams"] "bearerAuth": ["streams.view"]
} }
], ],
"parameters": [ "parameters": [

View File

@@ -1,10 +1,12 @@
{ {
"operationId": "createStream", "operationId": "createStream",
"summary": "Create a Stream", "summary": "Create a Stream",
"tags": ["Streams"], "tags": ["streams"],
"security": [ "security": [
{ {
"BearerAuth": ["streams"] "bearerAuth": [
"streams.manage"
]
} }
], ],
"requestBody": { "requestBody": {
@@ -15,7 +17,11 @@
"schema": { "schema": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["incoming_port", "forwarding_host", "forwarding_port"], "required": [
"incoming_port",
"forwarding_host",
"forwarding_port"
],
"properties": { "properties": {
"incoming_port": { "incoming_port": {
"$ref": "../../../components/stream-object.json#/properties/incoming_port" "$ref": "../../../components/stream-object.json#/properties/incoming_port"
@@ -37,8 +43,20 @@
}, },
"meta": { "meta": {
"$ref": "../../../components/stream-object.json#/properties/meta" "$ref": "../../../components/stream-object.json#/properties/meta"
},
"domain_names": {
"$ref": "../../../components/dead-host-object.json#/properties/domain_names"
} }
} }
},
"example": {
"incoming_port": 8888,
"forwarding_host": "127.0.0.1",
"forwarding_port": 8080,
"tcp_forwarding": true,
"udp_forwarding": false,
"certificate_id": 0,
"meta": {}
} }
} }
} }
@@ -69,13 +87,14 @@
"id": 1, "id": 1,
"created_on": "2024-10-09T02:33:16.000Z", "created_on": "2024-10-09T02:33:16.000Z",
"modified_on": "2024-10-09T02:33:16.000Z", "modified_on": "2024-10-09T02:33:16.000Z",
"is_deleted": false,
"is_disabled": false, "is_disabled": false,
"email": "admin@example.com", "email": "admin@example.com",
"name": "Administrator", "name": "Administrator",
"nickname": "Admin", "nickname": "Admin",
"avatar": "", "avatar": "",
"roles": ["admin"] "roles": [
"admin"
]
}, },
"certificate_id": 0 "certificate_id": 0
} }

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "deleteStream", "operationId": "deleteStream",
"summary": "Delete a Stream", "summary": "Delete a Stream",
"tags": ["Streams"], "tags": ["streams"],
"security": [ "security": [
{ {
"BearerAuth": ["streams"] "bearerAuth": ["streams.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "streamID", "name": "streamID",
"description": "The ID of the Stream",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "disableStream", "operationId": "disableStream",
"summary": "Disable a Stream", "summary": "Disable a Stream",
"tags": ["Streams"], "tags": ["streams"],
"security": [ "security": [
{ {
"BearerAuth": ["streams"] "bearerAuth": ["streams.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "streamID", "name": "streamID",
"description": "The ID of the Stream",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "enableStream", "operationId": "enableStream",
"summary": "Enable a Stream", "summary": "Enable a Stream",
"tags": ["Streams"], "tags": ["streams"],
"security": [ "security": [
{ {
"BearerAuth": ["streams"] "bearerAuth": ["streams.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "streamID", "name": "streamID",
"description": "The ID of the Stream",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "getStream", "operationId": "getStream",
"summary": "Get a Stream", "summary": "Get a Stream",
"tags": ["Streams"], "tags": ["streams"],
"security": [ "security": [
{ {
"BearerAuth": ["streams"] "bearerAuth": ["streams.view"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "streamID", "name": "streamID",
"description": "The ID of the Stream",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1

View File

@@ -1,16 +1,17 @@
{ {
"operationId": "updateStream", "operationId": "updateStream",
"summary": "Update a Stream", "summary": "Update a Stream",
"tags": ["Streams"], "tags": ["streams"],
"security": [ "security": [
{ {
"BearerAuth": ["streams"] "bearerAuth": ["streams.manage"]
} }
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "streamID", "name": "streamID",
"description": "The ID of the Stream",
"schema": { "schema": {
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1
@@ -81,7 +82,6 @@
"id": 1, "id": 1,
"created_on": "2024-10-09T02:33:16.000Z", "created_on": "2024-10-09T02:33:16.000Z",
"modified_on": "2024-10-09T02:33:16.000Z", "modified_on": "2024-10-09T02:33:16.000Z",
"is_deleted": false,
"is_disabled": false, "is_disabled": false,
"email": "admin@example.com", "email": "admin@example.com",
"name": "Administrator", "name": "Administrator",

View File

@@ -1,10 +1,10 @@
{ {
"operationId": "reportsHosts", "operationId": "reportsHosts",
"summary": "Report on Host Statistics", "summary": "Report on Host Statistics",
"tags": ["Reports"], "tags": ["reports"],
"security": [ "security": [
{ {
"BearerAuth": ["reports"] "bearerAuth": []
} }
], ],
"responses": { "responses": {
@@ -27,19 +27,23 @@
"properties": { "properties": {
"proxy": { "proxy": {
"type": "integer", "type": "integer",
"description": "Proxy Hosts Count" "description": "Proxy Hosts Count",
"example": 20
}, },
"redirection": { "redirection": {
"type": "integer", "type": "integer",
"description": "Redirection Hosts Count" "description": "Redirection Hosts Count",
"example": 2
}, },
"stream": { "stream": {
"type": "integer", "type": "integer",
"description": "Streams Count" "description": "Streams Count",
"example": 0
}, },
"dead": { "dead": {
"type": "integer", "type": "integer",
"description": "404 Hosts Count" "description": "404 Hosts Count",
"example": 3
} }
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"operationId": "schema", "operationId": "schema",
"summary": "Returns this swagger API schema", "summary": "Returns this swagger API schema",
"tags": ["Public"], "tags": ["public"],
"responses": { "responses": {
"200": { "200": {
"description": "200 response" "description": "200 response"

View File

@@ -1,10 +1,10 @@
{ {
"operationId": "getSettings", "operationId": "getSettings",
"summary": "Get all settings", "summary": "Get all settings",
"tags": ["Settings"], "tags": ["settings"],
"security": [ "security": [
{ {
"BearerAuth": ["settings"] "bearerAuth": ["admin"]
} }
], ],
"responses": { "responses": {

Some files were not shown because too many files have changed in this diff Show More