dead-host.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. 'use strict';
  2. const _ = require('lodash');
  3. const error = require('../lib/error');
  4. const deadHostModel = require('../models/dead_host');
  5. const internalHost = require('./host');
  6. const internalNginx = require('./nginx');
  7. const internalAuditLog = require('./audit-log');
  8. const internalCertificate = require('./certificate');
  9. function omissions () {
  10. return ['is_deleted'];
  11. }
  12. const internalDeadHost = {
  13. /**
  14. * @param {Access} access
  15. * @param {Object} data
  16. * @returns {Promise}
  17. */
  18. create: (access, data) => {
  19. let create_certificate = data.certificate_id === 'new';
  20. if (create_certificate) {
  21. delete data.certificate_id;
  22. }
  23. return access.can('dead_hosts:create', data)
  24. .then(access_data => {
  25. // Get a list of the domain names and check each of them against existing records
  26. let domain_name_check_promises = [];
  27. data.domain_names.map(function (domain_name) {
  28. domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
  29. });
  30. return Promise.all(domain_name_check_promises)
  31. .then(check_results => {
  32. check_results.map(function (result) {
  33. if (result.is_taken) {
  34. throw new error.ValidationError(result.hostname + ' is already in use');
  35. }
  36. });
  37. });
  38. })
  39. .then(() => {
  40. // At this point the domains should have been checked
  41. data.owner_user_id = access.token.get('attrs').id;
  42. return deadHostModel
  43. .query()
  44. .omit(omissions())
  45. .insertAndFetch(data);
  46. })
  47. .then(row => {
  48. if (create_certificate) {
  49. return internalCertificate.createQuickCertificate(access, data)
  50. .then(cert => {
  51. // update host with cert id
  52. return internalDeadHost.update(access, {
  53. id: row.id,
  54. certificate_id: cert.id
  55. });
  56. })
  57. .then(() => {
  58. return row;
  59. });
  60. } else {
  61. return row;
  62. }
  63. })
  64. .then(row => {
  65. // re-fetch with cert
  66. return internalDeadHost.get(access, {
  67. id: row.id,
  68. expand: ['certificate', 'owner']
  69. });
  70. })
  71. .then(row => {
  72. // Configure nginx
  73. return internalNginx.configure(deadHostModel, 'dead_host', row)
  74. .then(() => {
  75. return row;
  76. });
  77. })
  78. .then(row => {
  79. data.meta = _.assign({}, data.meta || {}, row.meta);
  80. // Add to audit log
  81. return internalAuditLog.add(access, {
  82. action: 'created',
  83. object_type: 'dead-host',
  84. object_id: row.id,
  85. meta: data
  86. })
  87. .then(() => {
  88. return row;
  89. });
  90. });
  91. },
  92. /**
  93. * @param {Access} access
  94. * @param {Object} data
  95. * @param {Integer} data.id
  96. * @return {Promise}
  97. */
  98. update: (access, data) => {
  99. let create_certificate = data.certificate_id === 'new';
  100. if (create_certificate) {
  101. delete data.certificate_id;
  102. }
  103. return access.can('dead_hosts:update', data.id)
  104. .then(access_data => {
  105. // Get a list of the domain names and check each of them against existing records
  106. let domain_name_check_promises = [];
  107. if (typeof data.domain_names !== 'undefined') {
  108. data.domain_names.map(function (domain_name) {
  109. domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'dead', data.id));
  110. });
  111. return Promise.all(domain_name_check_promises)
  112. .then(check_results => {
  113. check_results.map(function (result) {
  114. if (result.is_taken) {
  115. throw new error.ValidationError(result.hostname + ' is already in use');
  116. }
  117. });
  118. });
  119. }
  120. })
  121. .then(() => {
  122. return internalDeadHost.get(access, {id: data.id});
  123. })
  124. .then(row => {
  125. if (row.id !== data.id) {
  126. // Sanity check that something crazy hasn't happened
  127. throw new error.InternalValidationError('404 Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
  128. }
  129. if (create_certificate) {
  130. return internalCertificate.createQuickCertificate(access, {
  131. domain_names: data.domain_names || row.domain_names,
  132. meta: _.assign({}, row.meta, data.meta)
  133. })
  134. .then(cert => {
  135. // update host with cert id
  136. data.certificate_id = cert.id;
  137. })
  138. .then(() => {
  139. return row;
  140. });
  141. } else {
  142. return row;
  143. }
  144. })
  145. .then(row => {
  146. // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
  147. data = _.assign({}, {
  148. domain_names: row.domain_names
  149. },data);
  150. return deadHostModel
  151. .query()
  152. .where({id: data.id})
  153. .patch(data)
  154. .then(saved_row => {
  155. // Add to audit log
  156. return internalAuditLog.add(access, {
  157. action: 'updated',
  158. object_type: 'dead-host',
  159. object_id: row.id,
  160. meta: data
  161. })
  162. .then(() => {
  163. return _.omit(saved_row, omissions());
  164. });
  165. });
  166. })
  167. .then(() => {
  168. return internalDeadHost.get(access, {
  169. id: data.id,
  170. expand: ['owner', 'certificate']
  171. })
  172. .then(row => {
  173. // Configure nginx
  174. return internalNginx.configure(deadHostModel, 'dead_host', row)
  175. .then(() => {
  176. return _.omit(row, omissions());
  177. });
  178. });
  179. });
  180. },
  181. /**
  182. * @param {Access} access
  183. * @param {Object} data
  184. * @param {Integer} data.id
  185. * @param {Array} [data.expand]
  186. * @param {Array} [data.omit]
  187. * @return {Promise}
  188. */
  189. get: (access, data) => {
  190. if (typeof data === 'undefined') {
  191. data = {};
  192. }
  193. return access.can('dead_hosts:get', data.id)
  194. .then(access_data => {
  195. let query = deadHostModel
  196. .query()
  197. .where('is_deleted', 0)
  198. .andWhere('id', data.id)
  199. .allowEager('[owner,certificate]')
  200. .first();
  201. if (access_data.permission_visibility !== 'all') {
  202. query.andWhere('owner_user_id', access.token.get('attrs').id);
  203. }
  204. // Custom omissions
  205. if (typeof data.omit !== 'undefined' && data.omit !== null) {
  206. query.omit(data.omit);
  207. }
  208. if (typeof data.expand !== 'undefined' && data.expand !== null) {
  209. query.eager('[' + data.expand.join(', ') + ']');
  210. }
  211. return query;
  212. })
  213. .then(row => {
  214. if (row) {
  215. return _.omit(row, omissions());
  216. } else {
  217. throw new error.ItemNotFoundError(data.id);
  218. }
  219. });
  220. },
  221. /**
  222. * @param {Access} access
  223. * @param {Object} data
  224. * @param {Integer} data.id
  225. * @param {String} [data.reason]
  226. * @returns {Promise}
  227. */
  228. delete: (access, data) => {
  229. return access.can('dead_hosts:delete', data.id)
  230. .then(() => {
  231. return internalDeadHost.get(access, {id: data.id});
  232. })
  233. .then(row => {
  234. if (!row) {
  235. throw new error.ItemNotFoundError(data.id);
  236. }
  237. return deadHostModel
  238. .query()
  239. .where('id', row.id)
  240. .patch({
  241. is_deleted: 1
  242. })
  243. .then(() => {
  244. // Delete Nginx Config
  245. return internalNginx.deleteConfig('dead_host', row)
  246. .then(() => {
  247. return internalNginx.reload();
  248. });
  249. })
  250. .then(() => {
  251. // Add to audit log
  252. return internalAuditLog.add(access, {
  253. action: 'deleted',
  254. object_type: 'dead-host',
  255. object_id: row.id,
  256. meta: _.omit(row, omissions())
  257. });
  258. });
  259. })
  260. .then(() => {
  261. return true;
  262. });
  263. },
  264. /**
  265. * All Hosts
  266. *
  267. * @param {Access} access
  268. * @param {Array} [expand]
  269. * @param {String} [search_query]
  270. * @returns {Promise}
  271. */
  272. getAll: (access, expand, search_query) => {
  273. return access.can('dead_hosts:list')
  274. .then(access_data => {
  275. let query = deadHostModel
  276. .query()
  277. .where('is_deleted', 0)
  278. .groupBy('id')
  279. .omit(['is_deleted'])
  280. .allowEager('[owner,certificate]')
  281. .orderBy('domain_names', 'ASC');
  282. if (access_data.permission_visibility !== 'all') {
  283. query.andWhere('owner_user_id', access.token.get('attrs').id);
  284. }
  285. // Query is used for searching
  286. if (typeof search_query === 'string') {
  287. query.where(function () {
  288. this.where('domain_names', 'like', '%' + search_query + '%');
  289. });
  290. }
  291. if (typeof expand !== 'undefined' && expand !== null) {
  292. query.eager('[' + expand.join(', ') + ']');
  293. }
  294. return query;
  295. });
  296. },
  297. /**
  298. * Report use
  299. *
  300. * @param {Integer} user_id
  301. * @param {String} visibility
  302. * @returns {Promise}
  303. */
  304. getCount: (user_id, visibility) => {
  305. let query = deadHostModel
  306. .query()
  307. .count('id as count')
  308. .where('is_deleted', 0);
  309. if (visibility !== 'all') {
  310. query.andWhere('owner_user_id', user_id);
  311. }
  312. return query.first()
  313. .then(row => {
  314. return parseInt(row.count, 10);
  315. });
  316. }
  317. };
  318. module.exports = internalDeadHost;