Explorar o código

Fix cypress tests following user wizard changes

Jamie Curnow hai 3 meses
pai
achega
fb2708d81d

+ 9 - 1
backend/internal/user.js

@@ -9,7 +9,7 @@ import internalAuditLog from "./audit-log.js";
 import internalToken from "./token.js";
 
 const omissions = () => {
-	return ["is_deleted"];
+	return ["is_deleted", "permissions.id", "permissions.user_id", "permissions.created_on", "permissions.modified_on"];
 };
 
 const DEFAULT_AVATAR = gravatar.url("[email protected]", { default: "mm" });
@@ -250,6 +250,14 @@ const internalUser = {
 			});
 	},
 
+	deleteAll: async () => {
+		await userModel
+			.query()
+			.patch({
+				is_deleted: 1,
+			});
+	},
+
 	/**
 	 * This will only count the users
 	 *

+ 8 - 1
backend/lib/config.js

@@ -199,6 +199,13 @@ const isPostgres = () => {
  */
 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
  *
@@ -234,4 +241,4 @@ const useLetsencryptServer = () => {
 	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 };

+ 4 - 1
backend/lib/error.js

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

+ 24 - 23
backend/routes/audit-log.js

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

+ 80 - 76
backend/routes/nginx/access_lists.js

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

+ 125 - 111
backend/routes/nginx/certificates.js

@@ -4,6 +4,7 @@ import errs from "../../lib/error.js";
 import jwtdecode from "../../lib/express/jwt-decode.js";
 import apiValidator from "../../lib/validator/api.js";
 import validator from "../../lib/validator/index.js";
