diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index 2bc83a41..b60d451f 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -21,88 +21,74 @@ const internalAccessList = { * @param {Object} data * @returns {Promise} */ - create: (access, data) => { - return access - .can("access_lists:create", data) - .then((/*access_data*/) => { - return accessListModel - .query() - .insertAndFetch({ - name: data.name, - satisfy_any: data.satisfy_any, - pass_auth: data.pass_auth, - owner_user_id: access.token.getUserId(1), - }) - .then(utils.omitRow(omissions())); + create: async (access, data) => { + await access.can("access_lists:create", data); + const row = await accessListModel + .query() + .insertAndFetch({ + name: data.name, + satisfy_any: data.satisfy_any, + pass_auth: data.pass_auth, + owner_user_id: access.token.getUserId(1), }) - .then((row) => { - data.id = row.id; + .then(utils.omitRow(omissions())); - const promises = []; + data.id = row.id; - // Now add the items - data.items.map((item) => { - promises.push( - accessListAuthModel.query().insert({ - access_list_id: row.id, - username: item.username, - password: item.password, - }), - ); - return true; - }); + const promises = []; + // Items + data.items.map((item) => { + promises.push( + accessListAuthModel.query().insert({ + access_list_id: row.id, + username: item.username, + password: item.password, + }), + ); + return true; + }); - // Now add the clients - if (typeof data.clients !== "undefined" && data.clients) { - data.clients.map((client) => { - promises.push( - accessListClientModel.query().insert({ - access_list_id: row.id, - address: client.address, - directive: client.directive, - }), - ); - return true; - }); - } + // Clients + data.clients?.map((client) => { + promises.push( + accessListClientModel.query().insert({ + access_list_id: row.id, + address: client.address, + directive: client.directive, + }), + ); + return true; + }); - return Promise.all(promises); - }) - .then(() => { - // re-fetch with expansions - return internalAccessList.get( - access, - { - id: data.id, - expand: ["owner", "items", "clients", "proxy_hosts.access_list.[clients,items]"], - }, - true /* <- skip masking */, - ); - }) - .then((row) => { - // Audit log - data.meta = _.assign({}, data.meta || {}, row.meta); + await Promise.all(promises); - return internalAccessList - .build(row) - .then(() => { - if (Number.parseInt(row.proxy_host_count, 10)) { - return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); - } - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: "created", - object_type: "access-list", - object_id: row.id, - meta: internalAccessList.maskItems(data), - }); - }) - .then(() => { - return internalAccessList.maskItems(row); - }); - }); + // re-fetch with expansions + const freshRow = await internalAccessList.get( + access, + { + id: data.id, + expand: ["owner", "items", "clients", "proxy_hosts.access_list.[clients,items]"], + }, + true // skip masking + ); + + // Audit log + data.meta = _.assign({}, data.meta || {}, freshRow.meta); + await internalAccessList.build(freshRow); + + if (Number.parseInt(freshRow.proxy_host_count, 10)) { + await internalNginx.bulkGenerateConfigs("proxy_host", freshRow.proxy_hosts); + } + + // Add to audit log + await internalAuditLog.add(access, { + action: "created", + object_type: "access-list", + object_id: freshRow.id, + meta: internalAccessList.maskItems(data), + }); + + return internalAccessList.maskItems(freshRow); }, /** @@ -113,127 +99,107 @@ const internalAccessList = { * @param {String} [data.items] * @return {Promise} */ - update: (access, data) => { - return access - .can("access_lists:update", data.id) - .then((/*access_data*/) => { - return internalAccessList.get(access, { id: data.id }); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new errs.InternalValidationError( - `Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + update: async (access, data) => { + await access.can("access_lists:update", data.id); + const row = await internalAccessList.get(access, { id: data.id }); + if (row.id !== data.id) { + // Sanity check that something crazy hasn't happened + throw new errs.InternalValidationError( + `Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); + } + + // patch name if specified + if (typeof data.name !== "undefined" && data.name) { + await accessListModel.query().where({ id: data.id }).patch({ + name: data.name, + satisfy_any: data.satisfy_any, + pass_auth: data.pass_auth, + }); + } + + // Check for items and add/update/remove them + if (typeof data.items !== "undefined" && data.items) { + const promises = []; + const itemsToKeep = []; + + data.items.map((item) => { + if (item.password) { + promises.push( + accessListAuthModel.query().insert({ + access_list_id: data.id, + username: item.username, + password: item.password, + }), + ); + } else { + // This was supplied with an empty password, which means keep it but don't change the password + itemsToKeep.push(item.username); + } + return true; + }); + + const query = accessListAuthModel.query().delete().where("access_list_id", data.id); + + if (itemsToKeep.length) { + query.andWhere("username", "NOT IN", itemsToKeep); + } + + await query; + // Add new items + if (promises.length) { + await Promise.all(promises); + } + } + + // Check for clients and add/update/remove them + if (typeof data.clients !== "undefined" && data.clients) { + const clientPromises = []; + data.clients.map((client) => { + if (client.address) { + clientPromises.push( + accessListClientModel.query().insert({ + access_list_id: data.id, + address: client.address, + directive: client.directive, + }), ); } - }) - .then(() => { - // patch name if specified - if (typeof data.name !== "undefined" && data.name) { - return accessListModel.query().where({ id: data.id }).patch({ - name: data.name, - satisfy_any: data.satisfy_any, - pass_auth: data.pass_auth, - }); - } - }) - .then(() => { - // Check for items and add/update/remove them - if (typeof data.items !== "undefined" && data.items) { - const promises = []; - const items_to_keep = []; - - data.items.map((item) => { - if (item.password) { - promises.push( - accessListAuthModel.query().insert({ - access_list_id: data.id, - username: item.username, - password: item.password, - }), - ); - } else { - // This was supplied with an empty password, which means keep it but don't change the password - items_to_keep.push(item.username); - } - return true; - }); - - const query = accessListAuthModel.query().delete().where("access_list_id", data.id); - - if (items_to_keep.length) { - query.andWhere("username", "NOT IN", items_to_keep); - } - - return query.then(() => { - // Add new items - if (promises.length) { - return Promise.all(promises); - } - }); - } - }) - .then(() => { - // Check for clients and add/update/remove them - if (typeof data.clients !== "undefined" && data.clients) { - const promises = []; - - data.clients.map((client) => { - if (client.address) { - promises.push( - accessListClientModel.query().insert({ - access_list_id: data.id, - address: client.address, - directive: client.directive, - }), - ); - } - return true; - }); - - const query = accessListClientModel.query().delete().where("access_list_id", data.id); - - return query.then(() => { - // Add new items - if (promises.length) { - return Promise.all(promises); - } - }); - } - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: "updated", - object_type: "access-list", - object_id: data.id, - meta: internalAccessList.maskItems(data), - }); - }) - .then(() => { - // re-fetch with expansions - return internalAccessList.get( - access, - { - id: data.id, - expand: ["owner", "items", "clients", "proxy_hosts.[certificate,access_list.[clients,items]]"], - }, - true /* <- skip masking */, - ); - }) - .then((row) => { - return internalAccessList - .build(row) - .then(() => { - if (Number.parseInt(row.proxy_host_count, 10)) { - return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); - } - }) - .then(internalNginx.reload) - .then(() => { - return internalAccessList.maskItems(row); - }); + return true; }); + + const query = accessListClientModel.query().delete().where("access_list_id", data.id); + await query; + // Add new clitens + if (clientPromises.length) { + await Promise.all(clientPromises); + } + } + + // Add to audit log + await internalAuditLog.add(access, { + action: "updated", + object_type: "access-list", + object_id: data.id, + meta: internalAccessList.maskItems(data), + }); + + // re-fetch with expansions + const freshRow = await internalAccessList.get( + access, + { + id: data.id, + expand: ["owner", "items", "clients", "proxy_hosts.[certificate,access_list.[clients,items]]"], + }, + true // skip masking + ); + + await internalAccessList.build(freshRow) + if (Number.parseInt(row.proxy_host_count, 10)) { + await internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); + } + await internalNginx.reload(); + return internalAccessList.maskItems(row); }, /** @@ -242,55 +208,50 @@ const internalAccessList = { * @param {Integer} data.id * @param {Array} [data.expand] * @param {Array} [data.omit] - * @param {Boolean} [skip_masking] + * @param {Boolean} [skipMasking] * @return {Promise} */ - get: (access, data, skip_masking) => { + get: async (access, data, skipMasking) => { 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 - .query() - .select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) - .leftJoin("proxy_host", function () { - this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( - "proxy_host.is_deleted", - "=", - 0, - ); - }) - .where("access_list.is_deleted", 0) - .andWhere("access_list.id", thisData.id) - .groupBy("access_list.id") - .allowGraph("[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]") - .first(); - - if (accessData.permission_visibility !== "all") { - query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); - } - - if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { - query.withGraphFetched(`[${thisData.expand.join(", ")}]`); - } - - return query.then(utils.omitRow(omissions())); + const query = accessListModel + .query() + .select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) + .leftJoin("proxy_host", function () { + this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( + "proxy_host.is_deleted", + "=", + 0, + ); }) - .then((row) => { - let thisRow = row; - if (!row || !row.id) { - throw new errs.ItemNotFoundError(thisData.id); - } - if (!skip_masking && typeof thisRow.items !== "undefined" && thisRow.items) { - thisRow = internalAccessList.maskItems(thisRow); - } - // Custom omissions - if (typeof data.omit !== "undefined" && data.omit !== null) { - thisRow = _.omit(thisRow, data.omit); - } - return thisRow; - }); + .where("access_list.is_deleted", 0) + .andWhere("access_list.id", thisData.id) + .groupBy("access_list.id") + .allowGraph("[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]") + .first(); + + if (accessData.permission_visibility !== "all") { + query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); + } + + if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { + query.withGraphFetched(`[${thisData.expand.join(", ")}]`); + } + + let row = await query.then(utils.omitRow(omissions())); + + if (!row || !row.id) { + throw new errs.ItemNotFoundError(thisData.id); + } + if (!skipMasking && typeof row.items !== "undefined" && row.items) { + row = internalAccessList.maskItems(row); + } + // Custom omissions + if (typeof data.omit !== "undefined" && data.omit !== null) { + row = _.omit(row, data.omit); + } + return row; }, /** @@ -300,75 +261,64 @@ const internalAccessList = { * @param {String} [data.reason] * @returns {Promise} */ - delete: (access, data) => { - return access - .can("access_lists:delete", data.id) - .then(() => { - return internalAccessList.get(access, { id: data.id, expand: ["proxy_hosts", "items", "clients"] }); - }) - .then((row) => { - if (!row || !row.id) { - throw new errs.ItemNotFoundError(data.id); - } + delete: async (access, data) => { + await access.can("access_lists:delete", data.id); + const row = await internalAccessList.get(access, { + id: data.id, + expand: ["proxy_hosts", "items", "clients"], + }); - // 1. update row to be deleted - // 2. update any proxy hosts that were using it (ignoring permissions) - // 3. reconfigure those hosts - // 4. audit log + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } - // 1. update row to be deleted - return accessListModel - .query() - .where("id", row.id) - .patch({ - is_deleted: 1, - }) - .then(() => { - // 2. update any proxy hosts that were using it (ignoring permissions) - if (row.proxy_hosts) { - return proxyHostModel - .query() - .where("access_list_id", "=", row.id) - .patch({ access_list_id: 0 }) - .then(() => { - // 3. reconfigure those hosts, then reload nginx + // 1. update row to be deleted + // 2. update any proxy hosts that were using it (ignoring permissions) + // 3. reconfigure those hosts + // 4. audit log - // set the access_list_id to zero for these items - row.proxy_hosts.map((_val, idx) => { - row.proxy_hosts[idx].access_list_id = 0; - return true; - }); + // 1. update row to be deleted + await accessListModel + .query() + .where("id", row.id) + .patch({ + is_deleted: 1, + }); - return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); - }) - .then(() => { - return internalNginx.reload(); - }); - } - }) - .then(() => { - // delete the htpasswd file - const htpasswd_file = internalAccessList.getFilename(row); + // 2. update any proxy hosts that were using it (ignoring permissions) + if (row.proxy_hosts) { + await proxyHostModel + .query() + .where("access_list_id", "=", row.id) + .patch({ access_list_id: 0 }); - try { - fs.unlinkSync(htpasswd_file); - } catch (_err) { - // do nothing - } - }) - .then(() => { - // 4. audit log - return internalAuditLog.add(access, { - action: "deleted", - object_type: "access-list", - object_id: row.id, - meta: _.omit(internalAccessList.maskItems(row), ["is_deleted", "proxy_hosts"]), - }); - }); - }) - .then(() => { + // 3. reconfigure those hosts, then reload nginx + // set the access_list_id to zero for these items + row.proxy_hosts.map((_val, idx) => { + row.proxy_hosts[idx].access_list_id = 0; return true; }); + + await internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); + } + + await internalNginx.reload(); + + // delete the htpasswd file + try { + fs.unlinkSync(internalAccessList.getFilename(row)); + } catch (_err) { + // do nothing + } + + // 4. audit log + await internalAuditLog.add(access, { + action: "deleted", + object_type: "access-list", + object_id: row.id, + meta: _.omit(internalAccessList.maskItems(row), ["is_deleted", "proxy_hosts"]), + }); + return true; }, /** @@ -376,76 +326,73 @@ const internalAccessList = { * * @param {Access} access * @param {Array} [expand] - * @param {String} [search_query] + * @param {String} [searchQuery] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access - .can("access_lists:list") - .then((access_data) => { - const query = accessListModel - .query() - .select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) - .leftJoin("proxy_host", function () { - this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( - "proxy_host.is_deleted", - "=", - 0, - ); - }) - .where("access_list.is_deleted", 0) - .groupBy("access_list.id") - .allowGraph("[owner,items,clients]") - .orderBy("access_list.name", "ASC"); + getAll: async (access, expand, searchQuery) => { + const accessData = await access.can("access_lists:list"); - if (access_data.permission_visibility !== "all") { - query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); - } - - // Query is used for searching - if (typeof search_query === "string") { - query.where(function () { - this.where("name", "like", `%${search_query}%`); - }); - } - - if (typeof expand !== "undefined" && expand !== null) { - query.withGraphFetched(`[${expand.join(", ")}]`); - } - - return query.then(utils.omitRows(omissions())); + const query = accessListModel + .query() + .select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) + .leftJoin("proxy_host", function () { + this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( + "proxy_host.is_deleted", + "=", + 0, + ); }) - .then((rows) => { - if (rows) { - rows.map((row, idx) => { - if (typeof row.items !== "undefined" && row.items) { - rows[idx] = internalAccessList.maskItems(row); - } - return true; - }); - } + .where("access_list.is_deleted", 0) + .groupBy("access_list.id") + .allowGraph("[owner,items,clients]") + .orderBy("access_list.name", "ASC"); - return rows; + if (accessData.permission_visibility !== "all") { + query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); + } + + // Query is used for searching + if (typeof searchQuery === "string") { + query.where(function () { + this.where("name", "like", `%${searchQuery}%`); }); + } + + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + const rows = await query.then(utils.omitRows(omissions())); + if (rows) { + rows.map((row, idx) => { + if (typeof row.items !== "undefined" && row.items) { + rows[idx] = internalAccessList.maskItems(row); + } + return true; + }); + } + return rows; }, /** - * Report use + * Count is used in reports * - * @param {Integer} user_id + * @param {Integer} userId * @param {String} visibility * @returns {Promise} */ - getCount: (user_id, visibility) => { - const query = accessListModel.query().count("id as count").where("is_deleted", 0); + getCount: async (userId, visibility) => { + const query = accessListModel + .query() + .count("id as count") + .where("is_deleted", 0); if (visibility !== "all") { - query.andWhere("owner_user_id", user_id); + query.andWhere("owner_user_id", userId); } - return query.first().then((row) => { - return Number.parseInt(row.count, 10); - }); + const row = await query.first(); + return Number.parseInt(row.count, 10); }, /** @@ -455,20 +402,19 @@ const internalAccessList = { maskItems: (list) => { if (list && typeof list.items !== "undefined") { list.items.map((val, idx) => { - let repeat_for = 8; - let first_char = "*"; + let repeatFor = 8; + let firstChar = "*"; if (typeof val.password !== "undefined" && val.password) { - repeat_for = val.password.length - 1; - first_char = val.password.charAt(0); + repeatFor = val.password.length - 1; + firstChar = val.password.charAt(0); } - list.items[idx].hint = first_char + "*".repeat(repeat_for); + list.items[idx].hint = firstChar + "*".repeat(repeatFor); list.items[idx].password = ""; return true; }); } - return list; }, @@ -488,66 +434,55 @@ const internalAccessList = { * @param {Array} list.items * @returns {Promise} */ - build: (list) => { + build: async (list) => { logger.info(`Building Access file #${list.id} for: ${list.name}`); - return new Promise((resolve, reject) => { - const htpasswd_file = internalAccessList.getFilename(list); + const htpasswdFile = internalAccessList.getFilename(list); - // 1. remove any existing access file - try { - fs.unlinkSync(htpasswd_file); - } catch (_err) { - // do nothing - } + // 1. remove any existing access file + try { + fs.unlinkSync(htpasswdFile); + } catch (_err) { + // do nothing + } - // 2. create empty access file - try { - fs.writeFileSync(htpasswd_file, "", { encoding: "utf8" }); - resolve(htpasswd_file); - } catch (err) { - reject(err); - } - }).then((htpasswd_file) => { - // 3. generate password for each user - if (list.items.length) { - return new Promise((resolve, reject) => { - batchflow(list.items) - .sequential() - .each((_i, item, next) => { - if (item.password?.length) { - logger.info(`Adding: ${item.username}`); + // 2. create empty access file + fs.writeFileSync(htpasswdFile, '', {encoding: 'utf8'}); - utils - .execFile("openssl", ["passwd", "-apr1", item.password]) - .then((res) => { - try { - fs.appendFileSync(htpasswd_file, `${item.username}:${res}\n`, { - encoding: "utf8", - }); - } catch (err) { - reject(err); - } - next(); - }) - .catch((err) => { - logger.error(err); - next(err); - }); - } - }) - .error((err) => { - logger.error(err); - reject(err); - }) - .end((results) => { - logger.success(`Built Access file #${list.id} for: ${list.name}`); - resolve(results); - }); - }); - } - }); - }, -}; + // 3. generate password for each user + if (list.items.length) { + await new Promise((resolve, reject) => { + batchflow(list.items).sequential() + .each((_i, item, next) => { + if (item.password?.length) { + logger.info(`Adding: ${item.username}`); + + utils.execFile('openssl', ['passwd', '-apr1', item.password]) + .then((res) => { + try { + fs.appendFileSync(htpasswdFile, `${item.username}:${res}\n`, {encoding: 'utf8'}); + } catch (err) { + reject(err); + } + next(); + }) + .catch((err) => { + logger.error(err); + next(err); + }); + } + }) + .error((err) => { + logger.error(err); + reject(err); + }) + .end((results) => { + logger.success(`Built Access file #${list.id} for: ${list.name}`); + resolve(results); + }); + }); + } + } +} export default internalAccessList; diff --git a/backend/internal/audit-log.js b/backend/internal/audit-log.js index 5a81976b..6d3ee9ac 100644 --- a/backend/internal/audit-log.js +++ b/backend/internal/audit-log.js @@ -9,31 +9,31 @@ const internalAuditLog = { * * @param {Access} access * @param {Array} [expand] - * @param {String} [search_query] + * @param {String} [searchQuery] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access.can("auditlog:list").then(() => { - const query = auditLogModel - .query() - .orderBy("created_on", "DESC") - .orderBy("id", "DESC") - .limit(100) - .allowGraph("[user]"); + getAll: async (access, expand, searchQuery) => { + await access.can("auditlog:list"); - // Query is used for searching - if (typeof search_query === "string" && search_query.length > 0) { - query.where(function () { - this.where(castJsonIfNeed("meta"), "like", `%${search_query}`); - }); - } + const query = auditLogModel + .query() + .orderBy("created_on", "DESC") + .orderBy("id", "DESC") + .limit(100) + .allowGraph("[user]"); - if (typeof expand !== "undefined" && expand !== null) { - query.withGraphFetched(`[${expand.join(", ")}]`); - } + // Query is used for searching + if (typeof searchQuery === "string" && searchQuery.length > 0) { + query.where(function () { + this.where(castJsonIfNeed("meta"), "like", `%${searchQuery}`); + }); + } - return query; - }); + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + return await query; }, /** @@ -50,27 +50,22 @@ const internalAuditLog = { * @param {Object} [data.meta] * @returns {Promise} */ - add: (access, data) => { - return new Promise((resolve, reject) => { - // Default the user id - if (typeof data.user_id === "undefined" || !data.user_id) { - data.user_id = access.token.getUserId(1); - } + add: async (access, data) => { + if (typeof data.user_id === "undefined" || !data.user_id) { + data.user_id = access.token.getUserId(1); + } - if (typeof data.action === "undefined" || !data.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 - resolve( - auditLogModel.query().insert({ - user_id: data.user_id, - action: data.action, - object_type: data.object_type || "", - object_id: data.object_id || 0, - meta: data.meta || {}, - }), - ); - } + if (typeof data.action === "undefined" || !data.action) { + throw new errs.InternalValidationError("Audit log entry must contain an Action"); + } + + // Make sure at least 1 of the IDs are set and action + return await auditLogModel.query().insert({ + user_id: data.user_id, + action: data.action, + object_type: data.object_type || "", + object_id: data.object_id || 0, + meta: data.meta || {}, }); }, }; diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index f5a93635..f776474d 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -110,147 +110,109 @@ const internalCertificate = { * @param {Object} data * @returns {Promise} */ - create: (access, data) => { - return access - .can("certificates:create", data) - .then(() => { - data.owner_user_id = access.token.getUserId(1); + create: async (access, data) => { + await access.can("certificates:create", data); + data.owner_user_id = access.token.getUserId(1); - if (data.provider === "letsencrypt") { - data.nice_name = data.domain_names.join(", "); - } + if (data.provider === "letsencrypt") { + data.nice_name = data.domain_names.join(", "); + } - return certificateModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); - }) - .then((certificate) => { - if (certificate.provider === "letsencrypt") { - // Request a new Cert from LE. Let the fun begin. + const certificate = await certificateModel + .query() + .insertAndFetch(data) + .then(utils.omitRow(omissions())); - // 1. Find out any hosts that are using any of the hostnames in this cert - // 2. Disable them in nginx temporarily - // 3. Generate the LE config + if (certificate.provider === "letsencrypt") { + // Request a new Cert from LE. Let the fun begin. + + // 1. Find out any hosts that are using any of the hostnames in this cert + // 2. Disable them in nginx temporarily + // 3. Generate the LE config + // 4. Request cert + // 5. Remove LE config + // 6. Re-instate previously disabled hosts + + // 1. Find out any hosts that are using any of the hostnames in this cert + const inUseResult = await internalHost.getHostsWithDomains(certificate.domain_names); + + // 2. Disable them in nginx temporarily + await internalCertificate.disableInUseHosts(inUseResult); + + // With DNS challenge no config is needed, so skip 3 and 5. + if (certificate.meta?.dns_challenge) { + try { + await internalNginx.reload(); // 4. Request cert - // 5. Remove LE config + await internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate); + await internalNginx.reload(); // 6. Re-instate previously disabled hosts - - // 1. Find out any hosts that are using any of the hostnames in this cert - return internalHost - .getHostsWithDomains(certificate.domain_names) - .then((in_use_result) => { - // 2. Disable them in nginx temporarily - return internalCertificate.disableInUseHosts(in_use_result).then(() => { - return in_use_result; - }); - }) - .then((in_use_result) => { - // With DNS challenge no config is needed, so skip 3 and 5. - if (certificate.meta.dns_challenge) { - return internalNginx - .reload() - .then(() => { - // 4. Request cert - return internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate); - }) - .then(internalNginx.reload) - .then(() => { - // 6. Re-instate previously disabled hosts - return internalCertificate.enableInUseHosts(in_use_result); - }) - .then(() => { - return certificate; - }) - .catch((err) => { - // In the event of failure, revert things and throw err back - return internalCertificate - .enableInUseHosts(in_use_result) - .then(internalNginx.reload) - .then(() => { - throw err; - }); - }); - } - // 3. Generate the LE config - return internalNginx - .generateLetsEncryptRequestConfig(certificate) - .then(internalNginx.reload) - .then(async () => await new Promise((r) => setTimeout(r, 5000))) - .then(() => { - // 4. Request cert - return internalCertificate.requestLetsEncryptSsl(certificate); - }) - .then(() => { - // 5. Remove LE config - return internalNginx.deleteLetsEncryptRequestConfig(certificate); - }) - .then(internalNginx.reload) - .then(() => { - // 6. Re-instate previously disabled hosts - return internalCertificate.enableInUseHosts(in_use_result); - }) - .then(() => { - return certificate; - }) - .catch((err) => { - // In the event of failure, revert things and throw err back - return internalNginx - .deleteLetsEncryptRequestConfig(certificate) - .then(() => { - return internalCertificate.enableInUseHosts(in_use_result); - }) - .then(internalNginx.reload) - .then(() => { - throw err; - }); - }); - }) - .then(() => { - // At this point, the letsencrypt cert should exist on disk. - // Lets get the expiry date from the file and update the row silently - return internalCertificate - .getCertificateInfoFromFile( - `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, - ) - .then((cert_info) => { - return certificateModel - .query() - .patchAndFetchById(certificate.id, { - expires_on: moment(cert_info.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), - }) - .then(utils.omitRow(omissions())) - .then((saved_row) => { - // Add cert data for audit log - saved_row.meta = _.assign({}, saved_row.meta, { - letsencrypt_certificate: cert_info, - }); - - return saved_row; - }); - }); - }) - .catch(async (error) => { - // Delete the certificate from the database if it was not created successfully - await certificateModel.query().deleteById(certificate.id); - - throw error; - }); + await internalCertificate.enableInUseHosts(inUseResult); + } catch (err) { + // In the event of failure, revert things and throw err back + await internalCertificate.enableInUseHosts(inUseResult); + await internalNginx.reload(); + throw err; } - return certificate; - }) - .then((certificate) => { - data.meta = _.assign({}, data.meta || {}, certificate.meta); + } else { + // 3. Generate the LE config + try { + await internalNginx.generateLetsEncryptRequestConfig(certificate); + await internalNginx.reload(); + setTimeout(() => {}, 5000) + // 4. Request cert + await internalCertificate.requestLetsEncryptSsl(certificate); + // 5. Remove LE config + await internalNginx.deleteLetsEncryptRequestConfig(certificate); + await internalNginx.reload(); + // 6. Re-instate previously disabled hosts + await internalCertificate.enableInUseHosts(inUseResult); + } catch (err) { + // In the event of failure, revert things and throw err back + await internalNginx.deleteLetsEncryptRequestConfig(certificate); + await internalCertificate.enableInUseHosts(inUseResult); + await internalNginx.reload(); + throw err; + } + } - // Add to audit log - return internalAuditLog - .add(access, { - action: "created", - object_type: "certificate", - object_id: certificate.id, - meta: data, + // At this point, the letsencrypt cert should exist on disk. + // Lets get the expiry date from the file and update the row silently + try { + const certInfo = await internalCertificate.getCertificateInfoFromFile( + `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, + ); + const savedRow = await certificateModel + .query() + .patchAndFetchById(certificate.id, { + expires_on: moment(certInfo.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), }) - .then(() => { - return certificate; - }); + .then(utils.omitRow(omissions())); + + // Add cert data for audit log + savedRow.meta = _.assign({}, savedRow.meta, { + letsencrypt_certificate: certInfo, + }); + return savedRow; + } catch (err) { + // Delete the certificate from the database if it was not created successfully + await certificateModel.query().deleteById(certificate.id); + throw err; + } + } + + data.meta = _.assign({}, data.meta || {}, certificate.meta); + + // Add to audit log + await internalAuditLog + .add(access, { + action: "created", + object_type: "certificate", + object_id: certificate.id, + meta: data, }); + + return certificate; }, /** @@ -261,46 +223,40 @@ const internalCertificate = { * @param {String} [data.name] * @return {Promise} */ - update: (access, data) => { - return access - .can("certificates:update", data.id) - .then((/*access_data*/) => { - return internalCertificate.get(access, { id: data.id }); - }) - .then((row) => { - if (row.id !== data.id) { - // Sanity check that something crazy hasn't happened - throw new error.InternalValidationError( - `Certificate could not be updated, IDs do not match: ${row.id} !== ${data.id}`, - ); - } + update: async (access, data) => { + await access.can("certificates:update", data.id); + const row = await internalCertificate.get(access, { id: data.id }); - return certificateModel - .query() - .patchAndFetchById(row.id, data) - .then(utils.omitRow(omissions())) - .then((saved_row) => { - saved_row.meta = internalCertificate.cleanMeta(saved_row.meta); - data.meta = internalCertificate.cleanMeta(data.meta); + if (row.id !== data.id) { + // Sanity check that something crazy hasn't happened + throw new error.InternalValidationError( + `Certificate could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); + } - // Add row.nice_name for custom certs - if (saved_row.provider === "other") { - data.nice_name = saved_row.nice_name; - } + const savedRow = await certificateModel + .query() + .patchAndFetchById(row.id, data) + .then(utils.omitRow(omissions())); - // Add to audit log - return internalAuditLog - .add(access, { - action: "updated", - object_type: "certificate", - object_id: row.id, - meta: _.omit(data, ["expires_on"]), // this prevents json circular reference because expires_on might be raw - }) - .then(() => { - return saved_row; - }); - }); + savedRow.meta = internalCertificate.cleanMeta(savedRow.meta); + data.meta = internalCertificate.cleanMeta(data.meta); + + // Add row.nice_name for custom certs + if (savedRow.provider === "other") { + data.nice_name = savedRow.nice_name; + } + + // Add to audit log + await internalAuditLog + .add(access, { + action: "updated", + object_type: "certificate", + object_id: row.id, + meta: _.omit(data, ["expires_on"]), // this prevents json circular reference because expires_on might be raw }); + + return savedRow; }, /** @@ -311,42 +267,35 @@ const internalCertificate = { * @param {Array} [data.omit] * @return {Promise} */ - get: (access, data) => { - const thisData = data || {}; + get: async (access, data) => { + const accessData = await access.can("certificates:get", data.id) + const query = certificateModel + .query() + .where("is_deleted", 0) + .andWhere("id", data.id) + .allowGraph("[owner]") + .allowGraph("[proxy_hosts]") + .allowGraph("[redirection_hosts]") + .allowGraph("[dead_hosts]") + .first(); - return access - .can("certificates:get", thisData.id) - .then((accessData) => { - const query = certificateModel - .query() - .where("is_deleted", 0) - .andWhere("id", thisData.id) - .allowGraph("[owner]") - .allowGraph("[proxy_hosts]") - .allowGraph("[redirection_hosts]") - .allowGraph("[dead_hosts]") - .first(); + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - if (accessData.permission_visibility !== "all") { - query.andWhere("owner_user_id", access.token.getUserId(1)); - } + if (typeof data.expand !== "undefined" && data.expand !== null) { + query.withGraphFetched(`[${data.expand.join(", ")}]`); + } - if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { - query.withGraphFetched(`[${thisData.expand.join(", ")}]`); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(thisData.id); - } - // Custom omissions - if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { - return _.omit(row, thisData.omit); - } - return row; - }); + const row = await query.then(utils.omitRow(omissions())); + if (!row || !row.id) { + throw new error.ItemNotFoundError(data.id); + } + // Custom omissions + if (typeof data.omit !== "undefined" && data.omit !== null) { + return _.omit(row, data.omit); + } + return row; }, /** @@ -355,43 +304,30 @@ const internalCertificate = { * @param {Number} data.id * @returns {Promise} */ - download: (access, data) => { - return new Promise((resolve, reject) => { - access - .can("certificates:get", data) - .then(() => { - return internalCertificate.get(access, data); - }) - .then((certificate) => { - if (certificate.provider === "letsencrypt") { - const zipDirectory = internalCertificate.getLiveCertPath(data.id); + download: async (access, data) => { + await access.can("certificates:get", data); + const certificate = await internalCertificate.get(access, data); + if (certificate.provider === "letsencrypt") { + const zipDirectory = internalCertificate.getLiveCertPath(data.id); + if (!fs.existsSync(zipDirectory)) { + throw new error.ItemNotFoundError(`Certificate ${certificate.nice_name} does not exists`); + } - if (!fs.existsSync(zipDirectory)) { - throw new error.ItemNotFoundError(`Certificate ${certificate.nice_name} does not exists`); - } + const certFiles = fs + .readdirSync(zipDirectory) + .filter((fn) => fn.endsWith(".pem")) + .map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); - const certFiles = fs - .readdirSync(zipDirectory) - .filter((fn) => fn.endsWith(".pem")) - .map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); - const downloadName = `npm-${data.id}-${Date.now()}.zip`; - const opName = `/tmp/${downloadName}`; - internalCertificate - .zipFiles(certFiles, opName) - .then(() => { - logger.debug("zip completed : ", opName); - const resp = { - fileName: opName, - }; - resolve(resp); - }) - .catch((err) => reject(err)); - } else { - throw new error.ValidationError("Only Let'sEncrypt certificates can be downloaded"); - } - }) - .catch((err) => reject(err)); - }); + const downloadName = `npm-${data.id}-${Date.now()}.zip`; + const opName = `/tmp/${downloadName}`; + + await internalCertificate.zipFiles(certFiles, opName); + logger.debug("zip completed : ", opName); + return { + fileName: opName, + }; + } + throw new error.ValidationError("Only Let'sEncrypt certificates can be downloaded"); }, /** @@ -399,7 +335,7 @@ const internalCertificate = { * @param {String} out * @returns {Promise} */ - zipFiles(source, out) { + zipFiles: async (source, out) => { const archive = archiver("zip", { zlib: { level: 9 } }); const stream = fs.createWriteStream(out); @@ -423,44 +359,36 @@ const internalCertificate = { * @param {String} [data.reason] * @returns {Promise} */ - delete: (access, data) => { - return access - .can("certificates:delete", data.id) - .then(() => { - return internalCertificate.get(access, { id: data.id }); - }) - .then((row) => { - if (!row || !row.id) { - throw new error.ItemNotFoundError(data.id); - } + delete: async (access, data) => { + await access.can("certificates:delete", data.id); + const row = await internalCertificate.get(access, { id: data.id }); - return certificateModel - .query() - .where("id", row.id) - .patch({ - is_deleted: 1, - }) - .then(() => { - // Add to audit log - row.meta = internalCertificate.cleanMeta(row.meta); + if (!row || !row.id) { + throw new error.ItemNotFoundError(data.id); + } - return internalAuditLog.add(access, { - action: "deleted", - object_type: "certificate", - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }) - .then(() => { - if (row.provider === "letsencrypt") { - // Revoke the cert - return internalCertificate.revokeLetsEncryptSsl(row); - } - }); - }) - .then(() => { - return true; + await certificateModel + .query() + .where("id", row.id) + .patch({ + is_deleted: 1, }); + + // Add to audit log + row.meta = internalCertificate.cleanMeta(row.meta); + + await internalAuditLog.add(access, { + action: "deleted", + object_type: "certificate", + object_id: row.id, + meta: _.omit(row, omissions()), + }); + + if (row.provider === "letsencrypt") { + // Revoke the cert + await internalCertificate.revokeLetsEncryptSsl(row); + } + return true; }, /** @@ -468,64 +396,66 @@ const internalCertificate = { * * @param {Access} access * @param {Array} [expand] - * @param {String} [search_query] + * @param {String} [searchQuery] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access.can("certificates:list").then((access_data) => { - const query = certificateModel - .query() - .where("is_deleted", 0) - .groupBy("id") - .allowGraph("[owner]") - .allowGraph("[proxy_hosts]") - .allowGraph("[redirection_hosts]") - .allowGraph("[dead_hosts]") - .orderBy("nice_name", "ASC"); + getAll: async (access, expand, searchQuery) => { + const accessData = await access.can("certificates:list"); - if (access_data.permission_visibility !== "all") { - query.andWhere("owner_user_id", access.token.getUserId(1)); - } + const query = certificateModel + .query() + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner]") + .allowGraph("[proxy_hosts]") + .allowGraph("[redirection_hosts]") + .allowGraph("[dead_hosts]") + .orderBy("nice_name", "ASC"); - // Query is used for searching - if (typeof search_query === "string") { - query.where(function () { - this.where("nice_name", "like", `%${search_query}%`); - }); - } + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - if (typeof expand !== "undefined" && expand !== null) { - query.withGraphFetched(`[${expand.join(", ")}]`); - } + // Query is used for searching + if (typeof searchQuery === "string") { + query.where(function () { + this.where("nice_name", "like", `%${searchQuery}%`); + }); + } - return query.then(utils.omitRows(omissions())); - }); + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + return await query.then(utils.omitRows(omissions())); }, /** * Report use * - * @param {Number} user_id + * @param {Number} userId * @param {String} visibility * @returns {Promise} */ - getCount: (user_id, visibility) => { - const query = certificateModel.query().count("id as count").where("is_deleted", 0); + getCount: async (userId, visibility) => { + const query = certificateModel + .query() + .count("id as count") + .where("is_deleted", 0); if (visibility !== "all") { - query.andWhere("owner_user_id", user_id); + query.andWhere("owner_user_id", userId); } - return query.first().then((row) => { - return Number.parseInt(row.count, 10); - }); + const row = await query.first(); + return Number.parseInt(row.count, 10); }, /** * @param {Object} certificate * @returns {Promise} */ - writeCustomCert: (certificate) => { + writeCustomCert: async (certificate) => { logger.info("Writing Custom Certificate:", certificate); const dir = `/data/custom_ssl/npm-${certificate.id}`; @@ -578,7 +508,7 @@ const internalCertificate = { * @param {Boolean} data.meta.letsencrypt_agree * @returns {Promise} */ - createQuickCertificate: (access, data) => { + createQuickCertificate: async (access, data) => { return internalCertificate.create(access, { provider: "letsencrypt", domain_names: data.domain_names, @@ -595,44 +525,38 @@ const internalCertificate = { * @returns {Promise} */ validate: (data) => { - return new Promise((resolve) => { - // Put file contents into an object - const files = {}; - _.map(data.files, (file, name) => { - if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { - files[name] = file.data.toString(); - } - }); - - resolve(files); - }).then((files) => { - // For each file, create a temp file and write the contents to it - // Then test it depending on the file type - const promises = []; - _.map(files, (content, type) => { - promises.push( - new Promise((resolve) => { - if (type === "certificate_key") { - resolve(internalCertificate.checkPrivateKey(content)); - } else { - // this should handle `certificate` and intermediate certificate - resolve(internalCertificate.getCertificateInfo(content, true)); - } - }).then((res) => { - return { [type]: res }; - }), - ); - }); - - return Promise.all(promises).then((files) => { - let data = {}; - - _.each(files, (file) => { - data = _.assign({}, data, file); - }); - - return data; + // Put file contents into an object + const files = {}; + _.map(data.files, (file, name) => { + if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { + files[name] = file.data.toString(); + } + }); + + // For each file, create a temp file and write the contents to it + // Then test it depending on the file type + const promises = []; + _.map(files, (content, type) => { + promises.push( + new Promise((resolve) => { + if (type === "certificate_key") { + resolve(internalCertificate.checkPrivateKey(content)); + } else { + // this should handle `certificate` and intermediate certificate + resolve(internalCertificate.getCertificateInfo(content, true)); + } + }).then((res) => { + return { [type]: res }; + }), + ); + }); + + return Promise.all(promises).then((files) => { + let data = {}; + _.each(files, (file) => { + data = _.assign({}, data, file); }); + return data; }); }, @@ -643,77 +567,62 @@ const internalCertificate = { * @param {Object} data.files * @returns {Promise} */ - upload: (access, data) => { - return internalCertificate.get(access, { id: data.id }).then((row) => { - if (row.provider !== "other") { - throw new error.ValidationError("Cannot upload certificates for this type of provider"); + upload: async (access, data) => { + const row = await internalCertificate.get(access, { id: data.id }); + if (row.provider !== "other") { + throw new error.ValidationError("Cannot upload certificates for this type of provider"); + } + + const validations = await internalCertificate.validate(data); + if (typeof validations.certificate === "undefined") { + throw new error.ValidationError("Certificate file was not provided"); + } + + _.map(data.files, (file, name) => { + if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { + row.meta[name] = file.data.toString(); } - - return internalCertificate - .validate(data) - .then((validations) => { - if (typeof validations.certificate === "undefined") { - throw new error.ValidationError("Certificate file was not provided"); - } - - _.map(data.files, (file, name) => { - if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { - row.meta[name] = file.data.toString(); - } - }); - - // TODO: This uses a mysql only raw function that won't translate to postgres - return internalCertificate - .update(access, { - id: data.id, - expires_on: moment(validations.certificate.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), - domain_names: [validations.certificate.cn], - meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later - }) - .then((certificate) => { - certificate.meta = row.meta; - return internalCertificate.writeCustomCert(certificate); - }); - }) - .then(() => { - return _.pick(row.meta, internalCertificate.allowedSslFiles); - }); }); + + const certificate = internalCertificate.update(access, { + id: data.id, + expires_on: moment(validations.certificate.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), + domain_names: [validations.certificate.cn], + meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later + }); + + certificate.meta = row.meta; + await internalCertificate.writeCustomCert(certificate); + return _.pick(row.meta, internalCertificate.allowedSslFiles); }, /** * Uses the openssl command to validate the private key. * It will save the file to disk first, then run commands on it, then delete the file. * - * @param {String} private_key This is the entire key contents as a string + * @param {String} privateKey This is the entire key contents as a string */ - checkPrivateKey: (private_key) => { - return tempWrite(private_key, "/tmp").then((filepath) => { - return new Promise((resolve, reject) => { - const failTimeout = setTimeout(() => { - reject( - new error.ValidationError( - "Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.", - ), - ); - }, 10000); - utils - .exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `) - .then((result) => { - clearTimeout(failTimeout); - if (!result.toLowerCase().includes("key is valid")) { - reject(new error.ValidationError(`Result Validation Error: ${result}`)); - } - fs.unlinkSync(filepath); - resolve(true); - }) - .catch((err) => { - clearTimeout(failTimeout); - fs.unlinkSync(filepath); - reject(new error.ValidationError(`Certificate Key is not valid (${err.message})`, err)); - }); - }); - }); + checkPrivateKey: async (privateKey) => { + const filepath = await tempWrite(privateKey, "/tmp"); + const failTimeout = setTimeout(() => { + throw new error.ValidationError( + "Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.", + ); + }, 10000); + + try { + const result = await utils.exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `); + clearTimeout(failTimeout); + if (!result.toLowerCase().includes("key is valid")) { + throw new error.ValidationError(`Result Validation Error: ${result}`); + } + fs.unlinkSync(filepath); + return true; + } catch (err) { + clearTimeout(failTimeout); + fs.unlinkSync(filepath); + throw new error.ValidationError(`Certificate Key is not valid (${err.message})`, err); + } }, /** @@ -721,104 +630,92 @@ const internalCertificate = { * It will save the file to disk first, then run commands on it, then delete the file. * * @param {String} certificate This is the entire cert contents as a string - * @param {Boolean} [throw_expired] Throw when the certificate is out of date + * @param {Boolean} [throwExpired] Throw when the certificate is out of date */ - getCertificateInfo: (certificate, throw_expired) => { - return tempWrite(certificate, "/tmp").then((filepath) => { - return internalCertificate - .getCertificateInfoFromFile(filepath, throw_expired) - .then((certData) => { - fs.unlinkSync(filepath); - return certData; - }) - .catch((err) => { - fs.unlinkSync(filepath); - throw err; - }); - }); + getCertificateInfo: async (certificate, throwExpired) => { + try { + const filepath = await tempWrite(certificate, "/tmp"); + const certData = await internalCertificate.getCertificateInfoFromFile(filepath, throwExpired); + fs.unlinkSync(filepath); + return certData; + } catch (err) { + fs.unlinkSync(filepath); + throw err; + } }, /** * Uses the openssl command to both validate and get info out of the certificate. * It will save the file to disk first, then run commands on it, then delete the file. * - * @param {String} certificate_file The file location on disk + * @param {String} certificateFile The file location on disk * @param {Boolean} [throw_expired] Throw when the certificate is out of date */ - getCertificateInfoFromFile: (certificate_file, throw_expired) => { + getCertificateInfoFromFile: async (certificateFile, throw_expired) => { const certData = {}; - return utils - .execFile("openssl", ["x509", "-in", certificate_file, "-subject", "-noout"]) - .then((result) => { - // Examples: - // subject=CN = *.jc21.com - // subject=CN = something.example.com - const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; - const match = regex.exec(result); - if (match && typeof match[1] !== "undefined") { - certData.cn = match[1]; - } - }) - .then(() => { - return utils.execFile("openssl", ["x509", "-in", certificate_file, "-issuer", "-noout"]); - }) + try { + const result = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-subject", "-noout"]) + // Examples: + // subject=CN = *.jc21.com + // subject=CN = something.example.com + const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; + const match = regex.exec(result); + if (match && typeof match[1] !== "undefined") { + certData.cn = match[1]; + } - .then((result) => { - // Examples: - // issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 - // issuer=C = US, O = Let's Encrypt, CN = E5 - // issuer=O = NginxProxyManager, CN = NginxProxyManager Intermediate CA","O = NginxProxyManager, CN = NginxProxyManager Intermediate CA - const regex = /^(?:issuer=)?(.*)$/gim; - const match = regex.exec(result); - if (match && typeof match[1] !== "undefined") { - certData.issuer = match[1]; - } - }) - .then(() => { - return utils.execFile("openssl", ["x509", "-in", certificate_file, "-dates", "-noout"]); - }) - .then((result) => { - // notBefore=Jul 14 04:04:29 2018 GMT - // notAfter=Oct 12 04:04:29 2018 GMT - let validFrom = null; - let validTo = null; + const result2 = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-issuer", "-noout"]); + // Examples: + // issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 + // issuer=C = US, O = Let's Encrypt, CN = E5 + // issuer=O = NginxProxyManager, CN = NginxProxyManager Intermediate CA","O = NginxProxyManager, CN = NginxProxyManager Intermediate CA + const regex2 = /^(?:issuer=)?(.*)$/gim; + const match2 = regex2.exec(result2); + if (match2 && typeof match2[1] !== "undefined") { + certData.issuer = match2[1]; + } - const lines = result.split("\n"); - lines.map((str) => { - const regex = /^(\S+)=(.*)$/gim; - const match = regex.exec(str.trim()); + const result3 = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-dates", "-noout"]); + // notBefore=Jul 14 04:04:29 2018 GMT + // notAfter=Oct 12 04:04:29 2018 GMT + let validFrom = null; + let validTo = null; - if (match && typeof match[2] !== "undefined") { - const date = Number.parseInt(moment(match[2], "MMM DD HH:mm:ss YYYY z").format("X"), 10); + const lines = result3.split("\n"); + lines.map((str) => { + const regex = /^(\S+)=(.*)$/gim; + const match = regex.exec(str.trim()); - if (match[1].toLowerCase() === "notbefore") { - validFrom = date; - } else if (match[1].toLowerCase() === "notafter") { - validTo = date; - } + if (match && typeof match[2] !== "undefined") { + const date = Number.parseInt(moment(match[2], "MMM DD HH:mm:ss YYYY z").format("X"), 10); + + if (match[1].toLowerCase() === "notbefore") { + validFrom = date; + } else if (match[1].toLowerCase() === "notafter") { + validTo = date; } - return true; - }); - - if (!validFrom || !validTo) { - throw new error.ValidationError(`Could not determine dates from certificate: ${result}`); } - - if (throw_expired && validTo < Number.parseInt(moment().format("X"), 10)) { - throw new error.ValidationError("Certificate has expired"); - } - - certData.dates = { - from: validFrom, - to: validTo, - }; - - return certData; - }) - .catch((err) => { - throw new error.ValidationError(`Certificate is not valid (${err.message})`, err); + return true; }); + + if (!validFrom || !validTo) { + throw new error.ValidationError(`Could not determine dates from certificate: ${result}`); + } + + if (throw_expired && validTo < Number.parseInt(moment().format("X"), 10)) { + throw new error.ValidationError("Certificate has expired"); + } + + certData.dates = { + from: validFrom, + to: validTo, + }; + + return certData; + } catch (err) { + throw new error.ValidationError(`Certificate is not valid (${err.message})`, err); + } }, /** @@ -839,7 +736,6 @@ const internalCertificate = { } return true; }); - return meta; }, @@ -848,7 +744,7 @@ const internalCertificate = { * @param {Object} certificate the certificate row * @returns {Promise} */ - requestLetsEncryptSsl: (certificate) => { + requestLetsEncryptSsl: async (certificate) => { logger.info( `Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, ); @@ -879,17 +775,13 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - return utils.execFile(certbotCommand, args, adds.opts).then((result) => { - logger.success(result); - return result; - }); + const result = await utils.execFile(certbotCommand, args, adds.opts); + logger.success(result); + return result; }, /** - * @param {Object} certificate the certificate row - * @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.json`) - * @param {String | null} credentials the content of this providers credentials file - * @param {String} propagation_seconds + * @param {Object} certificate the certificate row * @returns {Promise} */ requestLetsEncryptSslWithDnsChallenge: async (certificate) => { @@ -957,52 +849,43 @@ const internalCertificate = { * @param {Number} data.id * @returns {Promise} */ - renew: (access, data) => { - return access - .can("certificates:update", data) - .then(() => { - return internalCertificate.get(access, data); - }) - .then((certificate) => { - if (certificate.provider === "letsencrypt") { - const renewMethod = certificate.meta.dns_challenge - ? internalCertificate.renewLetsEncryptSslWithDnsChallenge - : internalCertificate.renewLetsEncryptSsl; + renew: async (access, data) => { + await access.can("certificates:update", data) + const certificate = await internalCertificate.get(access, data); - return renewMethod(certificate) - .then(() => { - return internalCertificate.getCertificateInfoFromFile( - `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, - ); - }) - .then((cert_info) => { - return certificateModel.query().patchAndFetchById(certificate.id, { - expires_on: moment(cert_info.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), - }); - }) - .then((updated_certificate) => { - // Add to audit log - return internalAuditLog - .add(access, { - action: "renewed", - object_type: "certificate", - object_id: updated_certificate.id, - meta: updated_certificate, - }) - .then(() => { - return updated_certificate; - }); - }); - } - throw new error.ValidationError("Only Let'sEncrypt certificates can be renewed"); + if (certificate.provider === "letsencrypt") { + const renewMethod = certificate.meta.dns_challenge + ? internalCertificate.renewLetsEncryptSslWithDnsChallenge + : internalCertificate.renewLetsEncryptSsl; + + await renewMethod(certificate); + const certInfo = await internalCertificate.getCertificateInfoFromFile( + `${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, + ); + + const updatedCertificate = await certificateModel + .query() + .patchAndFetchById(certificate.id, { + expires_on: moment(certInfo.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), + }); + + // Add to audit log + await internalAuditLog.add(access, { + action: "renewed", + object_type: "certificate", + object_id: updatedCertificate.id, + meta: updatedCertificate, }); + } else { + throw new error.ValidationError("Only Let'sEncrypt certificates can be renewed"); + } }, /** * @param {Object} certificate the certificate row * @returns {Promise} */ - renewLetsEncryptSsl: (certificate) => { + renewLetsEncryptSsl: async (certificate) => { logger.info( `Renewing LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, ); @@ -1029,19 +912,17 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - return utils.execFile(certbotCommand, args, adds.opts).then((result) => { - logger.info(result); - return result; - }); + const result = await utils.execFile(certbotCommand, args, adds.opts); + logger.info(result); + return result; }, /** * @param {Object} certificate the certificate row * @returns {Promise} */ - renewLetsEncryptSslWithDnsChallenge: (certificate) => { + renewLetsEncryptSslWithDnsChallenge: async (certificate) => { const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; - if (!dnsPlugin) { throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); } @@ -1070,18 +951,17 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - return utils.execFile(certbotCommand, args, adds.opts).then(async (result) => { - logger.info(result); - return result; - }); + const result = await utils.execFile(certbotCommand, args, adds.opts); + logger.info(result); + return result; }, /** * @param {Object} certificate the certificate row - * @param {Boolean} [throw_errors] + * @param {Boolean} [throwErrors] * @returns {Promise} */ - revokeLetsEncryptSsl: (certificate, throw_errors) => { + revokeLetsEncryptSsl: async (certificate, throwErrors) => { logger.info( `Revoking LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(", ")}`, ); @@ -1104,20 +984,17 @@ const internalCertificate = { logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); - return utils - .execFile(certbotCommand, args, adds.opts) - .then(async (result) => { - await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); - logger.info(result); - return result; - }) - .catch((err) => { - logger.error(err.message); - - if (throw_errors) { - throw err; - } - }); + try { + const result = await utils.execFile(certbotCommand, args, adds.opts); + await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); + logger.info(result); + return result; + } catch (err) { + logger.error(err.message); + if (throwErrors) { + throw err; + } + } }, /** @@ -1130,59 +1007,51 @@ const internalCertificate = { }, /** - * @param {Object} in_use_result - * @param {Number} in_use_result.total_count - * @param {Array} in_use_result.proxy_hosts - * @param {Array} in_use_result.redirection_hosts - * @param {Array} in_use_result.dead_hosts + * @param {Object} inUseResult + * @param {Number} inUseResult.total_count + * @param {Array} inUseResult.proxy_hosts + * @param {Array} inUseResult.redirection_hosts + * @param {Array} inUseResult.dead_hosts + * @returns {Promise} */ - disableInUseHosts: (in_use_result) => { - if (in_use_result.total_count) { - const promises = []; - - if (in_use_result.proxy_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs("proxy_host", in_use_result.proxy_hosts)); + disableInUseHosts: async (inUseResult) => { + if (inUseResult?.total_count) { + if (inUseResult?.proxy_hosts.length) { + await internalNginx.bulkDeleteConfigs("proxy_host", inUseResult.proxy_hosts); } - if (in_use_result.redirection_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs("redirection_host", in_use_result.redirection_hosts)); + if (inUseResult?.redirection_hosts.length) { + await internalNginx.bulkDeleteConfigs("redirection_host", inUseResult.redirection_hosts); } - if (in_use_result.dead_hosts.length) { - promises.push(internalNginx.bulkDeleteConfigs("dead_host", in_use_result.dead_hosts)); + if (inUseResult?.dead_hosts.length) { + await internalNginx.bulkDeleteConfigs("dead_host", inUseResult.dead_hosts); } - - return Promise.all(promises); } - return Promise.resolve(); }, /** - * @param {Object} in_use_result - * @param {Number} in_use_result.total_count - * @param {Array} in_use_result.proxy_hosts - * @param {Array} in_use_result.redirection_hosts - * @param {Array} in_use_result.dead_hosts + * @param {Object} inUseResult + * @param {Number} inUseResult.total_count + * @param {Array} inUseResult.proxy_hosts + * @param {Array} inUseResult.redirection_hosts + * @param {Array} inUseResult.dead_hosts + * @returns {Promise} */ - enableInUseHosts: (in_use_result) => { - if (in_use_result.total_count) { - const promises = []; - - if (in_use_result.proxy_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs("proxy_host", in_use_result.proxy_hosts)); + enableInUseHosts: async (inUseResult) => { + if (inUseResult.total_count) { + if (inUseResult.proxy_hosts.length) { + await internalNginx.bulkGenerateConfigs("proxy_host", inUseResult.proxy_hosts); } - if (in_use_result.redirection_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs("redirection_host", in_use_result.redirection_hosts)); + if (inUseResult.redirection_hosts.length) { + await internalNginx.bulkGenerateConfigs("redirection_host", inUseResult.redirection_hosts); } - if (in_use_result.dead_hosts.length) { - promises.push(internalNginx.bulkGenerateConfigs("dead_host", in_use_result.dead_hosts)); + if (inUseResult.dead_hosts.length) { + await internalNginx.bulkGenerateConfigs("dead_host", inUseResult.dead_hosts); } - - return Promise.all(promises); } - return Promise.resolve(); }, testHttpsChallenge: async (access, domains) => { @@ -1293,9 +1162,7 @@ const internalCertificate = { return "no-host"; } // Other errors - logger.info( - `HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`, - ); + logger.info(`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`); return `other:${result.responsecode}`; } @@ -1335,9 +1202,9 @@ const internalCertificate = { return { args: args, opts: opts }; }, - getLiveCertPath: (certificate_id) => { - return `/etc/letsencrypt/live/npm-${certificate_id}`; - }, + getLiveCertPath: (certificateId) => { + return `/etc/letsencrypt/live/npm-${certificateId}`; + } }; export default internalCertificate; diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js index 8b8573ca..3121d2af 100644 --- a/backend/internal/dead-host.js +++ b/backend/internal/dead-host.js @@ -18,91 +18,72 @@ const internalDeadHost = { * @param {Object} data * @returns {Promise} */ - create: (access, data) => { + create: async (access, data) => { const createCertificate = data.certificate_id === "new"; if (createCertificate) { delete data.certificate_id; } - 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 - const domain_name_check_promises = []; + await access.can("dead_hosts:create", data); - data.domain_names.map((domain_name) => { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); - return true; - }); + // Get a list of the domain names and check each of them against existing records + const domainNameCheckPromises = []; - return Promise.all(domain_name_check_promises).then((check_results) => { - check_results.map((result) => { - if (result.is_taken) { - throw new errs.ValidationError(`${result.hostname} is already in use`); - } - return true; - }); - }); - }) - .then(() => { - // At this point the domains should have been checked - data.owner_user_id = access.token.getUserId(1); - const thisData = internalHost.cleanSslHstsData(data); + data.domain_names.map((domain_name) => { + domainNameCheckPromises.push(internalHost.isHostnameTaken(domain_name)); + return true; + }); - // Fix for db field not having a default value - // for this optional field. - if (typeof data.advanced_config === "undefined") { - thisData.advanced_config = ""; + await Promise.all(domainNameCheckPromises).then((check_results) => { + check_results.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); } - - return deadHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); - }) - .then((row) => { - if (createCertificate) { - return internalCertificate - .createQuickCertificate(access, data) - .then((cert) => { - // update host with cert id - return internalDeadHost.update(access, { - id: row.id, - certificate_id: cert.id, - }); - }) - .then(() => { - return row; - }); - } - return row; - }) - .then((row) => { - // re-fetch with cert - return internalDeadHost.get(access, { - id: row.id, - expand: ["certificate", "owner"], - }); - }) - .then((row) => { - // Configure nginx - return internalNginx.configure(deadHostModel, "dead_host", row).then(() => { - return row; - }); - }) - .then((row) => { - data.meta = _.assign({}, data.meta || {}, row.meta); - - // Add to audit log - return internalAuditLog - .add(access, { - action: "created", - object_type: "dead-host", - object_id: row.id, - meta: data, - }) - .then(() => { - return row; - }); + return true; }); + }); + + // At this point the domains should have been checked + data.owner_user_id = access.token.getUserId(1); + const thisData = internalHost.cleanSslHstsData(data); + + // Fix for db field not having a default value + // for this optional field. + if (typeof data.advanced_config === "undefined") { + thisData.advanced_config = ""; + } + + const row = await deadHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions())); + + if (createCertificate) { + const cert = await internalCertificate.createQuickCertificate(access, data); + // update host with cert id + await internalDeadHost.update(access, { + id: row.id, + certificate_id: cert.id, + }); + } + + // re-fetch with cert + const freshRow = await internalDeadHost.get(access, { + id: row.id, + expand: ["certificate", "owner"], + }); + + // Configure nginx + await internalNginx.configure(deadHostModel, "dead_host", freshRow); + data.meta = _.assign({}, data.meta || {}, freshRow.meta); + + // Add to audit log + await internalAuditLog.add(access, { + action: "created", + object_type: "dead-host", + object_id: freshRow.id, + meta: data, + }); + + return freshRow; }, /** @@ -111,107 +92,79 @@ const internalDeadHost = { * @param {Number} data.id * @return {Promise} */ - update: (access, data) => { - let thisData = data; - const createCertificate = thisData.certificate_id === "new"; + update: async (access, data) => { + const createCertificate = data.certificate_id === "new"; if (createCertificate) { - delete thisData.certificate_id; + delete data.certificate_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 - const domain_name_check_promises = []; + await access.can("dead_hosts:update", data.id); - if (typeof thisData.domain_names !== "undefined") { - thisData.domain_names.map((domain_name) => { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, "dead", data.id)); - return true; - }); - - return Promise.all(domain_name_check_promises).then((check_results) => { - check_results.map((result) => { - if (result.is_taken) { - throw new errs.ValidationError(`${result.hostname} is already in use`); - } - return true; - }); - }); - } - }) - .then(() => { - return internalDeadHost.get(access, { id: thisData.id }); - }) - .then((row) => { - if (row.id !== thisData.id) { - // Sanity check that something crazy hasn't happened - throw new errs.InternalValidationError( - `404 Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`, - ); - } - - if (createCertificate) { - return internalCertificate - .createQuickCertificate(access, { - domain_names: thisData.domain_names || row.domain_names, - meta: _.assign({}, row.meta, thisData.meta), - }) - .then((cert) => { - // update host with cert id - thisData.certificate_id = cert.id; - }) - .then(() => { - return row; - }); - } - return row; - }) - .then((row) => { - // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. - thisData = _.assign( - {}, - { - domain_names: row.domain_names, - }, - data, - ); - - thisData = internalHost.cleanSslHstsData(thisData, row); - - return deadHostModel - .query() - .where({ id: thisData.id }) - .patch(thisData) - .then((saved_row) => { - // Add to audit log - return internalAuditLog - .add(access, { - action: "updated", - object_type: "dead-host", - object_id: row.id, - meta: thisData, - }) - .then(() => { - return _.omit(saved_row, omissions()); - }); - }); - }) - .then(() => { - return internalDeadHost - .get(access, { - id: thisData.id, - expand: ["owner", "certificate"], - }) - .then((row) => { - // Configure nginx - return internalNginx.configure(deadHostModel, "dead_host", row).then((new_meta) => { - row.meta = new_meta; - return _.omit(internalHost.cleanRowCertificateMeta(row), omissions()); - }); - }); + // Get a list of the domain names and check each of them against existing records + const domainNameCheckPromises = []; + if (typeof data.domain_names !== "undefined") { + data.domain_names.map((domainName) => { + domainNameCheckPromises.push(internalHost.isHostnameTaken(domainName, "dead", data.id)); + return true; }); + + const checkResults = await Promise.all(domainNameCheckPromises); + checkResults.map((result) => { + if (result.is_taken) { + throw new errs.ValidationError(`${result.hostname} is already in use`); + } + return true; + }); + } + const row = await internalDeadHost.get(access, { id: data.id }); + + if (row.id !== data.id) { + // Sanity check that something crazy hasn't happened + throw new errs.InternalValidationError( + `404 Host could not be updated, IDs do not match: ${row.id} !== ${data.id}`, + ); + } + + if (createCertificate) { + const cert = await internalCertificate.createQuickCertificate(access, { + domain_names: data.domain_names || row.domain_names, + meta: _.assign({}, row.meta, data.meta), + }); + + // update host with cert id + data.certificate_id = cert.id; + } + + // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. + let thisData = _.assign( + {}, + { + domain_names: row.domain_names, + }, + data, + ); + + thisData = internalHost.cleanSslHstsData(thisData, row); + + // Add to audit log + await internalAuditLog.add(access, { + action: "updated", + object_type: "dead-host", + object_id: row.id, + meta: thisData, + }); + + const thisRow = await internalDeadHost + .get(access, { + id: thisData.id, + expand: ["owner", "certificate"], + }); + + // Configure nginx + const newMeta = await internalNginx.configure(deadHostModel, "dead_host", row); + row.meta = newMeta; + return _.omit(internalHost.cleanRowCertificateMeta(thisRow), omissions()); }, /** @@ -222,39 +175,32 @@ const internalDeadHost = { * @param {Array} [data.omit] * @return {Promise} */ - get: (access, data) => { - const thisData = data || {}; + get: async (access, data) => { + const accessData = await access.can("dead_hosts:get", data.id); + const query = deadHostModel + .query() + .where("is_deleted", 0) + .andWhere("id", data.id) + .allowGraph("[owner,certificate]") + .first(); - return access - .can("dead_hosts:get", thisData.id) - .then((access_data) => { - const query = deadHostModel - .query() - .where("is_deleted", 0) - .andWhere("id", dthisDataata.id) - .allowGraph("[owner,certificate]") - .first(); + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - if (access_data.permission_visibility !== "all") { - query.andWhere("owner_user_id", access.token.getUserId(1)); - } + if (typeof data.expand !== "undefined" && data.expand !== null) { + query.withGraphFetched(`[${data.expand.join(", ")}]`); + } - if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { - query.withGraphFetched(`[${data.expand.join(", ")}]`); - } - - return query.then(utils.omitRow(omissions())); - }) - .then((row) => { - if (!row || !row.id) { - throw new errs.ItemNotFoundError(thisData.id); - } - // Custom omissions - if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { - return _.omit(row, thisData.omit); - } - return row; - }); + const row = await query.then(utils.omitRow(omissions())); + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } + // Custom omissions + if (typeof data.omit !== "undefined" && data.omit !== null) { + return _.omit(row, data.omit); + } + return row; }, /** @@ -264,42 +210,30 @@ const internalDeadHost = { * @param {String} [data.reason] * @returns {Promise} */ - delete: (access, data) => { - return access - .can("dead_hosts:delete", data.id) - .then(() => { - return internalDeadHost.get(access, { id: data.id }); - }) - .then((row) => { - if (!row || !row.id) { - throw new errs.ItemNotFoundError(data.id); - } + delete: async (access, data) => { + await access.can("dead_hosts:delete", data.id) + const row = await internalDeadHost.get(access, { id: data.id }); + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } - return deadHostModel - .query() - .where("id", row.id) - .patch({ - is_deleted: 1, - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig("dead_host", row).then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: "deleted", - object_type: "dead-host", - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; + await deadHostModel + .query() + .where("id", row.id) + .patch({ + is_deleted: 1, }); + + // Delete Nginx Config + await internalNginx.deleteConfig("dead_host", row); + await internalNginx.reload(); + // Add to audit log + await internalAuditLog.add(access, { + action: "deleted", + object_type: "dead-host", + object_id: row.id, + meta: _.omit(row, omissions()), + }); }, /** @@ -309,48 +243,39 @@ const internalDeadHost = { * @param {String} [data.reason] * @returns {Promise} */ - enable: (access, data) => { - return access - .can("dead_hosts:update", data.id) - .then(() => { - return internalDeadHost.get(access, { - id: data.id, - expand: ["certificate", "owner"], - }); - }) - .then((row) => { - if (!row || !row.id) { - throw new errs.ItemNotFoundError(data.id); - } - if (row.enabled) { - throw new errs.ValidationError("Host is already enabled"); - } + enable: async (access, data) => { + await access.can("dead_hosts:update", data.id) + const row = await internalDeadHost.get(access, { + id: data.id, + expand: ["certificate", "owner"], + }); + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } + if (row.enabled) { + throw new errs.ValidationError("Host is already enabled"); + } - row.enabled = 1; + row.enabled = 1; - return deadHostModel - .query() - .where("id", row.id) - .patch({ - enabled: 1, - }) - .then(() => { - // Configure nginx - return internalNginx.configure(deadHostModel, "dead_host", row); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: "enabled", - object_type: "dead-host", - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; + await deadHostModel + .query() + .where("id", row.id) + .patch({ + enabled: 1, }); + + // Configure nginx + await internalNginx.configure(deadHostModel, "dead_host", row); + + // Add to audit log + await internalAuditLog.add(access, { + action: "enabled", + object_type: "dead-host", + object_id: row.id, + meta: _.omit(row, omissions()), + }); + return true; }, /** @@ -360,47 +285,37 @@ const internalDeadHost = { * @param {String} [data.reason] * @returns {Promise} */ - disable: (access, data) => { - return access - .can("dead_hosts:update", data.id) - .then(() => { - return internalDeadHost.get(access, { id: data.id }); - }) - .then((row) => { - if (!row || !row.id) { - throw new errs.ItemNotFoundError(data.id); - } - if (!row.enabled) { - throw new errs.ValidationError("Host is already disabled"); - } + disable: async (access, data) => { + await access.can("dead_hosts:update", data.id) + const row = await internalDeadHost.get(access, { id: data.id }); + if (!row || !row.id) { + throw new errs.ItemNotFoundError(data.id); + } + if (!row.enabled) { + throw new errs.ValidationError("Host is already disabled"); + } - row.enabled = 0; + row.enabled = 0; - return deadHostModel - .query() - .where("id", row.id) - .patch({ - enabled: 0, - }) - .then(() => { - // Delete Nginx Config - return internalNginx.deleteConfig("dead_host", row).then(() => { - return internalNginx.reload(); - }); - }) - .then(() => { - // Add to audit log - return internalAuditLog.add(access, { - action: "disabled", - object_type: "dead-host", - object_id: row.id, - meta: _.omit(row, omissions()), - }); - }); - }) - .then(() => { - return true; + await deadHostModel + .query() + .where("id", row.id) + .patch({ + enabled: 0, }); + + // Delete Nginx Config + await internalNginx.deleteConfig("dead_host", row); + await internalNginx.reload(); + + // Add to audit log + await internalAuditLog.add(access, { + action: "disabled", + object_type: "dead-host", + object_id: row.id, + meta: _.omit(row, omissions()), + }); + return true; }, /** @@ -408,44 +323,38 @@ const internalDeadHost = { * * @param {Access} access * @param {Array} [expand] - * @param {String} [search_query] + * @param {String} [searchQuery] * @returns {Promise} */ - getAll: (access, expand, search_query) => { - return access - .can("dead_hosts:list") - .then((access_data) => { - const query = deadHostModel - .query() - .where("is_deleted", 0) - .groupBy("id") - .allowGraph("[owner,certificate]") - .orderBy(castJsonIfNeed("domain_names"), "ASC"); + getAll: async (access, expand, searchQuery) => { + const accessData = await access.can("dead_hosts:list") + const query = deadHostModel + .query() + .where("is_deleted", 0) + .groupBy("id") + .allowGraph("[owner,certificate]") + .orderBy(castJsonIfNeed("domain_names"), "ASC"); - if (access_data.permission_visibility !== "all") { - query.andWhere("owner_user_id", access.token.getUserId(1)); - } + if (accessData.permission_visibility !== "all") { + query.andWhere("owner_user_id", access.token.getUserId(1)); + } - // Query is used for searching - if (typeof search_query === "string" && search_query.length > 0) { - query.where(function () { - this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`); - }); - } - - if (typeof expand !== "undefined" && expand !== null) { - query.withGraphFetched(`[${expand.join(", ")}]`); - } - - return query.then(utils.omitRows(omissions())); - }) - .then((rows) => { - if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { - return internalHost.cleanAllRowsCertificateMeta(rows); - } - - return rows; + // Query is used for searching + if (typeof searchQuery === "string" && searchQuery.length > 0) { + query.where(function () { + this.where(castJsonIfNeed("domain_names"), "like", `%${searchQuery}%`); }); + } + + if (typeof expand !== "undefined" && expand !== null) { + query.withGraphFetched(`[${expand.join(", ")}]`); + } + + const rows = await query.then(utils.omitRows(omissions())); + if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) { + internalHost.cleanAllRowsCertificateMeta(rows); + } + return rows; }, /** @@ -455,16 +364,15 @@ const internalDeadHost = { * @param {String} visibility * @returns {Promise} */ - getCount: (user_id, visibility) => { + getCount: async (user_id, visibility) => { const query = deadHostModel.query().count("id as count").where("is_deleted", 0); if (visibility !== "all") { query.andWhere("owner_user_id", user_id); } - return query.first().then((row) => { - return Number.parseInt(row.count, 10); - }); + const row = await query.first(); + return Number.parseInt(row.count, 10); }, }; diff --git a/backend/internal/host.js b/backend/internal/host.js index a7f6f46a..74871626 100644 --- a/backend/internal/host.js +++ b/backend/internal/host.js @@ -65,50 +65,33 @@ const internalHost = { }, /** - * This returns all the host types with any domain listed in the provided domain_names array. + * This returns all the host types with any domain listed in the provided domainNames array. * This is used by the certificates to temporarily disable any host that is using the domain * - * @param {Array} domain_names + * @param {Array} domainNames * @returns {Promise} */ - getHostsWithDomains: (domain_names) => { - const promises = [ - proxyHostModel.query().where("is_deleted", 0), - redirectionHostModel.query().where("is_deleted", 0), - deadHostModel.query().where("is_deleted", 0), - ]; + getHostsWithDomains: async (domainNames) => { + const responseObject = { + total_count: 0, + dead_hosts: [], + proxy_hosts: [], + redirection_hosts: [], + }; - return Promise.all(promises).then((promises_results) => { - const response_object = { - total_count: 0, - dead_hosts: [], - proxy_hosts: [], - redirection_hosts: [], - }; + const proxyRes = await proxyHostModel.query().where("is_deleted", 0); + responseObject.proxy_hosts = internalHost._getHostsWithDomains(proxyRes, domainNames); + responseObject.total_count += responseObject.proxy_hosts.length; - if (promises_results[0]) { - // Proxy Hosts - 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); + responseObject.redirection_hosts = internalHost._getHostsWithDomains(redirRes, domainNames); + responseObject.total_count += responseObject.redirection_hosts.length; - if (promises_results[1]) { - // Redirection Hosts - 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); + responseObject.dead_hosts = internalHost._getHostsWithDomains(deadRes, domainNames); + responseObject.total_count += responseObject.dead_hosts.length; - if (promises_results[2]) { - // Dead Hosts - response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names); - response_object.total_count += response_object.dead_hosts.length; - } - - return response_object; - }); + return responseObject; }, /**