stream.js 10 KB

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