+import { express as logger } from "../../logger.js";
 import { getValidationSchema } from "../../schema/index.js";
 
 const router = express.Router({
@@ -27,31 +28,31 @@ router
 	 *
 	 * Retrieve all certificates
 	 */
-	.get((req, res, next) => {
-		validator(
-			{
-				additionalProperties: false,
-				properties: {
-					expand: {
-						$ref: "common#/properties/expand",
-					},
-					query: {
-						$ref: "common#/properties/query",
+	.get(async (req, res, next) => {
+		try {
+			const data = await validator(
+				{
+					additionalProperties: false,
+					properties: {
+						expand: {
+							$ref: "common#/properties/expand",
+						},
+						query: {
+							$ref: "common#/properties/query",
+						},
 					},
 				},
-			},
-			{
-				expand: typeof req.query.expand === "string" ? 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);
-			})
-			.then((rows) => {
-				res.status(200).send(rows);
-			})
-			.catch(next);
+				{
+					expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
+					query: typeof req.query.query === "string" ? req.query.query : null,
+				},
+			);
+			const rows = await internalCertificate.getAll(res.locals.access, data.expand, data.query);
+			res.status(200).send(rows);
+		} catch (err) {
+			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
+			next(err);
+		}
 	})
 
 	/**
@@ -59,16 +60,16 @@ router
 	 *
 	 * Create a new certificate
 	 */
-	.post((req, res, next) => {
-		apiValidator(getValidationSchema("/nginx/certificates", "post"), req.body)
-			.then((payload) => {
-				req.setTimeout(900000); // 15 minutes timeout
-				return internalCertificate.create(res.locals.access, payload);
-			})
-			.then((result) => {
-				res.status(201).send(result);
-			})
-			.catch(next);
+	.post(async (req, res, next) => {
+		try {
+			const payload = await apiValidator(getValidationSchema("/nginx/certificates", "post"), req.body);
+			req.setTimeout(900000); // 15 minutes timeout
+			const result = await internalCertificate.create(res.locals.access, payload);
+			res.status(201).send(result);
+		} catch (err) {
+			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
+			next(err);
+		}
 	});
 
 /**
@@ -88,18 +89,22 @@ router
 	 *
 	 * Test HTTP challenge for domains
 	 */
-	.get((req, res, next) => {
+	.get(async (req, res, next) => {
 		if (req.query.domains === undefined) {
 			next(new errs.ValidationError("Domains are required as query parameters"));
 			return;
 		}
 
-		internalCertificate
-			.testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains))
-			.then((result) => {
-				res.status(200).send(result);
-			})
-			.catch(next);
+		try {
+			const result = await internalCertificate.testHttpsChallenge(
+				res.locals.access,
+				JSON.parse(req.query.domains),
+			);
+			res.status(200).send(result);
+		} catch (err) {
+			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
+			next(err);
+		}
 	});
 
 /**
@@ -119,35 +124,35 @@ router
 	 *
 	 * Retrieve a specific certificate
 	 */
-	.get((req, res, next) => {
-		validator(
-			{
-				required: ["certificate_id"],
-				additionalProperties: false,
-				properties: {
-					certificate_id: {
-						$ref: "common#/properties/id",
-					},
-					expand: {
-						$ref: "common#/properties/expand",
+	.get(async (req, res, next) => {
+		try {
+			const data = await validator(
+				{
+					required: ["certificate_id"],
+					additionalProperties: false,
+					properties: {
+						certificate_id: {
+							$ref: "common#/properties/id",
+						},
+						expand: {
+							$ref: "common#/properties/expand",
+						},
 					},
 				},
-			},
-			{
-				certificate_id: req.params.certificate_id,
-				expand: 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),
-					expand: data.expand,
-				});
-			})
-			.then((row) => {
-				res.status(200).send(row);
-			})
-			.catch(next);
+				{
+					certificate_id: req.params.certificate_id,
+					expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
+				},
+			);
+			const row = await internalCertificate.get(res.locals.access, {
+				id: Number.parseInt(data.certificate_id, 10),
+				expand: data.expand,
+			});
+			res.status(200).send(row);
+		} catch (err) {
+			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
+			next(err);
+		}
 	})
 
 	/**
@@ -155,13 +160,16 @@ router
 	 *
 	 * Update and existing certificate
 	 */
-	.delete((req, res, next) => {
-		internalCertificate
-			.delete(res.locals.access, { id: Number.parseInt(req.params.certificate_id, 10) })
-			.then((result) => {
-				res.status(200).send(result);
-			})
-			.catch(next);
+	.delete(async (req, res, next) => {
+		try {
+			const result = await internalCertificate.delete(res.locals.access, {
+				id: Number.parseInt(req.params.certificate_id, 10),
+			});
+			res.status(200).send(result);
+		} catch (err) {
+			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
+			next(err);
+		}
 	});
 
 /**
@@ -181,19 +189,21 @@ router
 	 *
 	 * Upload certificates
 	 */
-	.post((req, res, next) => {
+	.post(async (req, res, next) => {
 		if (!req.files) {
 			res.status(400).send({ error: "No files were uploaded" });
-		} else {
-			internalCertificate
-				.upload(res.locals.access, {
-					id: Number.parseInt(req.params.certificate_id, 10),
-					files: req.files,
-				})
-				.then((result) => {
-					res.status(200).send(result);
-				})
-				.catch(next);
+			return;
+		}
+
+		try {
+			const result = await internalCertificate.upload(res.locals.access, {
+				id: Number.parseInt(req.params.certificate_id, 10),
+				files: req.files,
+			});
+			res.status(200).send(result);
+		} catch (err) {
+			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
+			next(err);
 		}
 	});
 
@@ -214,16 +224,17 @@ router
 	 *
 	 * Renew certificate
 	 */
-	.post((req, res, next) => {
+	.post(async (req, res, next) => {
 		req.setTimeout(900000); // 15 minutes timeout
-		internalCertificate
-			.renew(res.locals.access, {
+		try {
+			const result = await internalCertificate.renew(res.locals.access, {
 				id: Number.parseInt(req.params.certificate_id, 10),
-			})
-			.then((result) => {
-				res.status(200).send(result);
-			})
-			.catch(next);
+			});
+			res.status(200).send(result);
+		} catch (err) {
+			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
+			next(err);
+		}
 	});
 
 /**
@@ -243,15 +254,16 @@ router
 	 *
 	 * Renew certificate
 	 */
-	.get((req, res, next) => {
-		internalCertificate
-			.download(res.locals.access, {
+	.get(async (req, res, next) => {
+		try {
+			const result = await internalCertificate.download(res.locals.access, {
 				id: Number.parseInt(req.params.certificate_id, 10),
-			})
-			.then((result) => {
-				res.status(200).download(result.fileName);
-			})
-			.catch(next);
+			});
+			res.status(200).download(result.fileName);
+		} catch (err) {
+			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
+			next(err);
+		}
 	});
 
 /**
@@ -271,18 +283,20 @@ router
 	 *
 	 * Validate certificates
 	 */
-	.post((req, res, next) => {
+	.post(async (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);
+			return;
+		}
+
+		try {
+			const result = await internalCertificate.validate({
+				files: req.files,
+			});
+			res.status(200).send(result);
+		} catch (err) {
+			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
+			next(err);
 		}
 	});
 

+ 97 - 89
backend/routes/nginx/dead_hosts.js

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

+ 100 - 90
backend/routes/nginx/proxy_hosts.js

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

+ 103 - 90
backend/routes/nginx/redirection_hosts.js

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

+ 100 - 90
backend/routes/nginx/streams.js

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

+ 9 - 7
backend/routes/reports.js

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

+ 18 - 12
backend/routes/schema.js

@@ -1,4 +1,5 @@
 import express from "express";
+import { express as logger } from "../logger.js";
 import PACKAGE from "../package.json" with { type: "json" };
 import { getCompiledSchema } from "../schema/index.js";
 
@@ -18,21 +19,26 @@ router
 	 * GET /schema
 	 */
 	.get(async (req, res) => {
-		const swaggerJSON = await getCompiledSchema();
+		try {
+			const swaggerJSON = await getCompiledSchema();
 
-		let proto = req.protocol;
-		if (typeof req.headers["x-forwarded-proto"] !== "undefined" && req.headers["x-forwarded-proto"]) {
-			proto = req.headers["x-forwarded-proto"];
-		}
+			let proto = req.protocol;
+			if (typeof req.headers["x-forwarded-proto"] !== "undefined" && 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;
-		}
+			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);
+			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);
+		}
 	});
 
 export default router;

+ 42 - 40
backend/routes/settings.js

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

+ 69 - 10
backend/routes/users.js

@@ -1,6 +1,8 @@
 import express from "express";
 import internalUser from "../internal/user.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 userIdFromMe from "../lib/express/user-id-from-me.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,
 				},
 			);
-			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);
 		} catch (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);
 			res.status(201).send(user);
 		} catch (err) {
 			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${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,
-					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, {
 				id: data.user_id,
 				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);
 		} catch (err) {
@@ -152,7 +197,10 @@ router
 	 */
 	.put(async (req, res, next) => {
 		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;
 			const result = await internalUser.update(res.locals.access, payload);
 			res.status(200).send(result);
@@ -169,7 +217,9 @@ router
 	 */
 	.delete(async (req, res, next) => {
 		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);
 		} catch (err) {
 			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
@@ -197,7 +247,10 @@ router
 	 */
 	.put(async (req, res, next) => {
 		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;
 			const result = await internalUser.setPassword(res.locals.access, payload);
 			res.status(200).send(result);
@@ -227,9 +280,15 @@ router
 	 */
 	.put(async (req, res, next) => {
 		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;
-			const result = await internalUser.setPermissions(res.locals.access, payload);
+			const result = await internalUser.setPermissions(
+				res.locals.access,
+				payload,
+			);
 			res.status(200).send(result);
 		} catch (err) {
 			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);

+ 5 - 0
backend/schema/components/health-object.json

@@ -9,6 +9,11 @@
 			"description": "Healthy",
 			"example": "OK"
 		},
+		"setup": {
+			"type": "boolean",
+			"description": "Whether the initial setup has been completed",
+			"example": true
+		},
 		"version": {
 			"type": "object",
 			"description": "The version object",

+ 57 - 0
backend/schema/components/user-object.json

@@ -54,6 +54,63 @@
 			"items": {
 				"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": "all",
+					"pattern": "^(manage|view|hidden)$"
+				},
+				"redirection_hosts": {
+					"type": "string",
+					"description": "Redirection Hosts access level",
+					"example": "all",
+					"pattern": "^(manage|view|hidden)$"
+				},
+				"dead_hosts": {
+					"type": "string",
+					"description": "Dead Hosts access level",
+					"example": "all",
+					"pattern": "^(manage|view|hidden)$"
+				},
+				"streams": {
+					"type": "string",
+					"description": "Streams access level",
+					"example": "all",
+					"pattern": "^(manage|view|hidden)$"
+				},
+				"access_lists": {
+					"type": "string",
+					"description": "Access Lists access level",
+					"example": "all",
+					"pattern": "^(manage|view|hidden)$"
+				},
+				"certificates": {
+					"type": "string",
+					"description": "Certificates access level",
+					"example": "all",
+					"pattern": "^(manage|view|hidden)$"
+				}
+			}
 		}
 	}
 }

+ 1 - 0
backend/schema/paths/get.json

@@ -11,6 +11,7 @@
 						"default": {
 							"value": {
 								"status": "OK",
+								"setup": true,
 								"version": {
 									"major": 2,
 									"minor": 1,

+ 1 - 0
docker/docker-compose.ci.yml

@@ -9,6 +9,7 @@ services:
     environment:
       TZ: "${TZ:-Australia/Brisbane}"
       DEBUG: 'true'
+      CI: 'true'
       FORCE_COLOR: 1
       # Required for DNS Certificate provisioning in CI
       LE_SERVER: 'https://ca.internal/acme/acme/directory'

+ 1 - 0
test/cypress/e2e/api/Certificates.cy.js

@@ -5,6 +5,7 @@ describe('Certificates endpoints', () => {
 	let certID;
 
 	before(() => {
+		cy.resetUsers();
 		cy.getToken().then((tok) => {
 			token = tok;
 		});

+ 1 - 0
test/cypress/e2e/api/Dashboard.cy.js

@@ -4,6 +4,7 @@ describe('Dashboard endpoints', () => {
 	let token;
 
 	before(() => {
+		cy.resetUsers();
 		cy.getToken().then((tok) => {
 			token = tok;
 		});

+ 1 - 0
test/cypress/e2e/api/FullCertProvision.cy.js

@@ -4,6 +4,7 @@ describe('Full Certificate Provisions', () => {
 	let token;
 
 	before(() => {
+		cy.resetUsers();
 		cy.getToken().then((tok) => {
 			token = tok;
 		});

+ 1 - 0
test/cypress/e2e/api/Ldap.cy.js

@@ -5,6 +5,7 @@ describe('LDAP with Authentik', () => {
 	if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') {
 
 		before(() => {
+			cy.resetUsers();
 			cy.getToken().then((tok) => {
 				_token = tok;
 

+ 1 - 0
test/cypress/e2e/api/ProxyHosts.cy.js

@@ -4,6 +4,7 @@ describe('Proxy Hosts endpoints', () => {
 	let token;
 
 	before(() => {
+		cy.resetUsers();
 		cy.getToken().then((tok) => {
 			token = tok;
 		});

+ 1 - 0
test/cypress/e2e/api/Settings.cy.js

@@ -4,6 +4,7 @@ describe('Settings endpoints', () => {
 	let token;
 
 	before(() => {
+		cy.resetUsers();
 		cy.getToken().then((tok) => {
 			token = tok;
 		});

+ 1 - 0
test/cypress/e2e/api/Streams.cy.js

@@ -4,6 +4,7 @@ describe('Streams', () => {
 	let token;
 
 	before(() => {
+		cy.resetUsers();
 		cy.getToken().then((tok) => {
 			token = tok;
 			// Set default site content

+ 1 - 0
test/cypress/e2e/api/Users.cy.js

@@ -4,6 +4,7 @@ describe('Users endpoints', () => {
 	let token;
 
 	before(() => {
+		cy.resetUsers();
 		cy.getToken().then((tok) => {
 			token = tok;
 		});

+ 78 - 13
test/cypress/support/commands.js

@@ -12,10 +12,10 @@
 import 'cypress-wait-until';
 
 Cypress.Commands.add('randomString', (length) => {
-	var result           = '';
-	var characters       = 'ABCDEFGHIJK LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-	var charactersLength = characters.length;
-	for (var i = 0; i < length; i++) {
+	let result = '';
+	const characters = 'ABCDEFGHIJK LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+	const charactersLength = characters.length;
+	for (let i = 0; i < length; i++) {
 		result += characters.charAt(Math.floor(Math.random() * charactersLength));
 	}
 	return result;
@@ -40,16 +40,81 @@ Cypress.Commands.add('validateSwaggerSchema', (method, code, path, data) => {
 	}).should('equal', null);
 });
 
-Cypress.Commands.add('getToken', () => {
-	// login with existing user
-	cy.task('backendApiPost', {
-		path: '/api/tokens',
-		data: {
-			identity: '[email protected]',
-			secret:   'changeme'
+Cypress.Commands.add('createInitialUser', (defaultUser) => {
+	let user = {
+		name:        'Cypress McGee',
+		nickname:    'Cypress',
+		email:       '[email protected]',
+		auth:        {
+			type:   'password',
+			secret: 'changeme'
+		},
+	};
+
+	if (typeof defaultUser === 'object' && defaultUser) {
+		user = Object.assign({}, user, defaultUser);
+	}
+
+	return cy.task('backendApiPost', {
+		path: '/api/users',
+		data: user,
+	}).then((data) => {
+		// Check the swagger schema:
+		cy.validateSwaggerSchema('post', 201, '/users', data);
+		expect(data).to.have.property('id');
+		expect(data.id).to.be.greaterThan(0);
+		cy.wrap(data);
+	});
+});
+
+Cypress.Commands.add('getToken', (defaultUser, defaultAuth) => {
+	if (typeof defaultAuth === 'object' && defaultAuth) {
+		if (!defaultUser) {
+			defaultUser = {};
+		}
+		defaultUser.auth = defaultAuth;
+	}
+
+	cy.task('backendApiGet', {
+		path: '/api/',
+	}).then((data) => {
+		// Check the swagger schema:
+		cy.validateSwaggerSchema('get', 200, '/', data);
+
+		if (!data.setup) {
+			cy.log('Setup = false');
+			// create a new user
+			cy.createInitialUser(defaultUser).then(() => {
+				return cy.getToken(defaultUser);
+			});
+		} else {
+			let auth = {
+				identity: '[email protected]',
+				secret:   'changeme',
+			};
+
+			if (typeof defaultUser === 'object' && defaultUser && typeof defaultUser.auth === 'object' && defaultUser.auth) {
+				auth = Object.assign({}, auth, defaultUser.auth);
+			}
+
+			cy.log('Setup = true');
+			// login with existing user
+			cy.task('backendApiPost', {
+				path: '/api/tokens',
+				data: auth,
+			}).then((res) => {
+				cy.wrap(res.token);
+			});
 		}
-	}).then(res => {
-		cy.wrap(res.token);
+	});
+});
+
+Cypress.Commands.add('resetUsers', () => {
+	cy.task('backendApiDelete', {
+		path: '/api/users'
+	}).then((data) => {
+		expect(data).to.be.equal(true);
+		cy.wrap(data);
 	});
 });