Compare commits

..

4 Commits

Author SHA1 Message Date
Jamie Curnow
432afe73ad User table polishing, user delete modal 2025-09-04 14:59:01 +10:00
Jamie Curnow
5a01da2916 Notification toasts, nicer loading, add new user support 2025-09-04 12:11:39 +10:00
Jamie Curnow
ebd9148813 React 2025-09-03 14:02:14 +10:00
Jamie Curnow
a12553fec7 Convert backend to ESM
- About 5 years overdue
- Remove eslint, use bomejs instead
2025-09-03 13:59:40 +10:00
230 changed files with 4147 additions and 9546 deletions

View File

@@ -1 +1 @@
2.13.0 2.12.6

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.13.0-green.svg?style=for-the-badge"> <img src="https://img.shields.io/badge/version-2.12.6-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>
@@ -88,6 +88,14 @@ 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.4/schema.json", "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",

View File

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

View File

@@ -9,12 +9,11 @@ const internalAuditLog = {
* *
* @param {Access} access * @param {Access} access
* @param {Array} [expand] * @param {Array} [expand]
* @param {String} [searchQuery] * @param {String} [search_query]
* @returns {Promise} * @returns {Promise}
*/ */
getAll: async (access, expand, searchQuery) => { getAll: (access, expand, search_query) => {
await access.can("auditlog:list"); return access.can("auditlog:list").then(() => {
const query = auditLogModel const query = auditLogModel
.query() .query()
.orderBy("created_on", "DESC") .orderBy("created_on", "DESC")
@@ -23,9 +22,9 @@ const internalAuditLog = {
.allowGraph("[user]"); .allowGraph("[user]");
// Query is used for searching // Query is used for searching
if (typeof searchQuery === "string" && searchQuery.length > 0) { if (typeof search_query === "string" && search_query.length > 0) {
query.where(function () { query.where(function () {
this.where(castJsonIfNeed("meta"), "like", `%${searchQuery}`); this.where(castJsonIfNeed("meta"), "like", `%${search_query}`);
}); });
} }
@@ -33,36 +32,8 @@ const internalAuditLog = {
query.withGraphFetched(`[${expand.join(", ")}]`); query.withGraphFetched(`[${expand.join(", ")}]`);
} }
return await query; return 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;
}, },
/** /**
@@ -79,22 +50,27 @@ const internalAuditLog = {
* @param {Object} [data.meta] * @param {Object} [data.meta]
* @returns {Promise} * @returns {Promise}
*/ */
add: async (access, data) => { add: (access, data) => {
return new Promise((resolve, reject) => {
// Default the user id
if (typeof data.user_id === "undefined" || !data.user_id) { if (typeof data.user_id === "undefined" || !data.user_id) {
data.user_id = access.token.getUserId(1); data.user_id = access.token.getUserId(1);
} }
if (typeof data.action === "undefined" || !data.action) { if (typeof data.action === "undefined" || !data.action) {
throw new errs.InternalValidationError("Audit log entry must contain an Action"); reject(new errs.InternalValidationError("Audit log entry must contain an Action"));
} } else {
// Make sure at least 1 of the IDs are set and action // Make sure at least 1 of the IDs are set and action
return await auditLogModel.query().insert({ resolve(
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,24 +18,25 @@ const internalDeadHost = {
* @param {Object} data * @param {Object} data
* @returns {Promise} * @returns {Promise}
*/ */
create: async (access, data) => { create: (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;
} }
await access.can("dead_hosts:create", data); return access
.can("dead_hosts:create", data)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records // Get a list of the domain names and check each of them against existing records
const domainNameCheckPromises = []; const domain_name_check_promises = [];
data.domain_names.map((domain_name) => { data.domain_names.map((domain_name) => {
domainNameCheckPromises.push(internalHost.isHostnameTaken(domain_name)); domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
return true; return true;
}); });
await Promise.all(domainNameCheckPromises).then((check_results) => { return Promise.all(domain_name_check_promises).then((check_results) => {
check_results.map((result) => { check_results.map((result) => {
if (result.is_taken) { if (result.is_taken) {
throw new errs.ValidationError(`${result.hostname} is already in use`); throw new errs.ValidationError(`${result.hostname} is already in use`);
@@ -43,7 +44,8 @@ const internalDeadHost = {
return true; return true;
}); });
}); });
})
.then(() => {
// At this point the domains should have been checked // At this point the domains should have been checked
data.owner_user_id = access.token.getUserId(1); data.owner_user_id = access.token.getUserId(1);
const thisData = internalHost.cleanSslHstsData(data); const thisData = internalHost.cleanSslHstsData(data);
@@ -54,43 +56,53 @@ const internalDeadHost = {
thisData.advanced_config = ""; thisData.advanced_config = "";
} }
const row = await deadHostModel.query() return deadHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions()));
.insertAndFetch(thisData) })
.then(utils.omitRow(omissions())); .then((row) => {
// Add to audit log
await internalAuditLog.add(access, {
action: "created",
object_type: "dead-host",
object_id: row.id,
meta: thisData,
});
if (createCertificate) { if (createCertificate) {
const cert = await internalCertificate.createQuickCertificate(access, data); return internalCertificate
.createQuickCertificate(access, data)
.then((cert) => {
// update host with cert id // update host with cert id
await internalDeadHost.update(access, { return internalDeadHost.update(access, {
id: row.id, id: row.id,
certificate_id: cert.id, certificate_id: cert.id,
}); });
})
.then(() => {
return row;
});
} }
return row;
})
.then((row) => {
// re-fetch with cert // re-fetch with cert
const freshRow = await internalDeadHost.get(access, { return internalDeadHost.get(access, {
id: row.id, id: row.id,
expand: ["certificate", "owner"], expand: ["certificate", "owner"],
}); });
})
// Sanity check .then((row) => {
if (createCertificate && !freshRow.certificate_id) {
throw new errs.InternalValidationError("The host was created but the Certificate creation failed.");
}
// Configure nginx // Configure nginx
await internalNginx.configure(deadHostModel, "dead_host", freshRow); return internalNginx.configure(deadHostModel, "dead_host", row).then(() => {
return row;
});
})
.then((row) => {
data.meta = _.assign({}, data.meta || {}, row.meta);
return freshRow; // Add to audit log
return internalAuditLog
.add(access, {
action: "created",
object_type: "dead-host",
object_id: row.id,
meta: data,
})
.then(() => {
return row;
});
});
}, },
/** /**
@@ -99,51 +111,66 @@ const internalDeadHost = {
* @param {Number} data.id * @param {Number} data.id
* @return {Promise} * @return {Promise}
*/ */
update: async (access, data) => { update: (access, data) => {
const createCertificate = data.certificate_id === "new"; let thisData = data;
const createCertificate = thisData.certificate_id === "new";
if (createCertificate) { if (createCertificate) {
delete data.certificate_id; delete thisData.certificate_id;
} }
await access.can("dead_hosts:update", data.id); return access
.can("dead_hosts:update", thisData.id)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records // Get a list of the domain names and check each of them against existing records
const domainNameCheckPromises = []; const domain_name_check_promises = [];
if (typeof data.domain_names !== "undefined") {
data.domain_names.map((domainName) => { if (typeof thisData.domain_names !== "undefined") {
domainNameCheckPromises.push(internalHost.isHostnameTaken(domainName, "dead", data.id)); thisData.domain_names.map((domain_name) => {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, "dead", data.id));
return true; return true;
}); });
const checkResults = await Promise.all(domainNameCheckPromises); return Promise.all(domain_name_check_promises).then((check_results) => {
checkResults.map((result) => { check_results.map((result) => {
if (result.is_taken) { if (result.is_taken) {
throw new errs.ValidationError(`${result.hostname} is already in use`); throw new errs.ValidationError(`${result.hostname} is already in use`);
} }
return true; return true;
}); });
});
} }
const row = await internalDeadHost.get(access, { id: data.id }); })
.then(() => {
if (row.id !== data.id) { return internalDeadHost.get(access, { id: thisData.id });
})
.then((row) => {
if (row.id !== thisData.id) {
// Sanity check that something crazy hasn't happened // Sanity check that something crazy hasn't happened
throw new errs.InternalValidationError( throw new errs.InternalValidationError(
`404 Host could not be updated, IDs do not match: ${row.id} !== ${data.id}`, `404 Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`,
); );
} }
if (createCertificate) { if (createCertificate) {
const cert = await internalCertificate.createQuickCertificate(access, { return internalCertificate
domain_names: data.domain_names || row.domain_names, .createQuickCertificate(access, {
meta: _.assign({}, row.meta, data.meta), domain_names: thisData.domain_names || row.domain_names,
}); meta: _.assign({}, row.meta, thisData.meta),
})
.then((cert) => {
// update host with cert id // update host with cert id
data.certificate_id = 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. // 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( thisData = _.assign(
{}, {},
{ {
domain_names: row.domain_names, domain_names: row.domain_names,
@@ -153,31 +180,38 @@ const internalDeadHost = {
thisData = internalHost.cleanSslHstsData(thisData, row); thisData = internalHost.cleanSslHstsData(thisData, row);
return deadHostModel
// do the row update
await deadHostModel
.query() .query()
.where({id: data.id}) .where({ id: thisData.id })
.patch(data); .patch(thisData)
.then((saved_row) => {
// Add to audit log // Add to audit log
await internalAuditLog.add(access, { return internalAuditLog
.add(access, {
action: "updated", action: "updated",
object_type: "dead-host", object_type: "dead-host",
object_id: row.id, object_id: row.id,
meta: thisData, meta: thisData,
})
.then(() => {
return _.omit(saved_row, omissions());
}); });
});
const thisRow = await internalDeadHost })
.then(() => {
return internalDeadHost
.get(access, { .get(access, {
id: thisData.id, id: thisData.id,
expand: ["owner", "certificate"], expand: ["owner", "certificate"],
}); })
.then((row) => {
// Configure nginx // Configure nginx
const newMeta = await internalNginx.configure(deadHostModel, "dead_host", row); return internalNginx.configure(deadHostModel, "dead_host", row).then((new_meta) => {
row.meta = newMeta; row.meta = new_meta;
return _.omit(internalHost.cleanRowCertificateMeta(thisRow), omissions()); return _.omit(internalHost.cleanRowCertificateMeta(row), omissions());
});
});
});
}, },
/** /**
@@ -188,32 +222,39 @@ const internalDeadHost = {
* @param {Array} [data.omit] * @param {Array} [data.omit]
* @return {Promise} * @return {Promise}
*/ */
get: async (access, data) => { get: (access, data) => {
const accessData = await access.can("dead_hosts:get", data.id); const thisData = data || {};
return access
.can("dead_hosts:get", thisData.id)
.then((access_data) => {
const query = deadHostModel const query = deadHostModel
.query() .query()
.where("is_deleted", 0) .where("is_deleted", 0)
.andWhere("id", data.id) .andWhere("id", dthisDataata.id)
.allowGraph("[owner,certificate]") .allowGraph("[owner,certificate]")
.first(); .first();
if (accessData.permission_visibility !== "all") { if (access_data.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1)); query.andWhere("owner_user_id", access.token.getUserId(1));
} }
if (typeof data.expand !== "undefined" && data.expand !== null) { if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
query.withGraphFetched(`[${data.expand.join(", ")}]`); query.withGraphFetched(`[${data.expand.join(", ")}]`);
} }
const row = await query.then(utils.omitRow(omissions())); return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row || !row.id) { if (!row || !row.id) {
throw new errs.ItemNotFoundError(data.id); throw new errs.ItemNotFoundError(thisData.id);
} }
// Custom omissions // Custom omissions
if (typeof data.omit !== "undefined" && data.omit !== null) { if (typeof thisData.omit !== "undefined" && thisData.omit !== null) {
return _.omit(row, data.omit); return _.omit(row, thisData.omit);
} }
return row; return row;
});
}, },
/** /**
@@ -223,32 +264,42 @@ const internalDeadHost = {
* @param {String} [data.reason] * @param {String} [data.reason]
* @returns {Promise} * @returns {Promise}
*/ */
delete: async (access, data) => { delete: (access, data) => {
await access.can("dead_hosts:delete", data.id) return access
const row = await internalDeadHost.get(access, { id: data.id }); .can("dead_hosts:delete", data.id)
.then(() => {
return internalDeadHost.get(access, { id: data.id });
})
.then((row) => {
if (!row || !row.id) { if (!row || !row.id) {
throw new errs.ItemNotFoundError(data.id); throw new errs.ItemNotFoundError(data.id);
} }
await deadHostModel return deadHostModel
.query() .query()
.where("id", row.id) .where("id", row.id)
.patch({ .patch({
is_deleted: 1, is_deleted: 1,
}); })
.then(() => {
// Delete Nginx Config // Delete Nginx Config
await internalNginx.deleteConfig("dead_host", row); return internalNginx.deleteConfig("dead_host", row).then(() => {
await internalNginx.reload(); return internalNginx.reload();
});
})
.then(() => {
// Add to audit log // Add to audit log
await internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: "deleted", action: "deleted",
object_type: "dead-host", object_type: "dead-host",
object_id: row.id, object_id: row.id,
meta: _.omit(row, omissions()), meta: _.omit(row, omissions()),
}); });
});
})
.then(() => {
return true; return true;
});
}, },
/** /**
@@ -258,12 +309,16 @@ const internalDeadHost = {
* @param {String} [data.reason] * @param {String} [data.reason]
* @returns {Promise} * @returns {Promise}
*/ */
enable: async (access, data) => { enable: (access, data) => {
await access.can("dead_hosts:update", data.id) return access
const row = await internalDeadHost.get(access, { .can("dead_hosts:update", data.id)
.then(() => {
return internalDeadHost.get(access, {
id: data.id, id: data.id,
expand: ["certificate", "owner"], expand: ["certificate", "owner"],
}); });
})
.then((row) => {
if (!row || !row.id) { if (!row || !row.id) {
throw new errs.ItemNotFoundError(data.id); throw new errs.ItemNotFoundError(data.id);
} }
@@ -273,24 +328,29 @@ const internalDeadHost = {
row.enabled = 1; row.enabled = 1;
await deadHostModel return deadHostModel
.query() .query()
.where("id", row.id) .where("id", row.id)
.patch({ .patch({
enabled: 1, enabled: 1,
}); })
.then(() => {
// Configure nginx // Configure nginx
await internalNginx.configure(deadHostModel, "dead_host", row); return internalNginx.configure(deadHostModel, "dead_host", row);
})
.then(() => {
// Add to audit log // Add to audit log
await internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: "enabled", action: "enabled",
object_type: "dead-host", object_type: "dead-host",
object_id: row.id, object_id: row.id,
meta: _.omit(row, omissions()), meta: _.omit(row, omissions()),
}); });
});
})
.then(() => {
return true; return true;
});
}, },
/** /**
@@ -300,9 +360,13 @@ const internalDeadHost = {
* @param {String} [data.reason] * @param {String} [data.reason]
* @returns {Promise} * @returns {Promise}
*/ */
disable: async (access, data) => { disable: (access, data) => {
await access.can("dead_hosts:update", data.id) return access
const row = await internalDeadHost.get(access, { id: data.id }); .can("dead_hosts:update", data.id)
.then(() => {
return internalDeadHost.get(access, { id: data.id });
})
.then((row) => {
if (!row || !row.id) { if (!row || !row.id) {
throw new errs.ItemNotFoundError(data.id); throw new errs.ItemNotFoundError(data.id);
} }
@@ -312,25 +376,31 @@ const internalDeadHost = {
row.enabled = 0; row.enabled = 0;
await deadHostModel return deadHostModel
.query() .query()
.where("id", row.id) .where("id", row.id)
.patch({ .patch({
enabled: 0, enabled: 0,
}); })
.then(() => {
// Delete Nginx Config // Delete Nginx Config
await internalNginx.deleteConfig("dead_host", row); return internalNginx.deleteConfig("dead_host", row).then(() => {
await internalNginx.reload(); return internalNginx.reload();
});
})
.then(() => {
// Add to audit log // Add to audit log
await internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: "disabled", action: "disabled",
object_type: "dead-host", object_type: "dead-host",
object_id: row.id, object_id: row.id,
meta: _.omit(row, omissions()), meta: _.omit(row, omissions()),
}); });
});
})
.then(() => {
return true; return true;
});
}, },
/** /**
@@ -338,11 +408,13 @@ const internalDeadHost = {
* *
* @param {Access} access * @param {Access} access
* @param {Array} [expand] * @param {Array} [expand]
* @param {String} [searchQuery] * @param {String} [search_query]
* @returns {Promise} * @returns {Promise}
*/ */
getAll: async (access, expand, searchQuery) => { getAll: (access, expand, search_query) => {
const accessData = await access.can("dead_hosts:list") return access
.can("dead_hosts:list")
.then((access_data) => {
const query = deadHostModel const query = deadHostModel
.query() .query()
.where("is_deleted", 0) .where("is_deleted", 0)
@@ -350,14 +422,14 @@ const internalDeadHost = {
.allowGraph("[owner,certificate]") .allowGraph("[owner,certificate]")
.orderBy(castJsonIfNeed("domain_names"), "ASC"); .orderBy(castJsonIfNeed("domain_names"), "ASC");
if (accessData.permission_visibility !== "all") { if (access_data.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 searchQuery === "string" && searchQuery.length > 0) { if (typeof search_query === "string" && search_query.length > 0) {
query.where(function () { query.where(function () {
this.where(castJsonIfNeed("domain_names"), "like", `%${searchQuery}%`); this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`);
}); });
} }
@@ -365,11 +437,15 @@ const internalDeadHost = {
query.withGraphFetched(`[${expand.join(", ")}]`); query.withGraphFetched(`[${expand.join(", ")}]`);
} }
const rows = await query.then(utils.omitRows(omissions())); return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
internalHost.cleanAllRowsCertificateMeta(rows); return internalHost.cleanAllRowsCertificateMeta(rows);
} }
return rows; return rows;
});
}, },
/** /**
@@ -379,15 +455,16 @@ const internalDeadHost = {
* @param {String} visibility * @param {String} visibility
* @returns {Promise} * @returns {Promise}
*/ */
getCount: async (user_id, visibility) => { getCount: (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);
} }
const row = await query.first(); return query.first().then((row) => {
return Number.parseInt(row.count, 10); return Number.parseInt(row.count, 10);
});
}, },
}; };

View File

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

View File

@@ -301,11 +301,8 @@ const internalNginx = {
* @param {String} filename * @param {String} filename
*/ */
deleteFile: (filename) => { deleteFile: (filename) => {
if (!fs.existsSync(filename)) {
return;
}
try {
logger.debug(`Deleting file: ${filename}`); logger.debug(`Deleting file: ${filename}`);
try {
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));

View File

@@ -420,8 +420,10 @@ const internalProxyHost = {
* @param {String} [search_query] * @param {String} [search_query]
* @returns {Promise} * @returns {Promise}
*/ */
getAll: async (access, expand, searchQuery) => { getAll: (access, expand, search_query) => {
const accessData = await access.can("proxy_hosts:list"); return access
.can("proxy_hosts:list")
.then((access_data) => {
const query = proxyHostModel const query = proxyHostModel
.query() .query()
.where("is_deleted", 0) .where("is_deleted", 0)
@@ -429,14 +431,14 @@ const internalProxyHost = {
.allowGraph("[owner,access_list,certificate]") .allowGraph("[owner,access_list,certificate]")
.orderBy(castJsonIfNeed("domain_names"), "ASC"); .orderBy(castJsonIfNeed("domain_names"), "ASC");
if (accessData.permission_visibility !== "all") { if (access_data.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 searchQuery === "string" && searchQuery.length > 0) { if (typeof search_query === "string" && search_query.length > 0) {
query.where(function () { query.where(function () {
this.where(castJsonIfNeed("domain_names"), "like", `%${searchQuery}%`); this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`);
}); });
} }
@@ -444,11 +446,15 @@ const internalProxyHost = {
query.withGraphFetched(`[${expand.join(", ")}]`); query.withGraphFetched(`[${expand.join(", ")}]`);
} }
const rows = await query.then(utils.omitRows(omissions())); return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows); return internalHost.cleanAllRowsCertificateMeta(rows);
} }
return 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", object_type: "stream-host",
object_id: row.id, object_id: row.id,
meta: _.omit(row, omissions()), meta: _.omit(row, omissions()),
}); });

