proxy-host.js 12 KB

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