redirection-host.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. import _ from "lodash";
  2. import errs from "../lib/error.js";
  3. import { castJsonIfNeed } from "../lib/helpers.js";
  4. import utils from "../lib/utils.js";
  5. import redirectionHostModel from "../models/redirection_host.js";
  6. import internalAuditLog from "./audit-log.js";
  7. import internalCertificate from "./certificate.js";
  8. import internalHost from "./host.js";
  9. import internalNginx from "./nginx.js";
  10. const omissions = () => {
  11. return ["is_deleted"];
  12. };
  13. const internalRedirectionHost = {
  14. /**
  15. * @param {Access} access
  16. * @param {Object} data
  17. * @returns {Promise}
  18. */
  19. create: (access, data) => {
  20. let thisData = data || {};
  21. const createCertificate = thisData.certificate_id === "new";
  22. if (createCertificate) {
  23. delete thisData.certificate_id;
  24. }
  25. return access
  26. .can("redirection_hosts:create", thisData)
  27. .then((/*access_data*/) => {
  28. // Get a list of the domain names and check each of them against existing records
  29. const domain_name_check_promises = [];
  30. thisData.domain_names.map((domain_name) => {
  31. domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
  32. return true;
  33. });
  34. return Promise.all(domain_name_check_promises).then((check_results) => {
  35. check_results.map((result) => {
  36. if (result.is_taken) {
  37. throw new errs.ValidationError(`${result.hostname} is already in use`);
  38. }
  39. return true;
  40. });
  41. });
  42. })
  43. .then(() => {
  44. // At this point the domains should have been checked
  45. thisData.owner_user_id = access.token.getUserId(1);
  46. thisData = internalHost.cleanSslHstsData(thisData);
  47. // Fix for db field not having a default value
  48. // for this optional field.
  49. if (typeof data.advanced_config === "undefined") {
  50. data.advanced_config = "";
  51. }
  52. return redirectionHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions()));
  53. })
  54. .then((row) => {
  55. if (createCertificate) {
  56. return internalCertificate
  57. .createQuickCertificate(access, thisData)
  58. .then((cert) => {
  59. // update host with cert id
  60. return internalRedirectionHost.update(access, {
  61. id: row.id,
  62. certificate_id: cert.id,
  63. });
  64. })
  65. .then(() => {
  66. return row;
  67. });
  68. }
  69. return row;
  70. })
  71. .then((row) => {
  72. // re-fetch with cert
  73. return internalRedirectionHost.get(access, {
  74. id: row.id,
  75. expand: ["certificate", "owner"],
  76. });
  77. })
  78. .then((row) => {
  79. // Configure nginx
  80. return internalNginx.configure(redirectionHostModel, "redirection_host", row).then(() => {
  81. return row;
  82. });
  83. })
  84. .then((row) => {
  85. thisData.meta = _.assign({}, thisData.meta || {}, row.meta);
  86. // Add to audit log
  87. return internalAuditLog
  88. .add(access, {
  89. action: "created",
  90. object_type: "redirection-host",
  91. object_id: row.id,
  92. meta: thisData,
  93. })
  94. .then(() => {
  95. return row;
  96. });
  97. });
  98. },
  99. /**
  100. * @param {Access} access
  101. * @param {Object} data
  102. * @param {Number} data.id
  103. * @return {Promise}
  104. */
  105. update: (access, data) => {
  106. let thisData = data || {};
  107. const createCertificate = thisData.certificate_id === "new";
  108. if (createCertificate) {
  109. delete thisData.certificate_id;
  110. }
  111. return access
  112. .can("redirection_hosts:update", thisData.id)
  113. .then((/*access_data*/) => {
  114. // Get a list of the domain names and check each of them against existing records
  115. const domain_name_check_promises = [];
  116. if (typeof thisData.domain_names !== "undefined") {
  117. thisData.domain_names.map((domain_name) => {
  118. domain_name_check_promises.push(
  119. internalHost.isHostnameTaken(domain_name, "redirection", thisData.id),
  120. );
  121. return true;
  122. });
  123. return Promise.all(domain_name_check_promises).then((check_results) => {
  124. check_results.map((result) => {
  125. if (result.is_taken) {
  126. throw new errs.ValidationError(`${result.hostname} is already in use`);
  127. }
  128. return true;
  129. });
  130. });
  131. }
  132. })
  133. .then(() => {
  134. return internalRedirectionHost.get(access, { id: thisData.id });
  135. })
  136. .then((row) => {
  137. if (row.id !== thisData.id) {
  138. // Sanity check that something crazy hasn't happened
  139. throw new errs.InternalValidationError(
  140. `Redirection Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`,
  141. );
  142. }
  143. if (createCertificate) {
  144. return internalCertificate
  145. .createQuickCertificate(access, {
  146. domain_names: thisData.domain_names || row.domain_names,
  147. meta: _.assign({}, row.meta, thisData.meta),
  148. })
  149. .then((cert) => {
  150. // update host with cert id
  151. thisData.certificate_id = cert.id;
  152. })
  153. .then(() => {
  154. return row;
  155. });
  156. }
  157. return row;
  158. })
  159. .then((row) => {
  160. // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
  161. thisData = _.assign(
  162. {},
  163. {
  164. domain_names: row.domain_names,
  165. },
  166. thisData,
  167. );
  168. thisData = internalHost.cleanSslHstsData(thisData, row);
  169. return redirectionHostModel
  170. .query()
  171. .where({ id: thisData.id })
  172. .patch(thisData)
  173. .then((saved_row) => {
  174. // Add to audit log
  175. return internalAuditLog
  176. .add(access, {
  177. action: "updated",
  178. object_type: "redirection-host",
  179. object_id: row.id,
  180. meta: thisData,
  181. })
  182. .then(() => {
  183. return _.omit(saved_row, omissions());
  184. });
  185. });
  186. })
  187. .then(() => {
  188. return internalRedirectionHost
  189. .get(access, {
  190. id: thisData.id,
  191. expand: ["owner", "certificate"],
  192. })
  193. .then((row) => {
  194. // Configure nginx
  195. return internalNginx
  196. .configure(redirectionHostModel, "redirection_host", row)
  197. .then((new_meta) => {
  198. row.meta = new_meta;
  199. return _.omit(internalHost.cleanRowCertificateMeta(row), omissions());
  200. });
  201. });
  202. });
  203. },
  204. /**
  205. * @param {Access} access
  206. * @param {Object} data
  207. * @param {Number} data.id
  208. * @param {Array} [data.expand]
  209. * @param {Array} [data.omit]
  210. * @return {Promise}
  211. */
  212. get: (access, data) => {
  213. const thisData = data || {};
  214. return access
  215. .can("redirection_hosts:get", thisData.id)
  216. .then((access_data) => {
  217. const query = redirectionHostModel
  218. .query()
  219. .where("is_deleted", 0)
  220. .andWhere("id", thisData.id)
  221. .allowGraph("[owner,certificate]")
  222. .first();
  223. if (access_data.permission_visibility !== "all") {
  224. query.andWhere("owner_user_id", access.token.getUserId(1));
  225. }
  226. if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
  227. query.withGraphFetched(`[${thisData.expand.join(", ")}]`);
  228. }
  229. return query.then(utils.omitRow(omissions()));
  230. })
  231. .then((row) => {
  232. let thisRow = row;
  233. if (!thisRow || !thisRow.id) {
  234. throw new errs.ItemNotFoundError(thisData.id);
  235. }
  236. thisRow = internalHost.cleanRowCertificateMeta(thisRow);
  237. // Custom omissions
  238. if (typeof thisData.omit !== "undefined" && thisData.omit !== null) {
  239. return _.omit(thisRow, thisData.omit);
  240. }
  241. return thisRow;
  242. });
  243. },
  244. /**
  245. * @param {Access} access
  246. * @param {Object} data
  247. * @param {Number} data.id
  248. * @param {String} [data.reason]
  249. * @returns {Promise}
  250. */
  251. delete: (access, data) => {
  252. return access
  253. .can("redirection_hosts:delete", data.id)
  254. .then(() => {
  255. return internalRedirectionHost.get(access, { id: data.id });
  256. })
  257. .then((row) => {
  258. if (!row || !row.id) {
  259. throw new errs.ItemNotFoundError(data.id);
  260. }
  261. return redirectionHostModel
  262. .query()
  263. .where("id", row.id)
  264. .patch({
  265. is_deleted: 1,
  266. })
  267. .then(() => {
  268. // Delete Nginx Config
  269. return internalNginx.deleteConfig("redirection_host", row).then(() => {
  270. return internalNginx.reload();
  271. });
  272. })
  273. .then(() => {
  274. // Add to audit log
  275. return internalAuditLog.add(access, {
  276. action: "deleted",
  277. object_type: "redirection-host",
  278. object_id: row.id,
  279. meta: _.omit(row, omissions()),
  280. });
  281. });
  282. })
  283. .then(() => {
  284. return true;
  285. });
  286. },
  287. /**
  288. * @param {Access} access
  289. * @param {Object} data
  290. * @param {Number} data.id
  291. * @param {String} [data.reason]
  292. * @returns {Promise}
  293. */
  294. enable: (access, data) => {
  295. return access
  296. .can("redirection_hosts:update", data.id)
  297. .then(() => {
  298. return internalRedirectionHost.get(access, {
  299. id: data.id,
  300. expand: ["certificate", "owner"],
  301. });
  302. })
  303. .then((row) => {
  304. if (!row || !row.id) {
  305. throw new errs.ItemNotFoundError(data.id);
  306. }
  307. if (row.enabled) {
  308. throw new errs.ValidationError("Host is already enabled");
  309. }
  310. row.enabled = 1;
  311. return redirectionHostModel
  312. .query()
  313. .where("id", row.id)
  314. .patch({
  315. enabled: 1,
  316. })
  317. .then(() => {
  318. // Configure nginx
  319. return internalNginx.configure(redirectionHostModel, "redirection_host", row);
  320. })
  321. .then(() => {
  322. // Add to audit log
  323. return internalAuditLog.add(access, {
  324. action: "enabled",
  325. object_type: "redirection-host",
  326. object_id: row.id,
  327. meta: _.omit(row, omissions()),
  328. });
  329. });
  330. })
  331. .then(() => {
  332. return true;
  333. });
  334. },
  335. /**
  336. * @param {Access} access
  337. * @param {Object} data
  338. * @param {Number} data.id
  339. * @param {String} [data.reason]
  340. * @returns {Promise}
  341. */
  342. disable: (access, data) => {
  343. return access
  344. .can("redirection_hosts:update", data.id)
  345. .then(() => {
  346. return internalRedirectionHost.get(access, { id: data.id });
  347. })
  348. .then((row) => {
  349. if (!row || !row.id) {
  350. throw new errs.ItemNotFoundError(data.id);
  351. }
  352. if (!row.enabled) {
  353. throw new errs.ValidationError("Host is already disabled");
  354. }
  355. row.enabled = 0;
  356. return redirectionHostModel
  357. .query()
  358. .where("id", row.id)
  359. .patch({
  360. enabled: 0,
  361. })
  362. .then(() => {
  363. // Delete Nginx Config
  364. return internalNginx.deleteConfig("redirection_host", row).then(() => {
  365. return internalNginx.reload();
  366. });
  367. })
  368. .then(() => {
  369. // Add to audit log
  370. return internalAuditLog.add(access, {
  371. action: "disabled",
  372. object_type: "redirection-host",
  373. object_id: row.id,
  374. meta: _.omit(row, omissions()),
  375. });
  376. });
  377. })
  378. .then(() => {
  379. return true;
  380. });
  381. },
  382. /**
  383. * All Hosts
  384. *
  385. * @param {Access} access
  386. * @param {Array} [expand]
  387. * @param {String} [search_query]
  388. * @returns {Promise}
  389. */
  390. getAll: (access, expand, search_query) => {
  391. return access
  392. .can("redirection_hosts:list")
  393. .then((access_data) => {
  394. const query = redirectionHostModel
  395. .query()
  396. .where("is_deleted", 0)
  397. .groupBy("id")
  398. .allowGraph("[owner,certificate]")
  399. .orderBy(castJsonIfNeed("domain_names"), "ASC");
  400. if (access_data.permission_visibility !== "all") {
  401. query.andWhere("owner_user_id", access.token.getUserId(1));
  402. }
  403. // Query is used for searching
  404. if (typeof search_query === "string" && search_query.length > 0) {
  405. query.where(function () {
  406. this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`);
  407. });
  408. }
  409. if (typeof expand !== "undefined" && expand !== null) {
  410. query.withGraphFetched(`[${expand.join(", ")}]`);
  411. }
  412. return query.then(utils.omitRows(omissions()));
  413. })
  414. .then((rows) => {
  415. if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
  416. return internalHost.cleanAllRowsCertificateMeta(rows);
  417. }
  418. return rows;
  419. });
  420. },
  421. /**
  422. * Report use
  423. *
  424. * @param {Number} user_id
  425. * @param {String} visibility
  426. * @returns {Promise}
  427. */
  428. getCount: (user_id, visibility) => {
  429. const query = redirectionHostModel.query().count("id as count").where("is_deleted", 0);
  430. if (visibility !== "all") {
  431. query.andWhere("owner_user_id", user_id);
  432. }
  433. return query.first().then((row) => {
  434. return Number.parseInt(row.count, 10);
  435. });
  436. },
  437. };
  438. export default internalRedirectionHost;