dead-host.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. const _ = require('lodash');
  2. const error = require('../lib/error');
  3. const deadHostModel = require('../models/dead_host');
  4. const internalHost = require('./host');
  5. const internalNginx = require('./nginx');
  6. const internalAuditLog = require('./audit-log');
  7. const internalCertificate = require('./certificate');
  8. function omissions () {
  9. return ['is_deleted'];
  10. }
  11. const internalDeadHost = {
  12. /**
  13. * @param {Access} access
  14. * @param {Object} data
  15. * @returns {Promise}
  16. */
  17. create: (access, data) => {
  18. let create_certificate = data.certificate_id === 'new';
  19. if (create_certificate) {
  20. delete data.certificate_id;
  21. }
  22. return access.can('dead_hosts:create', data)
  23. .then((/*access_data*/) => {
  24. // Get a list of the domain names and check each of them against existing records
  25. let domain_name_check_promises = [];
  26. data.domain_names.map(function (domain_name) {
  27. domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
  28. });
  29. return Promise.all(domain_name_check_promises)
  30. .then((check_results) => {
  31. check_results.map(function (result) {
  32. if (result.is_taken) {
  33. throw new error.ValidationError(result.hostname + ' is already in use');
  34. }
  35. });
  36. });
  37. })
  38. .then(() => {
  39. // At this point the domains should have been checked
  40. data.owner_user_id = access.token.getUserId(1);
  41. data = internalHost.cleanSslHstsData(data);
  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 {Number} 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. data = internalHost.cleanSslHstsData(data, row);
  151. return deadHostModel
  152. .query()
  153. .where({id: data.id})
  154. .patch(data)
  155. .then((saved_row) => {
  156. // Add to audit log
  157. return internalAuditLog.add(access, {
  158. action: 'updated',
  159. object_type: 'dead-host',
  160. object_id: row.id,
  161. meta: data
  162. })
  163. .then(() => {
  164. return _.omit(saved_row, omissions());
  165. });
  166. });
  167. })
  168. .then(() => {
  169. return internalDeadHost.get(access, {
  170. id: data.id,
  171. expand: ['owner', 'certificate']
  172. })
  173. .then((row) => {
  174. // Configure nginx
  175. return internalNginx.configure(deadHostModel, 'dead_host', row)
  176. .then((new_meta) => {
  177. row.meta = new_meta;
  178. row = internalHost.cleanRowCertificateMeta(row);
  179. return _.omit(row, omissions());
  180. });
  181. });
  182. });
  183. },
  184. /**
  185. * @param {Access} access
  186. * @param {Object} data
  187. * @param {Number} data.id
  188. * @param {Array} [data.expand]
  189. * @param {Array} [data.omit]
  190. * @return {Promise}
  191. */
  192. get: (access, data) => {
  193. if (typeof data === 'undefined') {
  194. data = {};
  195. }
  196. return access.can('dead_hosts:get', data.id)
  197. .then((access_data) => {
  198. let query = deadHostModel
  199. .query()
  200. .where('is_deleted', 0)
  201. .andWhere('id', data.id)
  202. .allowEager('[owner,certificate]')
  203. .first();
  204. if (access_data.permission_visibility !== 'all') {
  205. query.andWhere('owner_user_id', access.token.getUserId(1));
  206. }
  207. // Custom omissions
  208. if (typeof data.omit !== 'undefined' && data.omit !== null) {
  209. query.omit(data.omit);
  210. }
  211. if (typeof data.expand !== 'undefined' && data.expand !== null) {
  212. query.eager('[' + data.expand.join(', ') + ']');
  213. }
  214. return query;
  215. })
  216. .then((row) => {
  217. if (row) {
  218. row = internalHost.cleanRowCertificateMeta(row);
  219. return _.omit(row, omissions());
  220. } else {
  221. throw new error.ItemNotFoundError(data.id);
  222. }
  223. });
  224. },
  225. /**
  226. * @param {Access} access
  227. * @param {Object} data
  228. * @param {Number} data.id
  229. * @param {String} [data.reason]
  230. * @returns {Promise}
  231. */
  232. delete: (access, data) => {
  233. return access.can('dead_hosts:delete', data.id)
  234. .then(() => {
  235. return internalDeadHost.get(access, {id: data.id});
  236. })
  237. .then((row) => {
  238. if (!row) {
  239. throw new error.ItemNotFoundError(data.id);
  240. }
  241. return deadHostModel
  242. .query()
  243. .where('id', row.id)
  244. .patch({
  245. is_deleted: 1
  246. })
  247. .then(() => {
  248. // Delete Nginx Config
  249. return internalNginx.deleteConfig('dead_host', row)
  250. .then(() => {
  251. return internalNginx.reload();
  252. });
  253. })
  254. .then(() => {
  255. // Add to audit log
  256. return internalAuditLog.add(access, {
  257. action: 'deleted',
  258. object_type: 'dead-host',
  259. object_id: row.id,
  260. meta: _.omit(row, omissions())
  261. });
  262. });
  263. })
  264. .then(() => {
  265. return true;
  266. });
  267. },
  268. /**
  269. * @param {Access} access
  270. * @param {Object} data
  271. * @param {Number} data.id
  272. * @param {String} [data.reason]
  273. * @returns {Promise}
  274. */
  275. enable: (access, data) => {
  276. return access.can('dead_hosts:update', data.id)
  277. .then(() => {
  278. return internalDeadHost.get(access, {
  279. id: data.id,
  280. expand: ['certificate', 'owner']
  281. });
  282. })
  283. .then((row) => {
  284. if (!row) {
  285. throw new error.ItemNotFoundError(data.id);
  286. } else if (row.enabled) {
  287. throw new error.ValidationError('Host is already enabled');
  288. }
  289. row.enabled = 1;
  290. return deadHostModel
  291. .query()
  292. .where('id', row.id)
  293. .patch({
  294. enabled: 1
  295. })
  296. .then(() => {
  297. // Configure nginx
  298. return internalNginx.configure(deadHostModel, 'dead_host', row);
  299. })
  300. .then(() => {
  301. // Add to audit log
  302. return internalAuditLog.add(access, {
  303. action: 'enabled',
  304. object_type: 'dead-host',
  305. object_id: row.id,
  306. meta: _.omit(row, omissions())
  307. });
  308. });
  309. })
  310. .then(() => {
  311. return true;
  312. });
  313. },
  314. /**
  315. * @param {Access} access
  316. * @param {Object} data
  317. * @param {Number} data.id
  318. * @param {String} [data.reason]
  319. * @returns {Promise}
  320. */
  321. disable: (access, data) => {
  322. return access.can('dead_hosts:update', data.id)
  323. .then(() => {
  324. return internalDeadHost.get(access, {id: data.id});
  325. })
  326. .then((row) => {
  327. if (!row) {
  328. throw new error.ItemNotFoundError(data.id);
  329. } else if (!row.enabled) {
  330. throw new error.ValidationError('Host is already disabled');
  331. }
  332. row.enabled = 0;
  333. return deadHostModel
  334. .query()
  335. .where('id', row.id)
  336. .patch({
  337. enabled: 0
  338. })
  339. .then(() => {
  340. // Delete Nginx Config
  341. return internalNginx.deleteConfig('dead_host', row)
  342. .then(() => {
  343. return internalNginx.reload();
  344. });
  345. })
  346. .then(() => {
  347. // Add to audit log
  348. return internalAuditLog.add(access, {
  349. action: 'disabled',
  350. object_type: 'dead-host',
  351. object_id: row.id,
  352. meta: _.omit(row, omissions())
  353. });
  354. });
  355. })
  356. .then(() => {
  357. return true;
  358. });
  359. },
  360. /**
  361. * All Hosts
  362. *
  363. * @param {Access} access
  364. * @param {Array} [expand]
  365. * @param {String} [search_query]
  366. * @returns {Promise}
  367. */
  368. getAll: (access, expand, search_query) => {
  369. return access.can('dead_hosts:list')
  370. .then((access_data) => {
  371. let query = deadHostModel
  372. .query()
  373. .where('is_deleted', 0)
  374. .groupBy('id')
  375. .omit(['is_deleted'])
  376. .allowEager('[owner,certificate]')
  377. .orderBy('domain_names', 'ASC');
  378. if (access_data.permission_visibility !== 'all') {
  379. query.andWhere('owner_user_id', access.token.getUserId(1));
  380. }
  381. // Query is used for searching
  382. if (typeof search_query === 'string') {
  383. query.where(function () {
  384. this.where('domain_names', 'like', '%' + search_query + '%');
  385. });
  386. }
  387. if (typeof expand !== 'undefined' && expand !== null) {
  388. query.eager('[' + expand.join(', ') + ']');
  389. }
  390. return query;
  391. })
  392. .then((rows) => {
  393. if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
  394. return internalHost.cleanAllRowsCertificateMeta(rows);
  395. }
  396. return rows;
  397. });
  398. },
  399. /**
  400. * Report use
  401. *
  402. * @param {Number} user_id
  403. * @param {String} visibility
  404. * @returns {Promise}
  405. */
  406. getCount: (user_id, visibility) => {
  407. let query = deadHostModel
  408. .query()
  409. .count('id as count')
  410. .where('is_deleted', 0);
  411. if (visibility !== 'all') {
  412. query.andWhere('owner_user_id', user_id);
  413. }
  414. return query.first()
  415. .then((row) => {
  416. return parseInt(row.count, 10);
  417. });
  418. }
  419. };
  420. module.exports = internalDeadHost;