View File

@@ -18,41 +18,30 @@ export default {
* @param {String} [issuer] * @param {String} [issuer]
* @returns {Promise} * @returns {Promise}
*/ */
getTokenFromEmail: async (data, issuer) => { getTokenFromEmail: (data, issuer) => {
const Token = TokenModel(); const Token = TokenModel();
data.scope = data.scope || "user"; data.scope = data.scope || "user";
data.expiry = data.expiry || "1d"; data.expiry = data.expiry || "1d";
const user = await userModel return userModel
.query() .query()
.where("email", data.identity.toLowerCase().trim()) .where("email", data.identity.toLowerCase().trim())
.andWhere("is_deleted", 0) .andWhere("is_deleted", 0)
.andWhere("is_disabled", 0) .andWhere("is_disabled", 0)
.first(); .first()
.then((user) => {
if (!user) { if (user) {
throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH); // Get auth
} return authModel
const auth = await authModel
.query() .query()
.where("user_id", "=", user.id) .where("user_id", "=", user.id)
.where("type", "=", "password") .where("type", "=", "password")
.first(); .first()
.then((auth) => {
if (!auth) { if (auth) {
throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH); return auth.verifyPassword(data.secret).then((valid) => {
} if (valid) {
const valid = await auth.verifyPassword(data.secret);
if (!valid) {
throw new errs.AuthError(
ERROR_MESSAGE_INVALID_AUTH,
ERROR_MESSAGE_INVALID_AUTH_I18N,
);
}
if (data.scope !== "user" && _.indexOf(user.roles, data.scope) === -1) { if (data.scope !== "user" && _.indexOf(user.roles, data.scope) === -1) {
// The scope requested doesn't exist as a role against the user, // The scope requested doesn't exist as a role against the user,
// you shall not pass. // you shall not pass.
@@ -65,19 +54,31 @@ export default {
throw new errs.AuthError(`Invalid expiry time: ${data.expiry}`); throw new errs.AuthError(`Invalid expiry time: ${data.expiry}`);
} }
const signed = await Token.create({ return Token.create({
iss: issuer || "api", iss: issuer || "api",
attrs: { attrs: {
id: user.id, id: user.id,
}, },
scope: [data.scope], scope: [data.scope],
expiresIn: data.expiry, expiresIn: data.expiry,
}); }).then((signed) => {
return { return {
token: signed.token, token: signed.token,
expires: expiry.toISOString(), expires: expiry.toISOString(),
}; };
});
}
throw new errs.AuthError(
ERROR_MESSAGE_INVALID_AUTH,
ERROR_MESSAGE_INVALID_AUTH_I18N,
);
});
}
throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH);
});
}
throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH);
});
}, },
/** /**
@@ -87,7 +88,7 @@ export default {
* @param {String} [data.scope] Only considered if existing token scope is admin * @param {String} [data.scope] Only considered if existing token scope is admin
* @returns {Promise} * @returns {Promise}
*/ */
getFreshToken: async (access, data) => { getFreshToken: (access, data) => {
const Token = TokenModel(); const Token = TokenModel();
const thisData = data || {}; const thisData = data || {};
@@ -114,17 +115,17 @@ export default {
} }
} }
const signed = await Token.create({ return Token.create({
iss: "api", iss: "api",
scope: scope, scope: scope,
attrs: token_attrs, attrs: token_attrs,
expiresIn: thisData.expiry, expiresIn: thisData.expiry,
}); }).then((signed) => {
return { return {
token: signed.token, token: signed.token,
expires: expiry.toISOString(), expires: expiry.toISOString(),
}; };
});
} }
throw new error.AssertionFailedError("Existing token contained invalid user data"); throw new error.AssertionFailedError("Existing token contained invalid user data");
}, },
@@ -135,7 +136,7 @@ export default {
*/ */
getTokenFromUser: async (user) => { getTokenFromUser: async (user) => {
const expire = "1d"; const expire = "1d";
const Token = TokenModel(); const Token = new TokenModel();
const expiry = parseDatePeriod(expire); const expiry = parseDatePeriod(expire);
const signed = await Token.create({ const signed = await Token.create({

View File

@@ -9,21 +9,18 @@ import internalAuditLog from "./audit-log.js";
import internalToken from "./token.js"; import internalToken from "./token.js";
const omissions = () => { const omissions = () => {
return ["is_deleted", "permissions.id", "permissions.user_id", "permissions.created_on", "permissions.modified_on"]; return ["is_deleted"];
}; }
const DEFAULT_AVATAR = gravatar.url("admin@example.com", { default: "mm" }); const DEFAULT_AVATAR = 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=200&d=mp&r=g';
const internalUser = { const internalUser = {
/** /**
* Create a user can happen unauthenticated only once and only when no active users exist.
* Otherwise, a valid auth method is required.
*
* @param {Access} access * @param {Access} access
* @param {Object} data * @param {Object} data
* @returns {Promise} * @returns {Promise}
*/ */
create: async (access, data) => { create: (access, data) => {
const auth = data.auth || null; const auth = data.auth || null;
delete data.auth; delete data.auth;
@@ -34,43 +31,61 @@ const internalUser = {
data.is_disabled = data.is_disabled ? 1 : 0; data.is_disabled = data.is_disabled ? 1 : 0;
} }
await access.can("users:create", data); return access
.can("users:create", data)
.then(() => {
data.avatar = gravatar.url(data.email, { default: "mm" }); data.avatar = gravatar.url(data.email, { default: "mm" });
return userModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
let user = await userModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); })
.then((user) => {
if (auth) { if (auth) {
user = await authModel.query().insert({ return authModel
.query()
.insert({
user_id: user.id, user_id: user.id,
type: auth.type, type: auth.type,
secret: auth.secret, secret: auth.secret,
meta: {}, meta: {},
})
.then(() => {
return user;
}); });
} }
return user;
})
.then((user) => {
// Create permissions row as well // Create permissions row as well
const isAdmin = data.roles.indexOf("admin") !== -1; const is_admin = data.roles.indexOf("admin") !== -1;
await userPermissionModel.query().insert({ return userPermissionModel
.query()
.insert({
user_id: user.id, user_id: user.id,
visibility: isAdmin ? "all" : "user", visibility: is_admin ? "all" : "user",
proxy_hosts: "manage", proxy_hosts: "manage",
redirection_hosts: "manage", redirection_hosts: "manage",
dead_hosts: "manage", dead_hosts: "manage",
streams: "manage", streams: "manage",
access_lists: "manage", access_lists: "manage",
certificates: "manage", certificates: "manage",
})
.then(() => {
return internalUser.get(access, { id: user.id, expand: ["permissions"] });
}); });
})
user = await internalUser.get(access, { id: user.id, expand: ["permissions"] }); .then((user) => {
// Add to audit log
await internalAuditLog.add(access, { return internalAuditLog
.add(access, {
action: "created", action: "created",
object_type: "user", object_type: "user",
object_id: user.id, object_id: user.id,
meta: user, meta: user,
}); })
.then(() => {
return user; return user;
});
});
}, },
/** /**
@@ -131,7 +146,7 @@ const internalUser = {
action: "updated", action: "updated",
object_type: "user", object_type: "user",
object_id: user.id, object_id: user.id,
meta: { ...data, id: user.id, name: user.name }, meta: data,
}) })
.then(() => { .then(() => {
return user; return user;
@@ -250,14 +265,6 @@ const internalUser = {
}); });
}, },
deleteAll: async () => {
await userModel
.query()
.patch({
is_deleted: 1,
});
},
/** /**
* This will only count the users * This will only count the users
* *
@@ -309,7 +316,11 @@ const internalUser = {
// Query is used for searching // Query is used for searching
if (typeof search_query === "string") { if (typeof search_query === "string") {
query.where(function () { query.where(function () {
this.where("name", "like", `%${search_query}%`).orWhere("email", "like", `%${search_query}%`); this.where("name", "like", `%${search_query}%`).orWhere(
"email",
"like",
`%${search_query}%`,
);
}); });
} }

View File

@@ -22,13 +22,13 @@ import errs from "./error.js";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
export default function (tokenString) { export default function (token_string) {
const Token = TokenModel(); const Token = TokenModel();
let tokenData = null; let token_data = null;
let initialised = false; let initialised = false;
const objectCache = {}; const object_cache = {};
let allowInternalAccess = false; let allow_internal_access = false;
let userRoles = []; let user_roles = [];
let permissions = {}; let permissions = {};
/** /**
@@ -36,58 +36,65 @@ export default function (tokenString) {
* *
* @returns {Promise} * @returns {Promise}
*/ */
this.init = async () => { this.init = () => {
return new Promise((resolve, reject) => {
if (initialised) { if (initialised) {
return; resolve();
} } else if (!token_string) {
reject(new errs.PermissionError("Permission Denied"));
if (!tokenString) { } else {
throw new errs.PermissionError("Permission Denied"); resolve(
} Token.load(token_string).then((data) => {
token_data = data;
tokenData = await Token.load(tokenString);
// At this point we need to load the user from the DB and make sure they: // At this point we need to load the user from the DB and make sure they:
// - exist (and not soft deleted) // - exist (and not soft deleted)
// - still have the appropriate scopes for this token // - still have the appropriate scopes for this token
// This is only required when the User ID is supplied or if the token scope has `user` // This is only required when the User ID is supplied or if the token scope has `user`
if ( if (
tokenData.attrs.id || token_data.attrs.id ||
(typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, "user") !== -1) (typeof token_data.scope !== "undefined" &&
_.indexOf(token_data.scope, "user") !== -1)
) { ) {
// Has token user id or token user scope // Has token user id or token user scope
const user = await userModel return userModel
.query() .query()
.where("id", tokenData.attrs.id) .where("id", token_data.attrs.id)
.andWhere("is_deleted", 0) .andWhere("is_deleted", 0)
.andWhere("is_disabled", 0) .andWhere("is_disabled", 0)
.allowGraph("[permissions]") .allowGraph("[permissions]")
.withGraphFetched("[permissions]") .withGraphFetched("[permissions]")
.first(); .first()
.then((user) => {
if (user) { if (user) {
// make sure user has all scopes of the token // make sure user has all scopes of the token
// The `user` role is not added against the user row, so we have to just add it here to get past this check. // The `user` role is not added against the user row, so we have to just add it here to get past this check.
user.roles.push("user"); user.roles.push("user");
let ok = true; let is_ok = true;
_.forEach(tokenData.scope, (scope_item) => { _.forEach(token_data.scope, (scope_item) => {
if (_.indexOf(user.roles, scope_item) === -1) { if (_.indexOf(user.roles, scope_item) === -1) {
ok = false; is_ok = false;
} }
}); });
if (!ok) { if (!is_ok) {
throw new errs.AuthError("Invalid token scope for User"); throw new errs.AuthError("Invalid token scope for User");
} }
initialised = true; initialised = true;
userRoles = user.roles; user_roles = user.roles;
permissions = user.permissions; permissions = user.permissions;
} else { } else {
throw new errs.AuthError("User cannot be loaded for Token"); throw new errs.AuthError("User cannot be loaded for Token");
} }
});
} }
initialised = true; initialised = true;
}),
);
}
});
}; };
/** /**
@@ -95,66 +102,82 @@ export default function (tokenString) {
* This only applies to USER token scopes, as all other tokens are not really bound * This only applies to USER token scopes, as all other tokens are not really bound
* by object scopes * by object scopes
* *
* @param {String} objectType * @param {String} object_type
* @returns {Promise} * @returns {Promise}
*/ */
this.loadObjects = async (objectType) => { this.loadObjects = (object_type) => {
let objects = null; return new Promise((resolve, reject) => {
if (Token.hasScope("user")) { if (Token.hasScope("user")) {
if (typeof tokenData.attrs.id === "undefined" || !tokenData.attrs.id) { if (
throw new errs.AuthError("User Token supplied without a User ID"); typeof token_data.attrs.id === "undefined" ||
} !token_data.attrs.id
) {
const tokenUserId = tokenData.attrs.id ? tokenData.attrs.id : 0; reject(new errs.AuthError("User Token supplied without a User ID"));
if (typeof objectCache[objectType] !== "undefined") {
objects = objectCache[objectType];
} else { } else {
switch (objectType) { const token_user_id = token_data.attrs.id ? token_data.attrs.id : 0;
let query;
if (typeof object_cache[object_type] === "undefined") {
switch (object_type) {
// USERS - should only return yourself // USERS - should only return yourself
case "users": case "users":
objects = tokenUserId ? [tokenUserId] : []; resolve(token_user_id ? [token_user_id] : []);
break; break;
// Proxy Hosts // Proxy Hosts
case "proxy_hosts": { case "proxy_hosts":
const query = proxyHostModel query = proxyHostModel
.query() .query()
.select("id") .select("id")
.andWhere("is_deleted", 0); .andWhere("is_deleted", 0);
if (permissions.visibility === "user") { if (permissions.visibility === "user") {
query.andWhere("owner_user_id", tokenUserId); query.andWhere("owner_user_id", token_user_id);
} }
const rows = await query; resolve(
objects = []; query.then((rows) => {
_.forEach(rows, (ruleRow) => { const result = [];
objects.push(ruleRow.id); _.forEach(rows, (rule_row) => {
result.push(rule_row.id);
}); });
// enum should not have less than 1 item // enum should not have less than 1 item
if (!objects.length) { if (!result.length) {
objects.push(0); result.push(0);
} }
return result;
}),
);
break;
// DEFAULT: null
default:
resolve(null);
break; break;
} }
} } else {
objectCache[objectType] = objects; resolve(object_cache[object_type]);
} }
} }
} else {
resolve(null);
}
}).then((objects) => {
object_cache[object_type] = objects;
return objects; return objects;
});
}; };
/** /**
* Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema * Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema
* *
* @param {String} permissionLabel * @param {String} permission_label
* @returns {Object} * @returns {Object}
*/ */
this.getObjectSchema = async (permissionLabel) => { this.getObjectSchema = (permission_label) => {
const baseObjectType = permissionLabel.split(":").shift(); const base_object_type = permission_label.split(":").shift();
const schema = { const schema = {
$id: "objects", $id: "objects",
@@ -177,39 +200,41 @@ export default function (tokenString) {
}, },
}; };
const result = await this.loadObjects(baseObjectType); return this.loadObjects(base_object_type).then((object_result) => {
if (typeof result === "object" && result !== null) { if (typeof object_result === "object" && object_result !== null) {
schema.properties[baseObjectType] = { schema.properties[base_object_type] = {
type: "number", type: "number",
enum: result, enum: object_result,
minimum: 1, minimum: 1,
}; };
} else { } else {
schema.properties[baseObjectType] = { schema.properties[base_object_type] = {
type: "number", type: "number",
minimum: 1, minimum: 1,
}; };
} }
return schema; return schema;
});
}; };
// here:
return { return {
token: Token, token: Token,
/** /**
* *
* @param {Boolean} [allowInternal] * @param {Boolean} [allow_internal]
* @returns {Promise} * @returns {Promise}
*/ */
load: async (allowInternal) => { load: (allow_internal) => {
if (tokenString) { return new Promise((resolve /*, reject*/) => {
return await Token.load(tokenString); if (token_string) {
resolve(Token.load(token_string));
} else {
allow_internal_access = allow_internal;
resolve(allow_internal_access || null);
} }
allowInternalAccess = allowInternal; });
return allowInternal || null;
}, },
reloadObjects: this.loadObjects, reloadObjects: this.loadObjects,
@@ -221,7 +246,7 @@ export default function (tokenString) {
* @returns {Promise} * @returns {Promise}
*/ */
can: async (permission, data) => { can: async (permission, data) => {
if (allowInternalAccess === true) { if (allow_internal_access === true) {
return true; return true;
} }
@@ -233,7 +258,7 @@ export default function (tokenString) {
[permission]: { [permission]: {
data: data, data: data,
scope: Token.get("scope"), scope: Token.get("scope"),
roles: userRoles, roles: user_roles,
permission_visibility: permissions.visibility, permission_visibility: permissions.visibility,
permission_proxy_hosts: permissions.proxy_hosts, permission_proxy_hosts: permissions.proxy_hosts,
permission_redirection_hosts: permissions.redirection_hosts, permission_redirection_hosts: permissions.redirection_hosts,
@@ -252,9 +277,10 @@ export default function (tokenString) {
properties: {}, properties: {},
}; };
const rawData = fs.readFileSync(`${__dirname}/access/${permission.replace(/:/gim, "-")}.json`, { const rawData = fs.readFileSync(
encoding: "utf8", `${__dirname}/access/${permission.replace(/:/gim, "-")}.json`,
}); { encoding: "utf8" },
);
permissionSchema.properties[permission] = JSON.parse(rawData); permissionSchema.properties[permission] = JSON.parse(rawData);
const ajv = new Ajv({ const ajv = new Ajv({

View File

@@ -6,6 +6,46 @@ 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 * ../global/certbot-dns-plugins.json
@@ -44,43 +84,4 @@ 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,13 +199,6 @@ 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
* *
@@ -241,4 +234,4 @@ const useLetsencryptServer = () => {
return null; return null;
}; };
export { isCI, configHas, configGet, isSqlite, isMysql, isPostgres, isDebugMode, getPrivateKey, getPublicKey, useLetsencryptStaging, useLetsencryptServer }; export { configHas, configGet, isSqlite, isMysql, isPostgres, isDebugMode, getPrivateKey, getPublicKey, useLetsencryptStaging, useLetsencryptServer };

View File

@@ -14,10 +14,7 @@ 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 = "Not Found"; this.message = `Item Not Found - ${id}`;
if (id) {
this.message = `Not Found - ${id}`;
}
this.public = true; this.public = true;
this.status = 404; this.status = 404;
}, },

View File

@@ -1,15 +1,15 @@
import Access from "../access.js"; import Access from "../access.js";
export default () => { export default () => {
return async (_, res, next) => { return (_, res, next) => {
try {
res.locals.access = null; res.locals.access = null;
const access = new Access(res.locals.token || null); const access = new Access(res.locals.token || null);
await access.load(); access
.load()
.then(() => {
res.locals.access = access; res.locals.access = access;
next(); next();
} catch (err) { })
next(err); .catch(next);
}
}; };
}; };

View File

@@ -14,27 +14,30 @@ const ajv = new Ajv({
* @param {Object} payload * @param {Object} payload
* @returns {Promise} * @returns {Promise}
*/ */
const apiValidator = async (schema, payload /*, description*/) => { function apiValidator(schema, payload /*, description*/) {
if (!schema) { return new Promise(function Promise_apiValidator(resolve, reject) {
throw new errs.ValidationError("Schema is undefined"); if (schema === null) {
reject(new errs.ValidationError("Schema is undefined"));
return;
} }
// Can't use falsy check here as valid payload could be `0` or `false`
if (typeof payload === "undefined") { if (typeof payload === "undefined") {
throw new errs.ValidationError("Payload is undefined"); reject(new errs.ValidationError("Payload is undefined"));
return;
} }
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; resolve(payload);
} } else {
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 = [validate.errors, payload];
throw err; reject(err);
}; }
});
}
export default apiValidator; export default apiValidator;

View File

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

View File

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

View File

@@ -38,7 +38,7 @@
}, },
"devDependencies": { "devDependencies": {
"@apidevtools/swagger-parser": "^10.1.0", "@apidevtools/swagger-parser": "^10.1.0",
"@biomejs/biome": "^2.2.4", "@biomejs/biome": "2.2.0",
"chalk": "4.1.2", "chalk": "4.1.2",
"nodemon": "^2.0.2" "nodemon": "^2.0.2"
}, },

View File

@@ -2,7 +2,6 @@ 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,
@@ -25,9 +24,8 @@ router
* *
* Retrieve all logs * Retrieve all logs
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
additionalProperties: false, additionalProperties: false,
properties: { properties: {
@@ -43,65 +41,14 @@ 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 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}`);
next(err);
}
});
/**
* Specific audit log entry
*
* /api/audit-log/123
*/
router
.route("/:event_id")
.options((_, res) => {
res.sendStatus(204);
}) })
.all(jwtdecode()) .then((rows) => {
res.status(200).send(rows);
/** })
* GET /api/audit-log/123 .catch(next);
*
* 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

@@ -1,7 +1,6 @@
import express from "express"; import express from "express";
import errs from "../lib/error.js"; import errs from "../lib/error.js";
import pjson from "../package.json" with { type: "json" }; import pjson from "../package.json" with { type: "json" };
import { isSetup } from "../setup.js";
import auditLogRoutes from "./audit-log.js"; import auditLogRoutes from "./audit-log.js";
import accessListsRoutes from "./nginx/access_lists.js"; import accessListsRoutes from "./nginx/access_lists.js";
import certificatesHostsRoutes from "./nginx/certificates.js"; import certificatesHostsRoutes from "./nginx/certificates.js";
@@ -25,13 +24,11 @@ const router = express.Router({
* Health Check * Health Check
* GET /api * GET /api
*/ */
router.get("/", async (_, res /*, next*/) => { router.get("/", (_, res /*, next*/) => {
const version = pjson.version.split("-").shift().split("."); const version = pjson.version.split("-").shift().split(".");
const setup = await isSetup();
res.status(200).send({ res.status(200).send({
status: "OK", status: "OK",
setup,
version: { version: {
major: Number.parseInt(version.shift(), 10), major: Number.parseInt(version.shift(), 10),
minor: Number.parseInt(version.shift(), 10), minor: Number.parseInt(version.shift(), 10),

View File

@@ -3,7 +3,6 @@ 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({
@@ -27,9 +26,8 @@ router
* *
* Retrieve all access-lists * Retrieve all access-lists
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
additionalProperties: false, additionalProperties: false,
properties: { properties: {
@@ -45,13 +43,14 @@ 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 rows = await internalAccessList.getAll(res.locals.access, data.expand, data.query); .then((data) => {
return internalAccessList.getAll(res.locals.access, data.expand, data.query);
})
.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);
}
}) })
/** /**
@@ -59,15 +58,15 @@ router
* *
* Create a new access-list * Create a new access-list
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/access-lists", "post"), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/access-lists", "post"), req.body); .then((payload) => {
const result = await internalAccessList.create(res.locals.access, payload); return internalAccessList.create(res.locals.access, payload);
})
.then((result) => {
res.status(201).send(result); res.status(201).send(result);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch(next);
next(err);
}
}); });
/** /**
@@ -87,9 +86,8 @@ router
* *
* Retrieve a specific access-list * Retrieve a specific access-list
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
required: ["list_id"], required: ["list_id"],
additionalProperties: false, additionalProperties: false,
@@ -106,16 +104,17 @@ router
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) => {
return internalAccessList.get(res.locals.access, {
id: Number.parseInt(data.list_id, 10), id: Number.parseInt(data.list_id, 10),
expand: data.expand, 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);
}
}) })
/** /**
@@ -123,16 +122,16 @@ router
* *
* Update and existing access-list * Update and existing access-list
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/access-lists/{listID}", "put"), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/access-lists/{listID}", "put"), req.body); .then((payload) => {
payload.id = Number.parseInt(req.params.list_id, 10); payload.id = Number.parseInt(req.params.list_id, 10);
const result = await internalAccessList.update(res.locals.access, payload); return internalAccessList.update(res.locals.access, payload);
})
.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);
}
}) })
/** /**
@@ -140,16 +139,13 @@ router
* *
* Delete and existing access-list * Delete and existing access-list
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalAccessList
const result = await internalAccessList.delete(res.locals.access, { .delete(res.locals.access, { id: Number.parseInt(req.params.list_id, 10) })
id: Number.parseInt(req.params.list_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

@@ -1,11 +1,9 @@
import express from "express"; import express from "express";
import dnsPlugins from "../../global/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({
@@ -29,9 +27,8 @@ router
* *
* Retrieve all certificates * Retrieve all certificates
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
additionalProperties: false, additionalProperties: false,
properties: { properties: {
@@ -47,13 +44,14 @@ 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 rows = await internalCertificate.getAll(res.locals.access, data.expand, data.query); .then((data) => {
return internalCertificate.getAll(res.locals.access, data.expand, data.query);
})
.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);
}
}) })
/** /**
@@ -61,50 +59,16 @@ router
* *
* Create a new certificate * Create a new certificate
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/certificates", "post"), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/certificates", "post"), req.body); .then((payload) => {
req.setTimeout(900000); // 15 minutes timeout req.setTimeout(900000); // 15 minutes timeout
const result = await internalCertificate.create(res.locals.access, payload); return internalCertificate.create(res.locals.access, 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()) .then((result) => {
res.status(201).send(result);
/** })
* GET /api/nginx/certificates/dns-providers .catch(next);
*
* 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);
}
}); });
/** /**
@@ -124,57 +88,18 @@ router
* *
* Test HTTP challenge for domains * Test HTTP challenge for domains
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
if (req.query.domains === undefined) { if (req.query.domains === undefined) {
next(new errs.ValidationError("Domains are required as query parameters")); next(new errs.ValidationError("Domains are required as query parameters"));
return; return;
} }
try { internalCertificate
const result = await internalCertificate.testHttpsChallenge( .testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains))
res.locals.access, .then((result) => {
JSON.parse(req.query.domains),
);
res.status(200).send(result); 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()) .catch(next);
/**
* 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;
}
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);
}
}); });
/** /**
@@ -194,9 +119,8 @@ router
* *
* Retrieve a specific certificate * Retrieve a specific certificate
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
required: ["certificate_id"], required: ["certificate_id"],
additionalProperties: false, additionalProperties: false,
@@ -213,16 +137,17 @@ router
certificate_id: req.params.certificate_id, certificate_id: req.params.certificate_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 internalCertificate.get(res.locals.access, { .then((data) => {
return internalCertificate.get(res.locals.access, {
id: Number.parseInt(data.certificate_id, 10), id: Number.parseInt(data.certificate_id, 10),
expand: data.expand, 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);
}
}) })
/** /**
@@ -230,16 +155,13 @@ router
* *
* Update and existing certificate * Update and existing certificate
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalCertificate
const result = await internalCertificate.delete(res.locals.access, { .delete(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);
}
}); });
/** /**
@@ -259,21 +181,19 @@ router
* *
* Upload certificates * Upload certificates
*/ */
.post(async (req, res, next) => { .post((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" });
return; } else {
} internalCertificate
.upload(res.locals.access, {
try {
const result = await internalCertificate.upload(res.locals.access, {
id: Number.parseInt(req.params.certificate_id, 10), id: Number.parseInt(req.params.certificate_id, 10),
files: req.files, files: req.files,
}); })
.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);
} }
}); });
@@ -294,17 +214,16 @@ router
* *
* Renew certificate * Renew certificate
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
req.setTimeout(900000); // 15 minutes timeout req.setTimeout(900000); // 15 minutes timeout
try { internalCertificate
const result = await internalCertificate.renew(res.locals.access, { .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);
}
}); });
/** /**
@@ -324,15 +243,46 @@ router
* *
* Renew certificate * Renew certificate
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { internalCertificate
const result = await internalCertificate.download(res.locals.access, { .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,7 +3,6 @@ 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({
@@ -27,9 +26,8 @@ router
* *
* Retrieve all dead-hosts * Retrieve all dead-hosts
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
additionalProperties: false, additionalProperties: false,
properties: { properties: {
@@ -45,13 +43,14 @@ 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 rows = await internalDeadHost.getAll(res.locals.access, data.expand, data.query); .then((data) => {
return internalDeadHost.getAll(res.locals.access, data.expand, data.query);
})
.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);
}
}) })
/** /**
@@ -59,15 +58,15 @@ router
* *
* Create a new dead-host * Create a new dead-host
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/dead-hosts", "post"), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/dead-hosts", "post"), req.body); .then((payload) => {
const result = await internalDeadHost.create(res.locals.access, payload); return internalDeadHost.create(res.locals.access, payload);
})
.then((result) => {
res.status(201).send(result); res.status(201).send(result);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch(next);
next(err);
}
}); });
/** /**
@@ -87,9 +86,8 @@ router
* *
* Retrieve a specific dead-host * Retrieve a specific dead-host
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
required: ["host_id"], required: ["host_id"],
additionalProperties: false, additionalProperties: false,
@@ -106,50 +104,48 @@ router
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) => {
return internalDeadHost.get(res.locals.access, {
id: Number.parseInt(data.host_id, 10), id: Number.parseInt(data.host_id, 10),
expand: data.expand, 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);
}
}) })
/** /**
* PUT /api/nginx/dead-hosts/123 * PUT /api/nginx/dead-hosts/123
* *
* Update an existing dead-host * Update and existing dead-host
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/dead-hosts/{hostID}", "put"), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/dead-hosts/{hostID}", "put"), req.body); .then((payload) => {
payload.id = Number.parseInt(req.params.host_id, 10); payload.id = Number.parseInt(req.params.host_id, 10);
const result = await internalDeadHost.update(res.locals.access, payload); return internalDeadHost.update(res.locals.access, payload);
})
.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);
}
}) })
/** /**
* DELETE /api/nginx/dead-hosts/123 * DELETE /api/nginx/dead-hosts/123
* *
* Delete a dead-host * Update and existing dead-host
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalDeadHost
const result = await internalDeadHost.delete(res.locals.access, { .delete(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) })
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);
}
}); });
/** /**
@@ -167,16 +163,13 @@ router
/** /**
* POST /api/nginx/dead-hosts/123/enable * POST /api/nginx/dead-hosts/123/enable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalDeadHost
const result = await internalDeadHost.enable(res.locals.access, { .enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) })
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);
}
}); });
/** /**
@@ -195,13 +188,12 @@ router
* POST /api/nginx/dead-hosts/123/disable * POST /api/nginx/dead-hosts/123/disable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
try { internalDeadHost
const result = internalDeadHost.disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }); .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,7 +3,6 @@ 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({
@@ -27,9 +26,8 @@ router
* *
* Retrieve all proxy-hosts * Retrieve all proxy-hosts
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
additionalProperties: false, additionalProperties: false,
properties: { properties: {
@@ -45,13 +43,14 @@ 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 rows = await internalProxyHost.getAll(res.locals.access, data.expand, data.query); .then((data) => {
return internalProxyHost.getAll(res.locals.access, data.expand, data.query);
})
.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);
}
}) })
/** /**
@@ -59,15 +58,15 @@ router
* *
* Create a new proxy-host * Create a new proxy-host
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/proxy-hosts", "post"), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/proxy-hosts", "post"), req.body); .then((payload) => {
const result = await internalProxyHost.create(res.locals.access, payload); return internalProxyHost.create(res.locals.access, payload);
})
.then((result) => {
res.status(201).send(result); res.status(201).send(result);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch(next);
next(err);
}
}); });
/** /**
@@ -87,9 +86,8 @@ router
* *
* Retrieve a specific proxy-host * Retrieve a specific proxy-host
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
required: ["host_id"], required: ["host_id"],
additionalProperties: false, additionalProperties: false,
@@ -106,16 +104,17 @@ router
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) => {
return internalProxyHost.get(res.locals.access, {
id: Number.parseInt(data.host_id, 10), id: Number.parseInt(data.host_id, 10),
expand: data.expand, 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);
}
}) })
/** /**
@@ -123,16 +122,16 @@ router
* *
* Update and existing proxy-host * Update and existing proxy-host
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/proxy-hosts/{hostID}", "put"), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/proxy-hosts/{hostID}", "put"), req.body); .then((payload) => {
payload.id = Number.parseInt(req.params.host_id, 10); payload.id = Number.parseInt(req.params.host_id, 10);
const result = await internalProxyHost.update(res.locals.access, payload); return internalProxyHost.update(res.locals.access, payload);
})
.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);
}
}) })
/** /**
@@ -140,16 +139,13 @@ router
* *
* Update and existing proxy-host * Update and existing proxy-host
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalProxyHost
const result = await internalProxyHost.delete(res.locals.access, { .delete(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) })
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);
}
}); });
/** /**
@@ -167,16 +163,13 @@ router
/** /**
* POST /api/nginx/proxy-hosts/123/enable * POST /api/nginx/proxy-hosts/123/enable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalProxyHost
const result = await internalProxyHost.enable(res.locals.access, { .enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) })
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);
}
}); });
/** /**
@@ -194,16 +187,13 @@ router
/** /**
* POST /api/nginx/proxy-hosts/123/disable * POST /api/nginx/proxy-hosts/123/disable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalProxyHost
const result = await internalProxyHost.disable(res.locals.access, { .disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) })
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,7 +3,6 @@ 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({
@@ -27,9 +26,8 @@ router
* *
* Retrieve all redirection-hosts * Retrieve all redirection-hosts
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
additionalProperties: false, additionalProperties: false,
properties: { properties: {
@@ -45,13 +43,14 @@ 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 rows = await internalRedirectionHost.getAll(res.locals.access, data.expand, data.query); .then((data) => {
return internalRedirectionHost.getAll(res.locals.access, data.expand, data.query);
})
.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);
}
}) })
/** /**
@@ -59,15 +58,15 @@ router
* *
* Create a new redirection-host * Create a new redirection-host
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/redirection-hosts", "post"), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/redirection-hosts", "post"), req.body); .then((payload) => {
const result = await internalRedirectionHost.create(res.locals.access, payload); return internalRedirectionHost.create(res.locals.access, payload);
})
.then((result) => {
res.status(201).send(result); res.status(201).send(result);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch(next);
next(err);
}
}); });
/** /**
@@ -87,9 +86,8 @@ router
* *
* Retrieve a specific redirection-host * Retrieve a specific redirection-host
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
required: ["host_id"], required: ["host_id"],
additionalProperties: false, additionalProperties: false,
@@ -106,16 +104,17 @@ router
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) => {
return internalRedirectionHost.get(res.locals.access, {
id: Number.parseInt(data.host_id, 10), id: Number.parseInt(data.host_id, 10),
expand: data.expand, 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);
}
}) })
/** /**
@@ -123,19 +122,16 @@ router
* *
* Update and existing redirection-host * Update and existing redirection-host
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/redirection-hosts/{hostID}", "put"), req.body)
const payload = await apiValidator( .then((payload) => {
getValidationSchema("/nginx/redirection-hosts/{hostID}", "put"),
req.body,
);
payload.id = Number.parseInt(req.params.host_id, 10); payload.id = Number.parseInt(req.params.host_id, 10);
const result = await internalRedirectionHost.update(res.locals.access, payload); return internalRedirectionHost.update(res.locals.access, payload);
})
.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);
}
}) })
/** /**
@@ -143,16 +139,13 @@ router
* *
* Update and existing redirection-host * Update and existing redirection-host
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalRedirectionHost
const result = await internalRedirectionHost.delete(res.locals.access, { .delete(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) })
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);
}
}); });
/** /**
@@ -170,16 +163,13 @@ router
/** /**
* POST /api/nginx/redirection-hosts/123/enable * POST /api/nginx/redirection-hosts/123/enable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalRedirectionHost
const result = await internalRedirectionHost.enable(res.locals.access, { .enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) })
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);
}
}); });
/** /**
@@ -197,16 +187,13 @@ router
/** /**
* POST /api/nginx/redirection-hosts/123/disable * POST /api/nginx/redirection-hosts/123/disable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalRedirectionHost
const result = await internalRedirectionHost.disable(res.locals.access, { .disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) })
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,7 +3,6 @@ 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({
@@ -27,9 +26,8 @@ router
* *
* Retrieve all streams * Retrieve all streams
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
additionalProperties: false, additionalProperties: false,
properties: { properties: {
@@ -45,13 +43,14 @@ 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 rows = await internalStream.getAll(res.locals.access, data.expand, data.query); .then((data) => {
return internalStream.getAll(res.locals.access, data.expand, data.query);
})
.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);
}
}) })
/** /**
@@ -59,15 +58,15 @@ router
* *
* Create a new stream * Create a new stream
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/streams", "post"), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/streams", "post"), req.body); .then((payload) => {
const result = await internalStream.create(res.locals.access, payload); return internalStream.create(res.locals.access, payload);
})
.then((result) => {
res.status(201).send(result); res.status(201).send(result);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch(next);
next(err);
}
}); });
/** /**
@@ -87,9 +86,8 @@ router
* *
* Retrieve a specific stream * Retrieve a specific stream
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
required: ["stream_id"], required: ["stream_id"],
additionalProperties: false, additionalProperties: false,
@@ -106,16 +104,17 @@ router
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) => {
return internalStream.get(res.locals.access, {
id: Number.parseInt(data.stream_id, 10), id: Number.parseInt(data.stream_id, 10),
expand: data.expand, 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);
}
}) })
/** /**
@@ -123,16 +122,16 @@ router
* *
* Update and existing stream * Update and existing stream
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(getValidationSchema("/nginx/streams/{streamID}", "put"), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/streams/{streamID}", "put"), req.body); .then((payload) => {
payload.id = Number.parseInt(req.params.stream_id, 10); payload.id = Number.parseInt(req.params.stream_id, 10);
const result = await internalStream.update(res.locals.access, payload); return internalStream.update(res.locals.access, payload);
})
.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);
}
}) })
/** /**
@@ -140,16 +139,13 @@ router
* *
* Update and existing stream * Update and existing stream
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalStream
const result = await internalStream.delete(res.locals.access, { .delete(res.locals.access, { id: Number.parseInt(req.params.stream_id, 10) })
id: Number.parseInt(req.params.stream_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);
}
}); });
/** /**
@@ -167,16 +163,13 @@ router
/** /**
* POST /api/nginx/streams/123/enable * POST /api/nginx/streams/123/enable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalStream
const result = await internalStream.enable(res.locals.access, { .enable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) })
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);
}
}); });
/** /**
@@ -194,16 +187,13 @@ router
/** /**
* POST /api/nginx/streams/123/disable * POST /api/nginx/streams/123/disable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalStream
const result = await internalStream.disable(res.locals.access, { .disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) })
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

@@ -1,7 +1,6 @@
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,
@@ -14,19 +13,17 @@ router
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode())
/** /**
* GET /reports/hosts * GET /reports/hosts
*/ */
.get(async (req, res, next) => { .get(jwtdecode(), (_, res, next) => {
try { internalReport
const data = await internalReport.getHostsReport(res.locals.access); .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,5 +1,4 @@
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";
@@ -19,7 +18,6 @@ router
* GET /schema * GET /schema
*/ */
.get(async (req, res) => { .get(async (req, res) => {
try {
const swaggerJSON = await getCompiledSchema(); const swaggerJSON = await getCompiledSchema();
let proto = req.protocol; let proto = req.protocol;
@@ -35,10 +33,6 @@ router
swaggerJSON.info.version = PACKAGE.version; swaggerJSON.info.version = PACKAGE.version;
swaggerJSON.servers[0].url = `${origin}/api`; swaggerJSON.servers[0].url = `${origin}/api`;
res.status(200).send(swaggerJSON); res.status(200).send(swaggerJSON);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; export default router;

View File

@@ -3,7 +3,6 @@ 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({
@@ -27,14 +26,13 @@ router
* *
* Retrieve all settings * Retrieve all settings
*/ */
.get(async (req, res, next) => { .get((_, res, next) => {
try { internalSetting
const rows = await internalSetting.getAll(res.locals.access); .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);
}
}); });
/** /**
@@ -54,9 +52,8 @@ router
* *
* Retrieve a specific setting * Retrieve a specific setting
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
required: ["setting_id"], required: ["setting_id"],
additionalProperties: false, additionalProperties: false,
@@ -70,15 +67,16 @@ router
{ {
setting_id: req.params.setting_id, setting_id: req.params.setting_id,
}, },
); )
const row = await internalSetting.get(res.locals.access, { .then((data) => {
return internalSetting.get(res.locals.access, {
id: data.setting_id, id: data.setting_id,
}); });
})
.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);
}
}) })
/** /**
@@ -86,16 +84,16 @@ router
* *
* Update and existing setting * Update and existing setting
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(getValidationSchema("/settings/{settingID}", "put"), req.body)
const payload = await apiValidator(getValidationSchema("/settings/{settingID}", "put"), req.body); .then((payload) => {
payload.id = req.params.setting_id; payload.id = req.params.setting_id;
const result = await internalSetting.update(res.locals.access, payload); return internalSetting.update(res.locals.access, payload);
})
.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

@@ -2,7 +2,6 @@ import express from "express";
import internalToken from "../internal/token.js"; import internalToken from "../internal/token.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 { 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({
@@ -24,17 +23,16 @@ router
* We also piggy back on to this method, allowing admins to get tokens * We also piggy back on to this method, allowing admins to get tokens
* for services like Job board and Worker. * for services like Job board and Worker.
*/ */
.get(jwtdecode(), async (req, res, next) => { .get(jwtdecode(), (req, res, next) => {
try { internalToken
const data = await internalToken.getFreshToken(res.locals.access, { .getFreshToken(res.locals.access, {
expiry: typeof req.query.expiry !== "undefined" ? req.query.expiry : null, expiry: typeof req.query.expiry !== "undefined" ? req.query.expiry : null,
scope: typeof req.query.scope !== "undefined" ? req.query.scope : null, scope: typeof req.query.scope !== "undefined" ? req.query.scope : null,
}); })
.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);
}
}) })
/** /**
@@ -43,14 +41,12 @@ router
* Create a new Token * Create a new Token
*/ */
.post(async (req, res, next) => { .post(async (req, res, next) => {
try { apiValidator(getValidationSchema("/tokens", "post"), req.body)
const data = await apiValidator(getValidationSchema("/tokens", "post"), req.body); .then(internalToken.getTokenFromEmail)
const result = await internalToken.getTokenFromEmail(data); .then((data) => {
res.status(200).send(result); 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,15 +1,10 @@
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 { 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";
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";
import { isSetup } from "../setup.js";
const router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
@@ -32,9 +27,8 @@ router
* *
* Retrieve all users * Retrieve all users
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
additionalProperties: false, additionalProperties: false,
properties: { properties: {
@@ -47,23 +41,21 @@ router
}, },
}, },
{ {
expand: expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
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( .then((data) => {
res.locals.access, return internalUser.getAll(res.locals.access, data.expand, data.query);
data.expand, })
data.query, .then((users) => {
);
res.status(200).send(users); res.status(200).send(users);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch((err) => {
console.log(err);
next(err); next(err);
} });
//.catch(next);
}) })
/** /**
@@ -71,66 +63,15 @@ router
* *
* Create a new User * Create a new User
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
const body = req.body; apiValidator(getValidationSchema("/users", "post"), req.body)
.then((payload) => {
try { return internalUser.create(res.locals.access, payload);
// If we are in setup mode, we don't check access for current user
const setup = await isSetup();
if (!setup) {
logger.info("Creating a new user in setup mode");
const access = new Access(null);
await access.load(true);
res.locals.access = access;
// We are in setup mode, set some defaults for this first new user, such as making
// them an admin.
body.is_disabled = false;
if (typeof body.roles !== "object" || body.roles === null) {
body.roles = [];
}
if (body.roles.indexOf("admin") === -1) {
body.roles.push("admin");
}
}
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);
}
}) })
.then((result) => {
/** res.status(201).send(result);
* DELETE /api/users })
* .catch(next);
* 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());
}); });
/** /**
@@ -151,9 +92,8 @@ router
* *
* Retrieve a specific user * Retrieve a specific user
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator(
const data = await validator(
{ {
required: ["user_id"], required: ["user_id"],
additionalProperties: false, additionalProperties: false,
@@ -168,26 +108,23 @@ router
}, },
{ {
user_id: req.params.user_id, user_id: req.params.user_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) => {
const user = await internalUser.get(res.locals.access, { return internalUser.get(res.locals.access, {
id: data.user_id, id: data.user_id,
expand: data.expand, expand: data.expand,
omit: internalUser.getUserOmisionsByAccess( omit: internalUser.getUserOmisionsByAccess(res.locals.access, data.user_id),
res.locals.access,
data.user_id,
),
}); });
})
.then((user) => {
res.status(200).send(user); res.status(200).send(user);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch((err) => {
console.log(err);
next(err); next(err);
} });
}) })
/** /**
@@ -195,19 +132,16 @@ router
* *
* Update and existing user * Update and existing user
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(getValidationSchema("/users/{userID}", "put"), req.body)
const payload = await apiValidator( .then((payload) => {
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); return internalUser.update(res.locals.access, payload);
})
.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);
}
}) })
/** /**
@@ -215,16 +149,13 @@ router
* *
* Update and existing user * Update and existing user
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalUser
const result = await internalUser.delete(res.locals.access, { .delete(res.locals.access, { id: req.params.user_id })
id: req.params.user_id, .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);
}
}); });
/** /**
@@ -245,19 +176,16 @@ router
* *
* Update password for a user * Update password for a user
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(getValidationSchema("/users/{userID}/auth", "put"), req.body)
const payload = await apiValidator( .then((payload) => {
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); return internalUser.setPassword(res.locals.access, payload);
})
.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);
}
}); });
/** /**
@@ -278,22 +206,16 @@ router
* *
* Set some or all permissions for a user * Set some or all permissions for a user
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(getValidationSchema("/users/{userID}/permissions", "put"), req.body)
const payload = await apiValidator( .then((payload) => {
getValidationSchema("/users/{userID}/permissions", "put"),
req.body,
);
payload.id = req.params.user_id; payload.id = req.params.user_id;
const result = await internalUser.setPermissions( return internalUser.setPermissions(res.locals.access, payload);
res.locals.access, })
payload, .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);
}
}); });
/** /**
@@ -313,16 +235,13 @@ router
* *
* Log in as a user * Log in as a user
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalUser
const result = await internalUser.loginAs(res.locals.access, { .loginAs(res.locals.access, { id: Number.parseInt(req.params.user_id, 10) })
id: Number.parseInt(req.params.user_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

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

View File

@@ -1,16 +1,7 @@
{ {
"type": "object", "type": "object",
"description": "Audit Log object", "description": "Audit Log object",
"required": [ "required": ["id", "created_on", "modified_on", "user_id", "object_type", "object_id", "action", "meta"],
"id",
"created_on",
"modified_on",
"user_id",
"object_type",
"object_id",
"action",
"meta"
],
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"id": { "id": {
@@ -36,9 +27,6 @@
}, },
"meta": { "meta": {
"type": "object" "type": "object"
},
"user": {
"$ref": "./user-object.json"
} }
} }
} }

View File

@@ -62,9 +62,15 @@
"dns_provider_credentials": { "dns_provider_credentials": {
"type": "string" "type": "string"
}, },
"letsencrypt_agree": {
"type": "boolean"
},
"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

View File

@@ -9,11 +9,6 @@
"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",

View File

@@ -31,7 +31,7 @@
}, },
{ {
"type": "string", "type": "string",
"format": "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$" "format": "ipv4"
}, },
{ {
"type": "string", "type": "string",

View File

@@ -54,63 +54,6 @@
"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": "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)$"
}
}
} }
} }
} }

View File

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

View File

@@ -1,73 +0,0 @@
{
"operationId": "getAuditLog",
"summary": "Get Audit Log Event",
"tags": [
"Audit Log"
],
"security": [
{
"BearerAuth": [
"audit-log"
]
}
],
"parameters": [
{
"in": "path",
"name": "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

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

View File

@@ -36,6 +36,8 @@
"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

@@ -37,6 +37,8 @@
"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

@@ -36,6 +36,8 @@
"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

@@ -52,6 +52,8 @@
"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

@@ -37,9 +37,6 @@
}, },
"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"
} }
} }
} }

View File

@@ -29,11 +29,6 @@
"$ref": "./paths/audit-log/get.json" "$ref": "./paths/audit-log/get.json"
} }
}, },
"/audit-log/{id}": {
"get": {
"$ref": "./paths/audit-log/id/get.json"
}
},
"/nginx/access-lists": { "/nginx/access-lists": {
"get": { "get": {
"$ref": "./paths/nginx/access-lists/get.json" "$ref": "./paths/nginx/access-lists/get.json"

View File

@@ -7,33 +7,24 @@ import settingModel from "./models/setting.js";
import userModel from "./models/user.js"; import userModel from "./models/user.js";
import userPermissionModel from "./models/user_permission.js"; import userPermissionModel from "./models/user_permission.js";
export const isSetup = async () => {
const row = await userModel.query().select("id").where("is_deleted", 0).first();
return row?.id > 0;
}
/** /**
* Creates a default admin users if one doesn't already exist in the database * Creates a default admin users if one doesn't already exist in the database
* *
* @returns {Promise} * @returns {Promise}
*/ */
const setupDefaultUser = async () => { const setupDefaultUser = () => {
const initialAdminEmail = process.env.INITIAL_ADMIN_EMAIL; return userModel
const initialAdminPassword = process.env.INITIAL_ADMIN_PASSWORD; .query()
.select("id")
// This will only create a new user when there are no active users in the database .where("is_deleted", 0)
// and the INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD environment variables are set. .first()
// Otherwise, users should be shown the setup wizard in the frontend. .then((row) => {
// I'm keeping this legacy behavior in case some people are automating deployments. if (!row || !row.id) {
if (!initialAdminEmail || !initialAdminPassword) {
return Promise.resolve();
}
const userIsetup = await isSetup();
if (!userIsetup) {
// Create a new user and set password // Create a new user and set password
logger.info(`Creating a new user: ${initialAdminEmail} with password: ${initialAdminPassword}`); const email = (process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com').toLowerCase();
const password = process.env.INITIAL_ADMIN_PASSWORD || "changeme";
logger.info(`Creating a new user: ${email} with password: ${password}`);
const data = { const data = {
is_deleted: 0, is_deleted: 0,
@@ -44,20 +35,20 @@ const setupDefaultUser = async () => {
roles: ["admin"], roles: ["admin"],
}; };
const user = await userModel return userModel
.query() .query()
.insertAndFetch(data); .insertAndFetch(data)
.then((user) => {
await authModel return authModel
.query() .query()
.insert({ .insert({
user_id: user.id, user_id: user.id,
type: "password", type: "password",
secret: password, secret: password,
meta: {}, meta: {},
}); })
.then(() => {
await userPermissionModel.query().insert({ return userPermissionModel.query().insert({
user_id: user.id, user_id: user.id,
visibility: "all", visibility: "all",
proxy_hosts: "manage", proxy_hosts: "manage",
@@ -67,8 +58,14 @@ const setupDefaultUser = async () => {
access_lists: "manage", access_lists: "manage",
certificates: "manage", certificates: "manage",
}); });
});
})
.then(() => {
logger.info("Initial admin setup completed"); logger.info("Initial admin setup completed");
});
} }
logger.debug("Admin user setup not required");
});
}; };
/** /**
@@ -76,15 +73,15 @@ const setupDefaultUser = async () => {
* *
* @returns {Promise} * @returns {Promise}
*/ */
const setupDefaultSettings = async () => { const setupDefaultSettings = () => {
const row = await settingModel return settingModel
.query() .query()
.select("id") .select("id")
.where({ id: "default-site" }) .where({ id: "default-site" })
.first(); .first()
.then((row) => {
if (!row?.id) { if (!row || !row.id) {
await settingModel settingModel
.query() .query()
.insert({ .insert({
id: "default-site", id: "default-site",
@@ -92,9 +89,13 @@ const setupDefaultSettings = async () => {
description: "What to show when Nginx is hit with an unknown Host", description: "What to show when Nginx is hit with an unknown Host",
value: "congratulations", value: "congratulations",
meta: {}, meta: {},
}); })
.then(() => {
logger.info("Default settings added"); logger.info("Default settings added");
});
} }
logger.debug("Default setting setup not required");
});
}; };
/** /**
@@ -102,12 +103,12 @@ const setupDefaultSettings = async () => {
* *
* @returns {Promise} * @returns {Promise}
*/ */
const setupCertbotPlugins = async () => { const setupCertbotPlugins = () => {
const certificates = await certificateModel return certificateModel
.query() .query()
.where("is_deleted", 0) .where("is_deleted", 0)
.andWhere("provider", "letsencrypt"); .andWhere("provider", "letsencrypt")
.then((certificates) => {
if (certificates?.length) { if (certificates?.length) {
const plugins = []; const plugins = [];
const promises = []; const promises = [];
@@ -121,24 +122,24 @@ const setupCertbotPlugins = async () => {
// Make sure credentials file exists // Make sure credentials file exists
const credentials_loc = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; const credentials_loc = `/etc/letsencrypt/credentials/credentials-${certificate.id}`;
// Escape single quotes and backslashes // Escape single quotes and backslashes
if (typeof certificate.meta.dns_provider_credentials === "string") {
const escapedCredentials = certificate.meta.dns_provider_credentials const escapedCredentials = certificate.meta.dns_provider_credentials
.replaceAll("'", "\\'") .replaceAll("'", "\\'")
.replaceAll("\\", "\\\\"); .replaceAll("\\", "\\\\");
const credentials_cmd = `[ -f '${credentials_loc}' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo '${escapedCredentials}' > '${credentials_loc}' && chmod 600 '${credentials_loc}'; }`; const credentials_cmd = `[ -f '${credentials_loc}' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo '${escapedCredentials}' > '${credentials_loc}' && chmod 600 '${credentials_loc}'; }`;
promises.push(utils.exec(credentials_cmd)); promises.push(utils.exec(credentials_cmd));
} }
}
return true; return true;
}); });
await installPlugins(plugins); return installPlugins(plugins).then(() => {
if (promises.length) { if (promises.length) {
await Promise.all(promises); return Promise.all(promises).then(() => {
logger.info(`Added Certbot plugins ${plugins.join(", ")}`); logger.info(`Added Certbot plugins ${plugins.join(", ")}`);
});
} }
});
} }
});
}; };
/** /**

View File

@@ -43,59 +43,59 @@
ajv-draft-04 "^1.0.0" ajv-draft-04 "^1.0.0"
call-me-maybe "^1.0.2" call-me-maybe "^1.0.2"
"@biomejs/biome@^2.2.4": "@biomejs/biome@2.2.0":
version "2.2.4" version "2.2.0"
resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.2.4.tgz#184e4b83f89bd0d4151682a5aa3840df37748e17" resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.2.0.tgz#823ba77363651f310c47909747c879791ebd15c9"
integrity sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg== integrity sha512-3On3RSYLsX+n9KnoSgfoYlckYBoU6VRM22cw1gB4Y0OuUVSYd/O/2saOJMrA4HFfA1Ff0eacOvMN1yAAvHtzIw==
optionalDependencies: optionalDependencies:
"@biomejs/cli-darwin-arm64" "2.2.4" "@biomejs/cli-darwin-arm64" "2.2.0"
"@biomejs/cli-darwin-x64" "2.2.4" "@biomejs/cli-darwin-x64" "2.2.0"
"@biomejs/cli-linux-arm64" "2.2.4" "@biomejs/cli-linux-arm64" "2.2.0"
"@biomejs/cli-linux-arm64-musl" "2.2.4" "@biomejs/cli-linux-arm64-musl" "2.2.0"
"@biomejs/cli-linux-x64" "2.2.4" "@biomejs/cli-linux-x64" "2.2.0"
"@biomejs/cli-linux-x64-musl" "2.2.4" "@biomejs/cli-linux-x64-musl" "2.2.0"
"@biomejs/cli-win32-arm64" "2.2.4" "@biomejs/cli-win32-arm64" "2.2.0"
"@biomejs/cli-win32-x64" "2.2.4" "@biomejs/cli-win32-x64" "2.2.0"
"@biomejs/cli-darwin-arm64@2.2.4": "@biomejs/cli-darwin-arm64@2.2.0":
version "2.2.4" version "2.2.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.4.tgz#9b50620c93501e370b7e6d5a8445f117f9946a0c" resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.0.tgz#1abf9508e7d0776871710687ddad36e692dce3bc"
integrity sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA== integrity sha512-zKbwUUh+9uFmWfS8IFxmVD6XwqFcENjZvEyfOxHs1epjdH3wyyMQG80FGDsmauPwS2r5kXdEM0v/+dTIA9FXAg==
"@biomejs/cli-darwin-x64@2.2.4": "@biomejs/cli-darwin-x64@2.2.0":
version "2.2.4" version "2.2.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.4.tgz#343620c884fc8141155d114430e80e4eacfddc9e" resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.0.tgz#3a51aa569505fedd3a32bb914d608ec27d87f26d"
integrity sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg== integrity sha512-+OmT4dsX2eTfhD5crUOPw3RPhaR+SKVspvGVmSdZ9y9O/AgL8pla6T4hOn1q+VAFBHuHhsdxDRJgFCSC7RaMOw==
"@biomejs/cli-linux-arm64-musl@2.2.4": "@biomejs/cli-linux-arm64-musl@2.2.0":
version "2.2.4" version "2.2.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.4.tgz#cabcdadce2bc88b697f4063374224266c6f8b6e5" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.0.tgz#4d720930732a825b7a8c7cfe1741aec9e7d5ae1d"
integrity sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ== integrity sha512-egKpOa+4FL9YO+SMUMLUvf543cprjevNc3CAgDNFLcjknuNMcZ0GLJYa3EGTCR2xIkIUJDVneBV3O9OcIlCEZQ==
"@biomejs/cli-linux-arm64@2.2.4": "@biomejs/cli-linux-arm64@2.2.0":
version "2.2.4" version "2.2.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.4.tgz#55620f8f088145e62e1158eb85c568554d0c8673" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.0.tgz#d0a5c153ff9243b15600781947d70d6038226feb"
integrity sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw== integrity sha512-6eoRdF2yW5FnW9Lpeivh7Mayhq0KDdaDMYOJnH9aT02KuSIX5V1HmWJCQQPwIQbhDh68Zrcpl8inRlTEan0SXw==
"@biomejs/cli-linux-x64-musl@2.2.4": "@biomejs/cli-linux-x64-musl@2.2.0":
version "2.2.4" version "2.2.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.4.tgz#6bfaea72505afdbda66a66c998d2d169a8b55f90" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.0.tgz#946095b0a444f395b2df9244153e1cd6b07404c0"
integrity sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg== integrity sha512-I5J85yWwUWpgJyC1CcytNSGusu2p9HjDnOPAFG4Y515hwRD0jpR9sT9/T1cKHtuCvEQ/sBvx+6zhz9l9wEJGAg==
"@biomejs/cli-linux-x64@2.2.4": "@biomejs/cli-linux-x64@2.2.0":
version "2.2.4" version "2.2.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.4.tgz#8c1ed61dcafb8a5939346c714ec122651f57e1db" resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.0.tgz#ae01e0a70c7cd9f842c77dfb4ebd425734667a34"
integrity sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ== integrity sha512-5UmQx/OZAfJfi25zAnAGHUMuOd+LOsliIt119x2soA2gLggQYrVPA+2kMUxR6Mw5M1deUF/AWWP2qpxgH7Nyfw==
"@biomejs/cli-win32-arm64@2.2.4": "@biomejs/cli-win32-arm64@2.2.0":
version "2.2.4" version "2.2.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.4.tgz#b2528f6c436e753d6083d7779f0662e08786cedb" resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.0.tgz#09a3988b9d4bab8b8b3a41b4de9560bf70943964"
integrity sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ== integrity sha512-n9a1/f2CwIDmNMNkFs+JI0ZjFnMO0jdOyGNtihgUNFnlmd84yIYY2KMTBmMV58ZlVHjgmY5Y6E1hVTnSRieggA==
"@biomejs/cli-win32-x64@2.2.4": "@biomejs/cli-win32-x64@2.2.0":
version "2.2.4" version "2.2.0"
resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.4.tgz#c8e21413120fe073fa49b78fdd987022941ff66f" resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.0.tgz#5d2523b421d847b13fac146cf745436ea8a72b95"
integrity sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg== integrity sha512-Nawu5nHjP/zPKTIryh2AavzTc/KEg4um/MxWdXW0A6P/RZOyIpa7+QSjeXwAwX/utJGaCoXRPWtF3m5U/bB3Ww==
"@gar/promisify@^1.0.1": "@gar/promisify@^1.0.1":
version "1.1.3" version "1.1.3"

View File

@@ -15,7 +15,7 @@ ENV SUPPRESS_NO_CONFIG_WARNING=1 \
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
&& apt-get update \ && apt-get update \
&& apt-get install -y jq python3-pip logrotate moreutils \ && apt-get install -y jq python3-pip logrotate \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*

View File

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

View File

@@ -18,7 +18,6 @@ services:
- website2.example.com - website2.example.com
- website3.example.com - website3.example.com
environment: environment:
TZ: "${TZ:-Australia/Brisbane}"
PUID: 1000 PUID: 1000
PGID: 1000 PGID: 1000
FORCE_COLOR: 1 FORCE_COLOR: 1
@@ -50,9 +49,8 @@ services:
- ../backend:/app - ../backend:/app
- ../frontend:/app/frontend - ../frontend:/app/frontend
- ../global:/app/global - ../global:/app/global
- '/etc/localtime:/etc/localtime:ro'
healthcheck: healthcheck:
test: [ "CMD", "/usr/bin/check-health" ] test: ["CMD", "/usr/bin/check-health"]
interval: 10s interval: 10s
timeout: 3s timeout: 3s
depends_on: depends_on:
@@ -71,14 +69,12 @@ services:
networks: networks:
- nginx_proxy_manager - nginx_proxy_manager
environment: environment:
TZ: "${TZ:-Australia/Brisbane}"
MYSQL_ROOT_PASSWORD: 'npm' MYSQL_ROOT_PASSWORD: 'npm'
MYSQL_DATABASE: 'npm' MYSQL_DATABASE: 'npm'
MYSQL_USER: 'npm' MYSQL_USER: 'npm'
MYSQL_PASSWORD: 'npm' MYSQL_PASSWORD: 'npm'
volumes: volumes:
- db_data:/var/lib/mysql - db_data:/var/lib/mysql
- '/etc/localtime:/etc/localtime:ro'
db-postgres: db-postgres:
image: postgres:latest image: postgres:latest
@@ -204,7 +200,7 @@ services:
- nginx_proxy_manager - nginx_proxy_manager
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: [ 'CMD-SHELL', 'redis-cli ping | grep PONG' ] test: ['CMD-SHELL', 'redis-cli ping | grep PONG']
start_period: 20s start_period: 20s
interval: 30s interval: 30s
retries: 5 retries: 5

View File

@@ -5,7 +5,7 @@
"preview": "vitepress preview" "preview": "vitepress preview"
}, },
"devDependencies": { "devDependencies": {
"vitepress": "^1.6.4" "vitepress": "^1.4.0"
}, },
"dependencies": {} "dependencies": {}
} }

View File

@@ -228,13 +228,3 @@ To enable the geoip2 module, you can create the custom configuration file `/data
load_module /usr/lib/nginx/modules/ngx_http_geoip2_module.so; load_module /usr/lib/nginx/modules/ngx_http_geoip2_module.so;
load_module /usr/lib/nginx/modules/ngx_stream_geoip2_module.so; load_module /usr/lib/nginx/modules/ngx_stream_geoip2_module.so;
``` ```
## Auto Initial User Creation
Setting these environment variables will create the default user on startup, skipping the UI first user setup screen:
```
environment:
INITIAL_ADMIN_EMAIL: my@example.com
INITIAL_ADMIN_PASSWORD: mypassword1
```

View File

@@ -23,10 +23,4 @@ Your best bet is to ask the [Reddit community for support](https://www.reddit.co
## When adding username and password access control to a proxy host, I can no longer login into the app. ## When adding username and password access control to a proxy host, I can no longer login into the app.
Having an Access Control List (ACL) with username and password requires the browser to always send this username Having an Access Control List (ACL) with username and password requires the browser to always send this username and password in the `Authorization` header on each request. If your proxied app also requires authentication (like Nginx Proxy Manager itself), most likely the app will also use the `Authorization` header to transmit this information, as this is the standardized header meant for this kind of information. However having multiples of the same headers is not allowed in the [internet standard](https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2) and almost all apps do not support multiple values in the `Authorization` header. Hence one of the two logins will be broken. This can only be fixed by either removing one of the logins or by changing the app to use other non-standard headers for authorization.
and password in the `Authorization` header on each request. If your proxied app also requires authentication (like
Nginx Proxy Manager itself), most likely the app will also use the `Authorization` header to transmit this information,
as this is the standardized header meant for this kind of information. However having multiples of the same headers
is not allowed in the [internet standard](https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2) and almost all apps
do not support multiple values in the `Authorization` header. Hence one of the two logins will be broken. This can
only be fixed by either removing one of the logins or by changing the app to use other non-standard headers for authorization.

View File

@@ -35,7 +35,7 @@ so that the barrier for entry here is low.
## Features ## Features
- Beautiful and Secure Admin Interface based on [Tabler](https://tabler.io/) - Beautiful and Secure Admin Interface based on [Tabler](https://tabler.github.io/)
- Easily create forwarding domains, redirections, streams and 404 hosts without knowing anything about Nginx - Easily create forwarding domains, redirections, streams and 404 hosts without knowing anything about Nginx
- Free SSL using Let's Encrypt or provide your own custom SSL certificates - Free SSL using Let's Encrypt or provide your own custom SSL certificates
- Access Lists and basic HTTP Authentication for your hosts - Access Lists and basic HTTP Authentication for your hosts
@@ -66,8 +66,6 @@ services:
app: app:
image: 'jc21/nginx-proxy-manager:latest' image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped restart: unless-stopped
environment:
TZ: "Australia/Brisbane"
ports: ports:
- '80:80' - '80:80'
- '81:81' - '81:81'
@@ -91,10 +89,17 @@ docker compose up -d
4. Log in to the Admin UI 4. Log in to the Admin UI
When your docker container is running, connect to it on port `81` for the admin interface. When your docker container is running, connect to it on port `81` for the admin interface.
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)
This startup can take a minute depending on your hardware. 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

@@ -13,7 +13,6 @@ services:
app: app:
image: 'jc21/nginx-proxy-manager:latest' image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped restart: unless-stopped
ports: ports:
# These ports are in format <host-port>:<container-port> # These ports are in format <host-port>:<container-port>
- '80:80' # Public HTTP Port - '80:80' # Public HTTP Port
@@ -22,9 +21,7 @@ services:
# Add any other Stream port you want to expose # Add any other Stream port you want to expose
# - '21:21' # FTP # - '21:21' # FTP
environment: #environment:
TZ: "Australia/Brisbane"
# Uncomment this if you want to change the location of # Uncomment this if you want to change the location of
# the SQLite DB file within the container # the SQLite DB file within the container
# DB_SQLITE_FILE: "/data/database.sqlite" # DB_SQLITE_FILE: "/data/database.sqlite"
@@ -68,7 +65,6 @@ services:
# Add any other Stream port you want to expose # Add any other Stream port you want to expose
# - '21:21' # FTP # - '21:21' # FTP
environment: environment:
TZ: "Australia/Brisbane"
# Mysql/Maria connection parameters: # Mysql/Maria connection parameters:
DB_MYSQL_HOST: "db" DB_MYSQL_HOST: "db"
DB_MYSQL_PORT: 3306 DB_MYSQL_PORT: 3306
@@ -119,7 +115,6 @@ services:
# Add any other Stream port you want to expose # Add any other Stream port you want to expose
# - '21:21' # FTP # - '21:21' # FTP
environment: environment:
TZ: "Australia/Brisbane"
# Postgres parameters: # Postgres parameters:
DB_POSTGRES_HOST: 'db' DB_POSTGRES_HOST: 'db'
DB_POSTGRES_PORT: '5432' DB_POSTGRES_PORT: '5432'
@@ -178,3 +173,21 @@ After the app is running for the first time, the following will happen:
3. A default admin user will be created 3. A default admin user will be created
This process can take a couple of minutes depending on your machine. This process can take a couple of minutes depending on your machine.
## Default Administrator 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. You can change defaults with:
```
environment:
INITIAL_ADMIN_EMAIL: my@example.com
INITIAL_ADMIN_PASSWORD: mypassword1
```

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",
@@ -64,8 +64,7 @@
"useUniqueElementIds": "off" "useUniqueElementIds": "off"
}, },
"suspicious": { "suspicious": {
"noExplicitAny": "off", "noExplicitAny": "off"
"noArrayIndexKey": "off"
}, },
"performance": { "performance": {
"noDelete": "off" "noDelete": "off"

View File

@@ -12,51 +12,47 @@
"prettier": "biome format --write ./src", "prettier": "biome format --write ./src",
"locale-extract": "formatjs extract 'src/**/*.tsx'", "locale-extract": "formatjs extract 'src/**/*.tsx'",
"locale-compile": "formatjs compile-folder src/locale/src src/locale/lang", "locale-compile": "formatjs compile-folder src/locale/src src/locale/lang",
"locale-sort": "./src/locale/scripts/locale-sort.sh",
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@tabler/core": "^1.4.0", "@tabler/core": "^1.4.0",
"@tabler/icons-react": "^3.35.0", "@tabler/icons-react": "^3.34.1",
"@tanstack/react-query": "^5.89.0", "@tanstack/react-query": "^5.85.6",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"@uiw/react-textarea-code-editor": "^3.1.1",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"country-flag-icons": "^1.5.20", "country-flag-icons": "^1.5.19",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"formik": "^2.4.6", "formik": "^2.4.6",
"generate-password-browser": "^1.1.0",
"humps": "^2.0.1", "humps": "^2.0.1",
"query-string": "^9.3.1", "query-string": "^9.2.2",
"react": "^19.1.1", "react": "^19.1.1",
"react-bootstrap": "^2.10.10", "react-bootstrap": "^2.10.10",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-intl": "^7.1.11", "react-intl": "^7.1.11",
"react-router-dom": "^7.9.1", "react-router-dom": "^7.8.2",
"react-select": "^5.10.2",
"react-toastify": "^11.0.5", "react-toastify": "^11.0.5",
"rooks": "^9.3.0" "rooks": "^9.2.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.2.4", "@biomejs/biome": "2.2.2",
"@formatjs/cli": "^6.7.2", "@formatjs/cli": "^6.7.2",
"@tanstack/react-query-devtools": "^5.89.0", "@tanstack/react-query-devtools": "^5.85.6",
"@testing-library/dom": "^10.4.1", "@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.8.0", "@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@types/country-flag-icons": "^1.2.2", "@types/country-flag-icons": "^1.2.2",
"@types/humps": "^2.0.6", "@types/humps": "^2.0.6",
"@types/react": "^19.1.13", "@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9", "@types/react-dom": "^19.1.9",
"@types/react-table": "^7.7.20", "@types/react-table": "^7.7.20",
"@vitejs/plugin-react": "^5.0.3", "@vitejs/plugin-react": "^5.0.2",
"happy-dom": "^18.0.1", "happy-dom": "^18.0.1",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",
"sass": "^1.93.0", "sass": "^1.91.0",
"tmp": "^0.2.5", "tmp": "^0.2.5",
"typescript": "5.9.2", "typescript": "5.9.2",
"vite": "^7.1.6", "vite": "^7.1.4",
"vite-plugin-checker": "^0.10.3", "vite-plugin-checker": "^0.10.3",
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4" "vitest": "^3.2.4"

View File

@@ -1,14 +1,3 @@
:root {
color-scheme: light dark;
}
.light {
color-scheme: light;
}
.dark {
color-scheme: dark;
}
.modal-backdrop { .modal-backdrop {
--tblr-backdrop-opacity: 0.8 !important; --tblr-backdrop-opacity: 0.8 !important;
} }
@@ -23,54 +12,3 @@
.ml-1 { .ml-1 {
margin-left: 0.25rem; margin-left: 0.25rem;
} }
.react-select-container {
.react-select__control {
color: var(--tblr-body-color);
background-color: var(--tblr-bg-forms);
border: var(--tblr-border-width) solid var(--tblr-border-color);
.react-select__input {
color: var(--tblr-body-color) !important;
}
.react-select__single-value {
color: var(--tblr-body-color);
}
.react-select__multi-value {
border: 1px solid var(--tblr-border-color);
background-color: var(--tblr-bg-surface-tertiary);
color: var(--tblr-secondary) !important;
.react-select__multi-value__label {
color: var(--tblr-secondary) !important;
}
}
}
.react-select__menu {
background-color: var(--tblr-bg-forms);
.react-select__option {
background: rgba(var(--tblr-primary-rgb), .04);
color: inherit !important;
&.react-select__option--is-focused {
background: rgba(var(--tblr-primary-rgb), .1);
}
&.react-select__option--is-focused.react-select__option--is-selected {
background: rgba(var(--tblr-primary-rgb), .2);
}
}
}
}
.textareaMono {
font-family: 'Courier New', Courier, monospace !important;
resize: vertical;
}
label.row {
cursor: pointer;
}

View File

@@ -13,9 +13,8 @@ import {
import { useAuthState } from "src/context"; import { useAuthState } from "src/context";
import { useHealth } from "src/hooks"; import { useHealth } from "src/hooks";
const Setup = lazy(() => import("src/pages/Setup"));
const Login = lazy(() => import("src/pages/Login"));
const Dashboard = lazy(() => import("src/pages/Dashboard")); const Dashboard = lazy(() => import("src/pages/Dashboard"));
const Login = lazy(() => import("src/pages/Login"));
const Settings = lazy(() => import("src/pages/Settings")); const Settings = lazy(() => import("src/pages/Settings"));
const Certificates = lazy(() => import("src/pages/Certificates")); const Certificates = lazy(() => import("src/pages/Certificates"));
const Access = lazy(() => import("src/pages/Access")); const Access = lazy(() => import("src/pages/Access"));
@@ -38,10 +37,6 @@ function Router() {
return <Unhealthy />; return <Unhealthy />;
} }
if (!health.data?.setup) {
return <Setup />;
}
if (!authenticated) { if (!authenticated) {
return ( return (
<Suspense fallback={<LoadingPage />}> <Suspense fallback={<LoadingPage />}>

View File

@@ -88,19 +88,15 @@ interface PostArgs {
url: string; url: string;
params?: queryString.StringifiableRecord; params?: queryString.StringifiableRecord;
data?: any; data?: any;
noAuth?: boolean;
} }
export async function post({ url, params, data, noAuth }: PostArgs, abortController?: AbortController) { export async function post({ url, params, data }: PostArgs, abortController?: AbortController) {
const apiUrl = buildUrl({ url, params }); const apiUrl = buildUrl({ url, params });
const method = "POST"; const method = "POST";
let headers: Record<string, string> = {}; let headers = {
if (!noAuth) {
headers = {
...buildAuthHeader(), ...buildAuthHeader(),
}; };
}
let body: string | FormData | undefined; let body: string | FormData | undefined;
// Check if the data is an instance of FormData // Check if the data is an instance of FormData
@@ -124,7 +120,7 @@ export async function post({ url, params, data, noAuth }: PostArgs, abortControl
interface PutArgs { interface PutArgs {
url: string; url: string;
params?: queryString.StringifiableRecord; params?: queryString.StringifiableRecord;
data?: Record<string, any>; data?: Record<string, unknown>;
} }
export async function put({ url, params, data }: PutArgs, abortController?: AbortController) { export async function put({ url, params, data }: PutArgs, abortController?: AbortController) {
const apiUrl = buildUrl({ url, params }); const apiUrl = buildUrl({ url, params });

View File

@@ -1,10 +1,13 @@
import * as api from "./base"; import * as api from "./base";
import type { AccessList } from "./models"; import type { AccessList } from "./models";
export async function createAccessList(item: AccessList): Promise<AccessList> { export async function createAccessList(item: AccessList, abortController?: AbortController): Promise<AccessList> {
return await api.post({ return await api.post(
{
url: "/nginx/access-lists", url: "/nginx/access-lists",
// todo: only use whitelist of fields for this data // todo: only use whitelist of fields for this data
data: item, data: item,
}); },
abortController,
);
} }

View File

@@ -1,10 +1,13 @@
import * as api from "./base"; import * as api from "./base";
import type { Certificate } from "./models"; import type { Certificate } from "./models";
export async function createCertificate(item: Certificate): Promise<Certificate> { export async function createCertificate(item: Certificate, abortController?: AbortController): Promise<Certificate> {
return await api.post({ return await api.post(
{
url: "/nginx/certificates", url: "/nginx/certificates",
// todo: only use whitelist of fields for this data // todo: only use whitelist of fields for this data
data: item, data: item,
}); },
abortController,
);
} }

View File

@@ -1,10 +1,13 @@
import * as api from "./base"; import * as api from "./base";
import type { DeadHost } from "./models"; import type { DeadHost } from "./models";
export async function createDeadHost(item: DeadHost): Promise<DeadHost> { export async function createDeadHost(item: DeadHost, abortController?: AbortController): Promise<DeadHost> {
return await api.post({ return await api.post(
{
url: "/nginx/dead-hosts", url: "/nginx/dead-hosts",
// todo: only use whitelist of fields for this data // todo: only use whitelist of fields for this data
data: item, data: item,
}); },
abortController,
);
} }

View File

@@ -1,10 +1,13 @@
import * as api from "./base"; import * as api from "./base";
import type { ProxyHost } from "./models"; import type { ProxyHost } from "./models";
export async function createProxyHost(item: ProxyHost): Promise<ProxyHost> { export async function createProxyHost(item: ProxyHost, abortController?: AbortController): Promise<ProxyHost> {
return await api.post({ return await api.post(
{
url: "/nginx/proxy-hosts", url: "/nginx/proxy-hosts",
// todo: only use whitelist of fields for this data // todo: only use whitelist of fields for this data
data: item, data: item,
}); },
abortController,
);
} }

View File

@@ -1,10 +1,16 @@
import * as api from "./base"; import * as api from "./base";
import type { RedirectionHost } from "./models"; import type { RedirectionHost } from "./models";
export async function createRedirectionHost(item: RedirectionHost): Promise<RedirectionHost> { export async function createRedirectionHost(
return await api.post({ item: RedirectionHost,
abortController?: AbortController,
): Promise<RedirectionHost> {
return await api.post(
{
url: "/nginx/redirection-hosts", url: "/nginx/redirection-hosts",
// todo: only use whitelist of fields for this data // todo: only use whitelist of fields for this data
data: item, data: item,
}); },
abortController,
);
} }

View File

@@ -1,10 +1,13 @@
import * as api from "./base"; import * as api from "./base";
import type { Stream } from "./models"; import type { Stream } from "./models";
export async function createStream(item: Stream): Promise<Stream> { export async function createStream(item: Stream, abortController?: AbortController): Promise<Stream> {
return await api.post({ return await api.post(
{
url: "/nginx/streams", url: "/nginx/streams",
// todo: only use whitelist of fields for this data // todo: only use whitelist of fields for this data
data: item, data: item,
}); },
abortController,
);
} }

View File

@@ -1,25 +1,13 @@
import * as api from "./base"; import * as api from "./base";
import type { User } from "./models"; import type { User } from "./models";
export interface AuthOptions { export async function createUser(item: User, abortController?: AbortController): Promise<User> {
type: string; return await api.post(
secret: string; {
}
export interface NewUser {
name: string;
nickname: string;
email: string;
isDisabled?: boolean;
auth?: AuthOptions;
roles?: string[];
}
export async function createUser(item: NewUser, noAuth?: boolean): Promise<User> {
return await api.post({
url: "/users", url: "/users",
// todo: only use whitelist of fields for this data // todo: only use whitelist of fields for this data
data: item, data: item,
noAuth, },
}); abortController,
);
} }

View File

@@ -1,7 +1,10 @@
import * as api from "./base"; import * as api from "./base";
export async function deleteAccessList(id: number): Promise<boolean> { export async function deleteAccessList(id: number, abortController?: AbortController): Promise<boolean> {
return await api.del({ return await api.del(
{
url: `/nginx/access-lists/${id}`, url: `/nginx/access-lists/${id}`,
}); },
abortController,
);
} }

View File

@@ -1,7 +1,10 @@
import * as api from "./base"; import * as api from "./base";
export async function deleteCertificate(id: number): Promise<boolean> { export async function deleteCertificate(id: number, abortController?: AbortController): Promise<boolean> {
return await api.del({ return await api.del(
{
url: `/nginx/certificates/${id}`, url: `/nginx/certificates/${id}`,
}); },
abortController,
);
} }

View File

@@ -1,7 +1,10 @@
import * as api from "./base"; import * as api from "./base";
export async function deleteDeadHost(id: number): Promise<boolean> { export async function deleteDeadHost(id: number, abortController?: AbortController): Promise<boolean> {
return await api.del({ return await api.del(
{
url: `/nginx/dead-hosts/${id}`, url: `/nginx/dead-hosts/${id}`,
}); },
abortController,
);
} }

View File

@@ -1,7 +1,10 @@
import * as api from "./base"; import * as api from "./base";
export async function deleteProxyHost(id: number): Promise<boolean> { export async function deleteProxyHost(id: number, abortController?: AbortController): Promise<boolean> {
return await api.del({ return await api.del(
{
url: `/nginx/proxy-hosts/${id}`, url: `/nginx/proxy-hosts/${id}`,
}); },
abortController,
);
} }

View File

@@ -1,7 +1,10 @@
import * as api from "./base"; import * as api from "./base";
export async function deleteRedirectionHost(id: number): Promise<boolean> { export async function deleteRedirectionHost(id: number, abortController?: AbortController): Promise<boolean> {
return await api.del({ return await api.del(
{
url: `/nginx/redirection-hosts/${id}`, url: `/nginx/redirection-hosts/${id}`,
}); },
abortController,
);
} }

View File

@@ -1,7 +1,10 @@
import * as api from "./base"; import * as api from "./base";
export async function deleteStream(id: number): Promise<boolean> { export async function deleteStream(id: number, abortController?: AbortController): Promise<boolean> {
return await api.del({ return await api.del(
{
url: `/nginx/streams/${id}`, url: `/nginx/streams/${id}`,
}); },
abortController,
);
} }

View File

@@ -1,7 +1,10 @@
import * as api from "./base"; import * as api from "./base";
export async function deleteUser(id: number): Promise<boolean> { export async function deleteUser(id: number, abortController?: AbortController): Promise<boolean> {
return await api.del({ return await api.del(
{
url: `/users/${id}`, url: `/users/${id}`,
}); },
abortController,
);
} }

View File

@@ -1,8 +1,11 @@
import * as api from "./base"; import * as api from "./base";
import type { Binary } from "./responseTypes"; import type { Binary } from "./responseTypes";
export async function downloadCertificate(id: number): Promise<Binary> { export async function downloadCertificate(id: number, abortController?: AbortController): Promise<Binary> {
return await api.get({ return await api.get(
{
url: `/nginx/certificates/${id}/download`, url: `/nginx/certificates/${id}/download`,
}); },
abortController,
);
} }

View File

@@ -1,6 +0,0 @@
export type AccessListExpansion = "owner" | "items" | "clients";
export type AuditLogExpansion = "user";
export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts";
export type HostExpansion = "owner" | "certificate";
export type ProxyHostExpansion = "owner" | "access_list" | "certificate";
export type UserExpansion = "permissions";

View File

@@ -1,13 +1,11 @@
import * as api from "./base"; import * as api from "./base";
import type { AccessListExpansion } from "./expansions";
import type { AccessList } from "./models"; import type { AccessList } from "./models";
export async function getAccessList(id: number, expand?: AccessListExpansion[], params = {}): Promise<AccessList> { export async function getAccessList(id: number, abortController?: AbortController): Promise<AccessList> {
return await api.get({ return await api.get(
{
url: `/nginx/access-lists/${id}`, url: `/nginx/access-lists/${id}`,
params: {
expand: expand?.join(","),
...params,
}, },
}); abortController,
);
} }

View File

@@ -1,7 +1,8 @@
import * as api from "./base"; import * as api from "./base";
import type { AccessListExpansion } from "./expansions";
import type { AccessList } from "./models"; import type { AccessList } from "./models";
export type AccessListExpansion = "owner" | "items" | "clients";
export async function getAccessLists(expand?: AccessListExpansion[], params = {}): Promise<AccessList[]> { export async function getAccessLists(expand?: AccessListExpansion[], params = {}): Promise<AccessList[]> {
return await api.get({ return await api.get({
url: "/nginx/access-lists", url: "/nginx/access-lists",

View File

@@ -1,10 +1,9 @@
import * as api from "./base"; import * as api from "./base";
import type { AuditLogExpansion } from "./expansions";
import type { AuditLog } from "./models"; import type { AuditLog } from "./models";
export async function getAuditLog(id: number, expand?: AuditLogExpansion[], params = {}): Promise<AuditLog> { export async function getAuditLog(expand?: string[], params = {}): Promise<AuditLog[]> {
return await api.get({ return await api.get({
url: `/audit-log/${id}`, url: "/audit-log",
params: { params: {
expand: expand?.join(","), expand: expand?.join(","),
...params, ...params,

View File

@@ -1,13 +0,0 @@
import * as api from "./base";
import type { AuditLogExpansion } from "./expansions";
import type { AuditLog } from "./models";
export async function getAuditLogs(expand?: AuditLogExpansion[], params = {}): Promise<AuditLog[]> {
return await api.get({
url: "/audit-log",
params: {
expand: expand?.join(","),
...params,
},
});
}

View File

@@ -1,13 +1,11 @@
import * as api from "./base"; import * as api from "./base";
import type { CertificateExpansion } from "./expansions";
import type { Certificate } from "./models"; import type { Certificate } from "./models";
export async function getCertificate(id: number, expand?: CertificateExpansion[], params = {}): Promise<Certificate> { export async function getCertificate(id: number, abortController?: AbortController): Promise<Certificate> {
return await api.get({ return await api.get(
{
url: `/nginx/certificates/${id}`, url: `/nginx/certificates/${id}`,
params: {
expand: expand?.join(","),
...params,
}, },
}); abortController,
);
} }

View File

@@ -1,9 +0,0 @@
import * as api from "./base";
import type { DNSProvider } from "./models";
export async function getCertificateDNSProviders(params = {}): Promise<DNSProvider[]> {
return await api.get({
url: "/nginx/certificates/dns-providers",
params,
});
}

View File

@@ -1,8 +1,7 @@
import * as api from "./base"; import * as api from "./base";
import type { CertificateExpansion } from "./expansions";
import type { Certificate } from "./models"; import type { Certificate } from "./models";
export async function getCertificates(expand?: CertificateExpansion[], params = {}): Promise<Certificate[]> { export async function getCertificates(expand?: string[], params = {}): Promise<Certificate[]> {
return await api.get({ return await api.get({
url: "/nginx/certificates", url: "/nginx/certificates",
params: { params: {

View File

@@ -1,13 +1,11 @@
import * as api from "./base"; import * as api from "./base";
import type { HostExpansion } from "./expansions";
import type { DeadHost } from "./models"; import type { DeadHost } from "./models";
export async function getDeadHost(id: number, expand?: HostExpansion[], params = {}): Promise<DeadHost> { export async function getDeadHost(id: number, abortController?: AbortController): Promise<DeadHost> {
return await api.get({ return await api.get(
{
url: `/nginx/dead-hosts/${id}`, url: `/nginx/dead-hosts/${id}`,
params: {
expand: expand?.join(","),
...params,
}, },
}); abortController,
);
} }

View File

@@ -1,8 +1,9 @@
import * as api from "./base"; import * as api from "./base";
import type { HostExpansion } from "./expansions";
import type { DeadHost } from "./models"; import type { DeadHost } from "./models";
export async function getDeadHosts(expand?: HostExpansion[], params = {}): Promise<DeadHost[]> { export type DeadHostExpansion = "owner" | "certificate";
export async function getDeadHosts(expand?: DeadHostExpansion[], params = {}): Promise<DeadHost[]> {
return await api.get({ return await api.get({
url: "/nginx/dead-hosts", url: "/nginx/dead-hosts",
params: { params: {

View File

@@ -1,8 +1,11 @@
import * as api from "./base"; import * as api from "./base";
import type { HealthResponse } from "./responseTypes"; import type { HealthResponse } from "./responseTypes";
export async function getHealth(): Promise<HealthResponse> { export async function getHealth(abortController?: AbortController): Promise<HealthResponse> {
return await api.get({ return await api.get(
{
url: "/", url: "/",
}); },
abortController,
);
} }

View File

@@ -1,7 +1,10 @@
import * as api from "./base"; import * as api from "./base";
export async function getHostsReport(): Promise<Record<string, number>> { export async function getHostsReport(abortController?: AbortController): Promise<Record<string, number>> {
return await api.get({ return await api.get(
{
url: "/reports/hosts", url: "/reports/hosts",
}); },
abortController,
);
} }

View File

@@ -1,13 +1,11 @@
import * as api from "./base"; import * as api from "./base";
import type { ProxyHostExpansion } from "./expansions";
import type { ProxyHost } from "./models"; import type { ProxyHost } from "./models";
export async function getProxyHost(id: number, expand?: ProxyHostExpansion[], params = {}): Promise<ProxyHost> { export async function getProxyHost(id: number, abortController?: AbortController): Promise<ProxyHost> {
return await api.get({ return await api.get(
{
url: `/nginx/proxy-hosts/${id}`, url: `/nginx/proxy-hosts/${id}`,
params: {
expand: expand?.join(","),
...params,
}, },
}); abortController,
);
} }

View File

@@ -1,7 +1,8 @@
import * as api from "./base"; import * as api from "./base";
import type { ProxyHostExpansion } from "./expansions";
import type { ProxyHost } from "./models"; import type { ProxyHost } from "./models";
export type ProxyHostExpansion = "owner" | "access_list" | "certificate";
export async function getProxyHosts(expand?: ProxyHostExpansion[], params = {}): Promise<ProxyHost[]> { export async function getProxyHosts(expand?: ProxyHostExpansion[], params = {}): Promise<ProxyHost[]> {
return await api.get({ return await api.get({
url: "/nginx/proxy-hosts", url: "/nginx/proxy-hosts",

View File

@@ -1,13 +1,11 @@
import * as api from "./base"; import * as api from "./base";
import type { HostExpansion } from "./expansions"; import type { ProxyHost } from "./models";
import type { RedirectionHost } from "./models";
export async function getRedirectionHost(id: number, expand?: HostExpansion[], params = {}): Promise<RedirectionHost> { export async function getRedirectionHost(id: number, abortController?: AbortController): Promise<ProxyHost> {
return await api.get({ return await api.get(
{
url: `/nginx/redirection-hosts/${id}`, url: `/nginx/redirection-hosts/${id}`,
params: {
expand: expand?.join(","),
...params,
}, },
}); abortController,
);
} }

View File

@@ -1,8 +1,11 @@
import * as api from "./base"; import * as api from "./base";
import type { HostExpansion } from "./expansions";
import type { RedirectionHost } from "./models"; import type { RedirectionHost } from "./models";
export async function getRedirectionHosts(expand?: HostExpansion[], params = {}): Promise<RedirectionHost[]> { export type RedirectionHostExpansion = "owner" | "certificate";
export async function getRedirectionHosts(
expand?: RedirectionHostExpansion[],
params = {},
): Promise<RedirectionHost[]> {
return await api.get({ return await api.get({
url: "/nginx/redirection-hosts", url: "/nginx/redirection-hosts",
params: { params: {

View File

@@ -1,12 +1,11 @@
import * as api from "./base"; import * as api from "./base";
import type { Setting } from "./models"; import type { Setting } from "./models";
export async function getSetting(id: string, expand?: string[], params = {}): Promise<Setting> { export async function getSetting(id: string, abortController?: AbortController): Promise<Setting> {
return await api.get({ return await api.get(
{
url: `/settings/${id}`, url: `/settings/${id}`,
params: {
expand: expand?.join(","),
...params,
}, },
}); abortController,
);
} }

View File

@@ -1,13 +1,11 @@
import * as api from "./base"; import * as api from "./base";
import type { HostExpansion } from "./expansions";
import type { Stream } from "./models"; import type { Stream } from "./models";
export async function getStream(id: number, expand?: HostExpansion[], params = {}): Promise<Stream> { export async function getStream(id: number, abortController?: AbortController): Promise<Stream> {
return await api.get({ return await api.get(
{
url: `/nginx/streams/${id}`, url: `/nginx/streams/${id}`,
params: {
expand: expand?.join(","),
...params,
}, },
}); abortController,
);
} }

View File

@@ -1,8 +1,9 @@
import * as api from "./base"; import * as api from "./base";
import type { HostExpansion } from "./expansions";
import type { Stream } from "./models"; import type { Stream } from "./models";
export async function getStreams(expand?: HostExpansion[], params = {}): Promise<Stream[]> { export type StreamExpansion = "owner" | "certificate";
export async function getStreams(expand?: StreamExpansion[], params = {}): Promise<Stream[]> {
return await api.get({ return await api.get({
url: "/nginx/streams", url: "/nginx/streams",
params: { params: {

View File

@@ -1,9 +1,19 @@
import * as api from "./base"; import * as api from "./base";
import type { TokenResponse } from "./responseTypes"; import type { TokenResponse } from "./responseTypes";
export async function getToken(identity: string, secret: string): Promise<TokenResponse> { interface Options {
return await api.post({ payload: {
url: "/tokens", identity: string;
data: { identity, secret }, secret: string;
}); };
}
export async function getToken({ payload }: Options, abortController?: AbortController): Promise<TokenResponse> {
return await api.post(
{
url: "/tokens",
data: payload,
},
abortController,
);
} }

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